Arcturus' RoomSettingsComposer appends an extra int at the end of the
payload — room.isAllowUnderpass() ? 1 : 0 — and RoomSettingsSaveEvent
optionally reads back a boolean at the end (if bytesAvailable > 0).
The renderer side never modeled this trailing field, so the client
couldn't surface or persist it.
- RoomSettingsData: add _allowUnderpass field + getter/setter +
propagation through the .from() copy.
- RoomSettingsDataParser: read one trailing int after the moderation
settings, guarded by 'if(wrapper.bytesAvailable)' so older servers
that don't emit it keep parsing cleanly.
- SaveRoomSettingsComposer: optional trailing allowUnderpass arg. The
server's optional-read guard tolerates 24-arg or 25-arg payloads, so
callers that don't care about the field still send the legacy shape.
Cross-repo reference points:
- Arcturus emit side: Emulator/src/main/java/com/eu/habbo/messages/
outgoing/rooms/RoomSettingsComposer.java line 55.
- Arcturus read side: Emulator/src/main/java/com/eu/habbo/messages/
incoming/rooms/RoomSettingsSaveEvent.java lines 133-135.
Net client tsgo error count: 3 -> 0 on the NavigatorRoomSettings cluster.
Two tsgo nits that propagate to the client when the renderer is linked
in:
- packages/utils/src/NitroConfig.ts declared
'NitroConfig?: { [index: string]: any }' on Window, but Nitro-V3
declares 'NitroConfig?: Record<string, unknown>' in its
react-app-env.d.ts. The two declaration-merging fragments must
match — switching the renderer side to Record<string, unknown>
unifies them.
- packages/assets/src/AssetManager.ts: 'import.meta.glob(...)' is
augmented as Record<string, string> in the client's typedef, so
'mod.default ?? mod' (defensive handling of either string or
{ default: string }) failed because mod is typed string. Cast
inline: '((mod as { default?: string }).default ?? mod)'.
Renderer tsgo error count: 3 -> 0.
- SocketConnection.processMessage() did 'new events[0].parserClass()'
where parserClass is typed as 'Function' on IMessageEvent (no
construct signature). Cast to 'new () => IMessageParser' at the
call site so the spawned instance is type-correct downstream.
- RoomChatHandler dispatched RoomSessionChatEvent with the args in
the wrong order: '[]' (intended as the 'links' array) was landing
in the 'chatColours' string slot. Swap to '"", []' so links go
to position 8 and chatColours stays a string.
Four sites where Pixi v8's stricter typing tripped tsgo:
- AvatarImage: container.filters is typed as 'readonly Filter[] | null'
in v8 (no longer a single-Filter union). The old fallback branch
'else container.filters = [container.filters, …]' tried to treat a
readonly array as a single Filter; collapsed to the array-spread
path which now covers both undefined and non-empty cases. Added
Filter to the pixi.js import.
- FurnitureBadgeDisplayVisualization.updateSprite() had a 4-arg
override (sprite, asset, scale, layerId) of the parent's 2-arg
signature (scale, layerId). The sprite/asset were never used from
the parameters — the body only mutated 'sprite'. Refactored to
fetch the sprite via this.getSprite(layerId) inside the override
body so the signature matches the base.
- ExtendedSprite: 'renderer.gl' / 'glRenderTarget.resolveTargetFramebuffer'
exist only on WebGLRenderer / GlRenderTarget (not the WebGPU
variants). The runtime check 'renderer.type === RendererType.WEBGL'
guarantees this; cast at the boundary to satisfy the typechecker.
- TextureUtils.generateImage: Pixi v8's Extractor.image() returns the
union ImageLike (HTMLCanvasElement | HTMLImageElement); the public
signature promises HTMLImageElement. Cast at return — the Pixi
default backend returns HTMLImageElement here.
Arcturus' RequestRoomLoadEvent reads the two extra ints only when
the inbound packet has 8+ bytes remaining after roomId+password, so
the renderer can send 2-arg or 4-arg payloads against the same
header. The client already calls 'new RoomEnterComposer(roomId,
password, spawnX, spawnY)' in two places inside RoomSession /
RoomSessionManager (the reconnect/respawn flow) — the composer
signature is what was lagging behind.
Server-side reference:
Arcturus-Morningstar-Extended/Emulator/src/main/java/com/eu/habbo/
messages/incoming/rooms/RequestRoomLoadEvent.java
bytesAvailable is a boolean (IMessageDataWrapper.bytesAvailable: boolean,
returns 'there is at least one byte left'); the parser was doing
'wrapper.bytesAvailable < 12' as if it were a count, which both
mis-compares boolean to number and short-circuits incorrectly when
exactly 11 bytes remain.
Align with every other parser in the codebase: 'if(!wrapper ||
!wrapper.bytesAvailable) return false;'. The downstream readInt
calls already throw on truncated packets so the explicit length
check was load-bearing only against malformed inputs that wouldn't
parse anyway.
WiredRoomSettingsRequestComposer and WiredUserVariablesRequestComposer
declared 'implements IMessageComposer<ConstructorParameters<typeof Self>>'
but neither defines a constructor, so ConstructorParameters resolved
to 'any[]' and getMessageArray() returning [] (any[]) failed the
narrower base-type signature () => [].
Both composers send zero payload; type as IMessageComposer<[]>
directly + annotate the return type.
IRoomSession.sendWhisperGroupMessage(userId) was declared in the
interface and implemented in RoomSession by sending 'new
ChatWhisperGroupComposer(userId)' — but no such composer class
exists in the renderer (the file was never created). The only
whisper composer is RoomUnitChatWhisperComposer, which takes
(recipientName, message, styleId), not a userId.
No client call site references sendWhisperGroupMessage (grep across
Nitro-V3/src returned zero hits). Removing the dead interface method
+ broken impl is safer than inventing a ChatWhisperGroupComposer
class with no server-side handler.
TypeScript 5.7 split ArrayBuffer / SharedArrayBuffer at the type level
(ArrayBuffer now exposes resizable/transfer/detached etc; SharedArrayBuffer
doesn't), and parametrized the typed-array constructors so plain
Uint8Array became Uint8Array<ArrayBufferLike>.
The renderer never uses SharedArrayBuffer, so this is type-level only —
narrowing back to ArrayBuffer at the boundaries:
- BinaryReader.readBytes() / .toArrayBuffer() return the underlying
DataView buffer; cast to ArrayBuffer.
- BinaryWriter.getBuffer() same shape.
- WsSessionCrypto.randomNonce() now returns Uint8Array<ArrayBuffer>
(it's always backed by a plain ArrayBuffer); aesGcmEncrypt/Decrypt
nonce parameter retyped accordingly so SubtleCrypto.encrypt accepts
it as BufferSource.
- ArrayBufferToBase64 now accepts Uint8Array | ArrayBufferLike directly
(pako/inflate hands back Uint8Array<ArrayBuffer> which the old
ArrayBuffer-only signature rejected).
The IRoomSession interface was missing three things that have always
existed on the RoomSession implementation:
- `password: string` — the room session's join password (used by the
reconnect flow in RoomSessionManager).
- `sendBackgroundMessage(backgroundImage, backgroundStand, backgroundOverlay, backgroundCard?)`
— sends the profile-background composer (used by the React client's
BackgroundsView).
Plus a signature relaxation:
- `sendChatMessage` / `sendShoutMessage` `chatColour` is now optional.
The implementation already accepted `undefined` (the composer forwards
it through), and every historical call site in the React client passes
only 2 args — making the 3rd optional simply types reality.
Net renderer typecheck: 26 → 23.
The change also drops 7 errors on the consumer side
(see ../Nitro-V3 typecheck after the workspace link picks this up).
CLAUDE.md gotchas updated to reflect the new interface contract.
Each workspace package was still pinning `typescript: ~5.5.x` or
`~5.8.2` in its own devDependencies even though the root bumped to 6.0.3
in 60b1143. The pins were dead (yarn 1 hoists from root) but they're
misleading when reading a single package.json. Bring them all to
`^6.0.3` to match the root.
Other:
- @thumbmarkjs/thumbmarkjs 1.8.1 → 1.9.0 (root + communication package)
- yarn.lock regenerated from a clean install (vitest 4 hoisting was
flaking on the patch vite bump; reverted vite to ^8.0.10)
Adds CLAUDE.md at the repo root: short project context for future
sessions — stack, the 12-workspace layout, the React-friendly v2.1.0
additions (`subscribe()`, `subscribeMessage()`, snapshot getters), build
scripts, and known gotchas (`SessionDataManager.getUserData` does NOT
exist; sendChat* expects 3 args; dispatchEvent is sync).
- CatalogAdminSavePageImagesComposer (header 10060): pageId, headerImage, teaserImage
- CatalogAdminSavePageIconComposer (header 10061): pageId, iconId
- Registered in OutgoingHeader, catalog index, and NitroMessages
- Server handler required in Arcturus to process these packets