Skip to content

feat(game): decode the October 2026 patch's layouts, and name the rest of a session's opcodes - #2

Closed
hanios123 wants to merge 4 commits into
nuriland:mainfrom
hanios123:feat/session2-opcodes
Closed

hanios123 wants to merge 4 commits into
nuriland:mainfrom
hanios123:feat/session2-opcodes

Conversation

@hanios123

Copy link
Copy Markdown

What

Today's decoding work from a 500 s recording of the game (16,111 server messages): decoded 11,353 → 15,613, failed 945 → 0. The branch holds four commits: the Account/Join, Turn and Heading commits from earlier today, and one that reads the rest of the recording.

Fixed (checked against every sample in the recording)

Opcode Change
1D 37 Turn The flag's low four bits each add one byte before the two headings; the trailing 01 is sent only with flag bit 0 clear. The old layout failed 945 of 980. New Heading2.
41 36 Spawn The mask is two bytes since the October patch, four before it. Both are read. Positions match the entity's later Move.
04 38 Hit Switch nibble 0 sends no damage varint (Damage is 0, never otherwise).
02 38 Cast The short form is read; Angle and Duration added. The older fixtures decode with the same tail.

New

  • 00 8D Stats: an entity's attributes, a mask then u32 and u64 groups. 1392/1392 parse.
  • 34 36 Spawn (entities that never moved), 2F 37 Move.

Guessed (game/guess.go)

Target, Combo, SkillUse, Windup, Engage, Threat, Attributes, Height, Effect, Area, Cutscene, Script, and the generic Ref, Tagged, Signal. Each doc comment says what the guess rests on. The other types in the recording are named from their family and not read.

a2k stats marks them guessed, as opposed to known.

wire

Guessed opcodes sit in a guessed list of their own. KnownOnly keeps them; the lock does not count them. Adding them to known doubled the noise frames that look like the game's (go run ./internal/score -n 100 -noise 5: 2869 vs 1419 known frames), and with the separate list it is back to 1478, still 0 locks.

Tests

  • Fixtures are frames of the recording. The synthetic Turn and Cast ones, whose layouts the recording contradicts, are replaced, and TestFight's casts now carry a tail.
  • New real-frame tests for each decoder, and TestReadsKnown checks the guessed and known lists stay apart.

Notes

  • The recording is not included. The fixtures hold only game payloads (entity ids, positions, skill ids), no account or character data.
  • The names in the guessed set are guesses from one recording, and may change when another one disagrees.

🤖 Generated with Claude Code

Account's tail changed: six zeros before the repeated Server and a bare 03
after it, where it used to be four zeros and 03 09 03. Both forms are read
now. The byte before the tail, previously skipped, is read as Faction (1 on
a Light server, 2 on a Dark one in every sample seen).

Join (39 0D), the server the client picked in the lobby, is read: 00 00 00
then the server ID.

Fixtures and tests cover both Account tails, Join, and a Servers list whose
last group of four holds only two entries.
…sition

The flag byte's 0x08 bit was skipped before; it marks whether the message
carries one extra byte. After that, a 16-bit value is always sent twice,
then a trailing 01. Verified against a real capture: the value matches
across repeats for the same entity and differs between entities, but no
sample has shown it change for one entity, so what it represents (heading,
most likely) isn't confirmed -- only the byte layout is.

0x371D moves from the wire decoder's family-match fallback to its known
list, alongside its 371A-371C siblings; `go generate ./wire` found no
fixture drift from the change.
…etimes position

28 37 and 29 37 carry an entity, a flag byte (04 or 02, likely redundant with
the opcode), a position, then a shared 15-byte tail: two headings, a speed,
a changing u16, and a trailing 01. 2A 37 ends with the same 15-byte tail but
has no position -- what comes before it instead varies in length (1 to 3
bytes so far) and isn't understood.

Rather than guess the rule behind that variable lead, headingOnly anchors the
tail from the end of the frame, using the length the reader already knows.
Verified against a real capture: 2A 37's Heading1/Heading2/Speed match the
position-carrying 28/29 37 exactly for the same entity at the same moment.

Which of Heading1/Heading2 is current vs. a target, if either, and what the
two opcodes' difference and the skipped u16 mean, are not confirmed -- only
the byte layout is.

wire/opcode.go gains 3728/3729/372A in its known list; `go generate ./wire`
found no fixture drift.
…t of a session's opcodes

Read from a 500 s recording (16111 server messages): decoded 11353 -> 15613,
failed 945 -> 0.

Fixed, each checked against every sample in the recording:
- Turn (1D 37): the flag's low four bits each add one byte before the two
  headings, and the trailing 01 is sent only with flag bit 0 clear. The old
  layout failed 945 of 980. New Heading2 field: the two differ on a few.
- Spawn (41 36): the mask is two bytes since the patch, four before it; both
  are read. Positions match the entity's later Move.
- Hit (04 38): switch nibble 0 sends no damage varint.
- Cast (02 38): the short form is read, and Angle and Duration added.

Decoded:
- Stats (00 8D): an entity's attributes, a mask then u32 and u64 groups.
- Spawn (34 36): entities that never moved, Move (2F 37).

Guessed, named and read in game/guess.go, each doc comment saying what the
guess rests on: Target, Combo, SkillUse, Windup, Engage, Threat, Attributes,
Height, Effect, Area, Cutscene, Script, and the generic Ref, Tagged and Signal.
The rest of the types in the recording are named from their family and not
read.

wire keeps the guessed opcodes in a list of their own: KnownOnly keeps them,
and the lock does not count them, as a longer known list made noise look like
the game's frames twice as often (internal/score).

Fixtures are frames of the recording; the synthetic Turn and Cast ones, whose
layouts the recording contradicts, are replaced.
@hanios123
hanios123 force-pushed the feat/session2-opcodes branch from 96a407b to dd0a3dc Compare October 10, 2026 16:42
@hanios123

Copy link
Copy Markdown
Author

Superseded by #3: same changes, with the branch renamed and the title and description reworded.

@hanios123 hanios123 closed this Oct 10, 2026
@hanios123
hanios123 deleted the feat/session2-opcodes branch October 10, 2026 16:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant