From e897fec56e479e896809772437bd424264a83276 Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Tue, 19 May 2026 22:17:51 +0200 Subject: [PATCH] docs(claude): RoomUnitParser per-user borderId wire contract Document that the per-user borderId int in RoomUsersComposer (Arcturus Infostand Borders) must be read unconditionally inside the per-user loop in RoomUnitParser, never wrapped in a bytesAvailable guard. Calls out the failure modes on both server shapes (emits / doesn't emit) explicitly so the next contributor doesn't re-introduce the guard "for safety" the way it was earlier today. Points future trailing-int additions at the same parser block for documentation. --- CLAUDE.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/CLAUDE.md b/CLAUDE.md index 1a727f1..f7fe301 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -113,6 +113,35 @@ accepts an optional `allowUnderpass` arg at the end of its parameter list; the server-side `RoomSettingsSaveEvent` reads it under `packet.bytesAvailable() > 0`. +### `RoomUnitParser` per-user `borderId` (Infostand Borders wire contract) + +`RoomUsersComposer` on Arcturus (post `54259f8` / fork commit `8f8f568` +"Infostand Borders") appends `appendInt(getInfostandBorder())` at the +end of EVERY user record — habbo, bot, rentable bot — using `0` as the +constant for records without a real border. To stay wire-aligned with +that, `RoomUnitParser` reads `user.borderId = wrapper.readInt();` +unconditionally inside the per-user loop, after +`roomEntryMethod` / `roomEntryTeleportId`. + +DO NOT wrap this in a `wrapper.bytesAvailable ? readInt() : 0` guard. +`bytesAvailable` is a boolean meaning "any bytes left in the WHOLE +packet?" — not "any bytes left for THIS user". For any non-last user +the guard evaluates `true` (next user's bytes follow) and reads, which +is fine ONLY by coincidence when the server emits borderId per user. +On a server that doesn't emit it, the guard steals 4 bytes from the +next record and cascade-corrupts the whole roster (symptom: users not +seeing each other on room enter). On a server that DOES emit it, +skipping the read leaves 4 unconsumed bytes per record and produces +the same corruption. Both shapes are wrong in a loop; unconditional +read paired with a server contract that always emits is the only +correct combination. + +If you ever need to pair this parser with an older Arcturus that +doesn't emit per-user borderId, the fix is on the server side (add +the cherry-pick), not the parser side. Document any future +trailing-int extension in this same place so the next reader doesn't +re-introduce a bytesAvailable guard "for safety". + ### Dropped dead code: `sendWhisperGroupMessage` `IRoomSession.sendWhisperGroupMessage(userId)` referenced a