Repository navigation
Conversation
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.
…odes Read from a 500 s capture (16111 server messages): decoded 11353 -> 15613, failed 945 -> 0. Fixed, each checked against every sample in the capture: - 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 capture 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 capture; the synthetic Turn and Cast ones, whose layouts the capture contradicts, are replaced.
|
Hey, thanks for putting this together! I'm currently occupied with other projects, so I haven't had a chance to fully take a look at your PR, but I'm going to get to it eventually. Meanwhile, I'm open to ideas on how to make it simpler to register opcodes. I don't think // guesses are opcodes named, and read, on the strength of one recording. Each is a guess until
// another confirms it, see guess.go, and each comment says what the guess is.
var guesses = map[wire.Opcode]op{ |
Hi @nuriland, thanks for taking the time to review this PR and share your feedback! Regarding the guess interface, your argument is totally valid. I wanted to bring this up so we can collaborate on confirming these guesses. What do you think about creating a md doc file to track cases where an opcode has been captured but its status is still unknown? Once we confirm each case, we can open a PR to update its status accordingly. This way, we can keep track of which opcodes have been confirmed and documented, and which ones still need further investigation or if you have a different idea to address this il be happy to hear it. I'll adjust my PR based on your next feedback and suggestions. |
|
I just pushed some of my leftover work into the main tree. There's a small overlap with your PR, sorry about that. I'd focus the PR on the known, but not labeled, opcodes. I have a bunch of work queued up to improve the DX for labelling opcodes in the future. Keep the pos changes and the lobby improvements. Those are good. The guessed opcodes should be removed from the dispatch loop and moved to a tracked Markdown list of opcodes to label in the future. I'll give some thought to managing matrices of test data. Right now the game protocol is dependant on individual recordings, which override eachother. That makes it hard to review/accept feature PRs, which improve the lexer or the instruction dispatch loop. There's no source of truth or spec to manage. All of this will improve in future versions. Thanks! |
Decodes what a 500 s capture holds (16,111 server messages), taken in October 2026 from the game's Steam client: decoded 11,353 → 15,613, failed 945 → 0. The branch also carries the Account/Join, Turn and Heading commits, which are not on
mainyet.Fixed (checked against every sample in the capture)
1D 37Turn01is sent only with flag bit 0 clear. The old layout failed 945 of 980. NewHeading2.41 36SpawnMove.04 38HitDamageis 0, never otherwise).02 38CastAngleandDurationadded. The older fixtures decode with the same tail.New
00 8DStats: an entity's attributes, a mask then u32 and u64 groups. 1392/1392 parse.34 36Spawn (entities that never moved),2F 37Move.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 capture are named from their family and not read.
a2k statsmarks themguessed, as opposed toknown.wire
Guessed opcodes sit in a
guessedlist of their own:KnownOnlykeeps them, the lock does not count them. Adding them toknowndoubled the noise frames that look like the game's (go run ./internal/score -n 100 -noise 5: 2869 vs 1419 known frames); with the separate list it is 1478, still 0 locks.Tests
TestFight's casts now carry a tail.TestReadsKnownchecks the guessed and known lists stay apart.Notes