Download docs/research/CHAT_EXPANSION.md from nmarshall/urt-rs: direct link, hf CLI and curl.
- Browser
- Download file 7.82 kB
-
https://huggingface.co/nmarshall/urt-rs/resolve/main/docs/research/CHAT_EXPANSION.md
- Command line
-
hf download hf://nmarshall/urt-rs/docs/research/CHAT_EXPANSION.md
-
curl -L -o CHAT_EXPANSION.md https://huggingface.co/nmarshall/urt-rs/resolve/main/docs/research/CHAT_EXPANSION.md
Radio/chat expansion states and substitution keys
Static pass, 2026-10-11. Reproduce with python3 scripts/analyze_chat_expansion.py; check with python3 scripts/test_chat_expansion.py. Generated evidence contains verified qagame provenance, complete partitions for parser 186435, lookup 185212 and comparator 69386, plus bounded synthetic traces. No original executable or full QVM runs. Calls are recorded with zero placeholder returns; no formatted/copy/append output is produced. These findings extend chat delivery.
EXP-001: marker parsing and length gate
Parser 186435 checks a source length returned by helper 36038. A value greater than 150 takes diagnostic CG_UTParseRadio: radio string > MAX_MAPPING_STRING\n through helper 2294 and leaves without the copy/clear path. Values 149/150 reach formatter 70386 with destination local 32, capacity 150, template %s and the source pointer, then clear the source's first byte. Acceptance at 150 does not prove preservation of 150 bytes: capacity, formatting truncation and effective string length remain separate questions. The fixtures supply lengths; recorded copy and repeated length calls return zero, so the accepted fixture skips the character loop. It must not be interpreted as a real empty-input transformation.
Per-character slices at 186481–186676 establish the following states. Pending marker lives at local byte 182, key text at local 183, key length at local word 336; current byte is signed through SEX8.
$(36) and#(35) both start a pending marker. An existing marker/key first gets formatter%c%sand append helper 36123, then marker is replaced and key cleared.- While pending, ASCII letters
a–zorA–Zare appended to the key with a NUL. Lookup 185212 receives the sender and each growing prefix, rather than waiting for a word delimiter. - A nonzero lookup result is passed to append helper 36123 and clears the marker. A zero result retains it. Separate post-call fixtures supply both results explicitly; no lookup callee is executed by those parser slices.
- A nonletter while pending goes through
%c%s%c, retaining marker/key/current-byte as formatter arguments, then clears the marker. A signed high byte 255 is forwarded as -1 in the character argument. Without a marker, the byte goes through%c. - End-of-loop pending marker/key takes
%c%sand append. No pending marker means no tail call.
Because lookup happens after each letter, a recognized short key can consume a prefix of a longer letter run. For example, the raw state machine permits matching hp before a later letter is inspected. Actual combined formatted output and buffer bounds require copy/length/append/lookup composition. A synthetic NUL per-character fixture verifies argument routing only; the real loop condition normally ends before its terminator.
EXP-002: exact comparison keys and unresolved substitution bodies
All twelve key comparisons call helper 69386 with bound 99999. Actual byte-loop traces establish ASCII case-insensitive lexicographic equality, terminated-string equality and rejection of a shorter/longer key. It folds lowercase ASCII to uppercase, reads signed bytes, and stops when its decrementing bound is exhausted. Comparison fixtures cover every exact/uppercase/shortened/extended key plus bounded prefix, embedded NUL and signed high-byte cases. This does not establish locale/Unicode matching.
| Key in comparison order | Static body evidence / remaining work |
|---|---|
health |
Four labels and dead override; bounded in EXP-003 |
hp |
Raw object field 684 formatted %i, then possible 0 override; bounded in EXP-003 |
clips |
Selected record word at offset 312 + 4*field144; high byte after mask/unsigned shift. Packing and item limits open |
bullets |
Same selected word, mask 65280 and signed shift 8. Packing semantics open |
weapon |
Low byte of selected word indexes stride 432 at base 4020; word interpreted by %s. Table/identity/bounds open |
location |
Helper 268637 requested with sender, shared buffer and capacity 150; complete location selection open |
gameitem |
Game types 7/8 choose flag/bomb; other path uses helper 36717 bit 1 for coinbird/cat-bird. Randomness and runtime modes open |
gametime |
Cvar/global arithmetic and %01d:%02d or no timelimit; full timing/wrap contract open |
roundtime |
Round-deadline arithmetic, game-type gates and %01d:%02d or no roundtime; full schedule open |
leader |
Helper 169397 and stride-880 object/record name access; return/no-leader handling open |
momo |
Buffer clear, optional referenced-object writes and zero return; bounded in EXP-003 |
crosshair |
Dead fallback or floating-point direction/trap trace/location path; vector, trace, ownership and geometry semantics open |
Lookup initializes a shared output buffer 35738436 through a capacity-150 formatting request for unknown. Recognized ordinary tails return that buffer pointer; unmatched keys take zero return. momo also returns zero after its side effect. Names in this table are exact supplied strings, not inferred command names. Static bodies are retained even where full evaluation is deferred.
EXP-003: health boundaries and momo side effect
The health branch uses signed object field 684 thresholds: <25 yields near death, 25–49 badly wounded, 50–74 wounded, and >=75 healthy. Negative values follow the first branch. It then requests a replacement dead if sender-record offset 4324 is nonzero or record offset 4 is >=3. The hp branch first requests %i with field 684, then 0 under the same predicate. These are formatting requests; the formatter does not execute in the slices.
Thirty-six health fixtures cover -1/0/24/25/49/50/74/75/100 across four state/override combinations. Four hp fixtures preserve raw value 42 while selecting the override. The offset-4 test is >=3 here, unlike chat delivery's ==3 dead flag. Do not unify those predicates when porting without further evidence.
The momo match clears shared buffer byte zero and reads sender-record pointer 6168. If nonzero, it writes global time 34992532 + 3500 to that referenced object's offset 84, and 2 to offset 204. Both NULL and nonzero pointer paths return 0, with no callee calls in these bounded slices. A near-INT_MAX synthetic time fixture produces signed wrap in the evaluator; original interpreter/JIT conformance and timestamp lifecycle remain open. The referenced object's type, animation/UI meaning, reset/update ownership and who invokes this key are not assigned from the string alone.
Remaining work and Rust dependencies
RE-269–271 record parser-state, key-comparison and health/momo milestones. RE-268 remains open for the complete expansion/output contract; RE-272 tracks full capacity-150 copy and append bounds, prefix consumption and buffer aliases, item/ammunition tables, timer arithmetic and cvar identities, location/leader/crosshair callees, momo object writers/consumers and original-runtime/platform fixtures. A Rust implementation will need owned-buffer semantics, exact marker/control-byte behavior and distinct field predicates, backed by completed output specifications before claiming parity.
The ITEM-001–003 follow-up recovers bounded ammo/weapon/game-item/timer paths and thirty sampled name words. Full table, producer and timing/output contracts remain open under RE-272/276.
The LOC-001–003 follow-up recovers leader scan/name paths, location cache/format boundaries and list-building/static spatial evidence. Full caller/output/geometry behavior remains open under RE-272/280.