Recover packed chat item fields and countdown branches
Browse files- docs/FINDINGS.md +6 -0
- docs/PROVISIONAL_TODO.md +1 -1
- docs/RESEARCH_LEDGER.md +4 -0
- docs/REVERSE_ENGINEERING_TODO.md +5 -0
- docs/research/CHAT_EXPANSION.md +2 -0
- docs/research/CHAT_ITEMS_TIME.md +67 -0
- docs/research/README.md +4 -1
- docs/research/chat-items-time.json +0 -0
- scripts/analyze_binaries.py +1 -1
- scripts/analyze_chat_items_time.py +84 -0
- scripts/analyze_deferred_auth.py +6 -4
- scripts/test_chat_items_time.py +102 -0
docs/FINDINGS.md
CHANGED
|
@@ -264,3 +264,9 @@ Bounded qagame paths recover bomb-plant/inactivity correction requests, direct f
|
|
| 264 |
**F-062 — Observed substitution keys:** twelve exact supplied keys use comparator 69386 with bound 99999. Bounded byte traces confirm ASCII case-insensitive terminated equality and reject shortened/extended keys. All bodies are retained; weapon/timer/location/leader/crosshair behavior remains incomplete. [EXP-002](research/CHAT_EXPANSION.md).
|
| 265 |
|
| 266 |
**F-063 — Observed health and momo differences:** health labels split at 25/50/75; health/hp dead override tests offset 4 >=3, whereas delivery's dead flag uses ==3. The momo branch clears the shared buffer, optionally writes now+3500 and 2 through record pointer 6168, and returns zero. Field ownership, formatted output and runtime wrap remain unverified. [EXP-003](research/CHAT_EXPANSION.md).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 264 |
**F-062 — Observed substitution keys:** twelve exact supplied keys use comparator 69386 with bound 99999. Bounded byte traces confirm ASCII case-insensitive terminated equality and reject shortened/extended keys. All bodies are retained; weapon/timer/location/leader/crosshair behavior remains incomplete. [EXP-002](research/CHAT_EXPANSION.md).
|
| 265 |
|
| 266 |
**F-063 — Observed health and momo differences:** health labels split at 25/50/75; health/hp dead override tests offset 4 >=3, whereas delivery's dead flag uses ==3. The momo branch clears the shared buffer, optionally writes now+3500 and 2 through record pointer 6168, and returns zero. Field ownership, formatted output and runtime wrap remain unverified. [EXP-003](research/CHAT_EXPANSION.md).
|
| 267 |
+
|
| 268 |
+
**F-064 — Observed ammunition extraction:** clips/bullets read the high byte and bits 8–15 of a selected packed record word; weapon uses the low byte. Post-format replacement uses offset-4 >=3 or offset-4324 nonzero. Inventory/reload producer semantics and selector bounds remain open. [ITEM-001](research/CHAT_ITEMS_TIME.md).
|
| 269 |
+
|
| 270 |
+
**F-065 — Observed sampled item names:** weapon-name words use 4020+432*low_byte; sampled indices 0–29 include duplicate None strings and a NULL pointer at 18. This does not establish legal IDs or complete table extent. Game-item types 7/8 select flag/bomb; other paths branch on helper-36717's least-significant bit with RNG semantics unresolved. [ITEM-002](research/CHAT_ITEMS_TIME.md).
|
| 271 |
+
|
| 272 |
+
**F-066 — Observed countdown predicates:** gametime uses registered timelimit integer times 60000 minus elapsed; nonpositive results request no timelimit even at expiry. Roundtime requires positive remaining and types 4/5/8/10. Boundary/wrap traces are synthetic; timer writers, active cvar bounds, output and original-runtime conformance remain open. [ITEM-003](research/CHAT_ITEMS_TIME.md).
|
docs/PROVISIONAL_TODO.md
CHANGED
|
@@ -33,7 +33,7 @@ For implementation items, the minimum done condition is a documented contract, a
|
|
| 33 |
|
| 34 |
### First research batch
|
| 35 |
|
| 36 |
-
Progress: [RE-232–234 / TIME-001–003](research/FRAME_TIMING.md) now cover Linux ELF64 elapsed modification, event sources and pause predicates. The [RE-237–239 follow-up](research/EVENT_QUEUES.md) resolves the pause-field address/bot chain and adds queue/journal evidence. The [RE-241–243 announcement pass](research/AUTH_ANNOUNCEMENTS.md) recovers label tables and recipient paths; The [RE-245–247 cvar pass](research/GAME_CVARS.md) adds all 126 main registration rows and resolves auth_verbosity/callers. The [RE-249–251 change pass](research/CVAR_CHANGES.md) adds cache/notification behavior, four numeric correction branches and direct registration coverage. The [RE-253–255 query pass](research/AUTH_QUERIES.md) adds selector helpers, command-role routing and internal authentication modes. The [RE-257–259 bounds/entry pass](research/SELECTOR_BOUNDS.md) adds integer parsing, selector storage/count evidence and native/QVM/chat route connections. The [RE-261–263 chat pass](research/CHAT_PATHS.md) adds native command metadata/filter evidence and bounded message/recipient paths, including the documented argument-mapping correction. The [RE-265–267 delivery pass](research/CHAT_DELIVERY.md) adds bounded dead-chat predicates, formatter argument routing and echo/fanout calls. The [RE-269–271 expansion pass](research/CHAT_EXPANSION.md) adds marker states, twelve key comparisons and health/momo branches. PROV-002 remains open for full output/reset coverage. PROV-001 remains open for complete scheduling and platform coverage.
|
| 37 |
|
| 38 |
Start with the open questions raised by the current binary evidence. These tasks depend on the linked reports, not on a working rewrite.
|
| 39 |
|
|
|
|
| 33 |
|
| 34 |
### First research batch
|
| 35 |
|
| 36 |
+
Progress: [RE-232–234 / TIME-001–003](research/FRAME_TIMING.md) now cover Linux ELF64 elapsed modification, event sources and pause predicates. The [RE-237–239 follow-up](research/EVENT_QUEUES.md) resolves the pause-field address/bot chain and adds queue/journal evidence. The [RE-241–243 announcement pass](research/AUTH_ANNOUNCEMENTS.md) recovers label tables and recipient paths; The [RE-245–247 cvar pass](research/GAME_CVARS.md) adds all 126 main registration rows and resolves auth_verbosity/callers. The [RE-249–251 change pass](research/CVAR_CHANGES.md) adds cache/notification behavior, four numeric correction branches and direct registration coverage. The [RE-253–255 query pass](research/AUTH_QUERIES.md) adds selector helpers, command-role routing and internal authentication modes. The [RE-257–259 bounds/entry pass](research/SELECTOR_BOUNDS.md) adds integer parsing, selector storage/count evidence and native/QVM/chat route connections. The [RE-261–263 chat pass](research/CHAT_PATHS.md) adds native command metadata/filter evidence and bounded message/recipient paths, including the documented argument-mapping correction. The [RE-265–267 delivery pass](research/CHAT_DELIVERY.md) adds bounded dead-chat predicates, formatter argument routing and echo/fanout calls. The [RE-269–271 expansion pass](research/CHAT_EXPANSION.md) adds marker states, twelve key comparisons and health/momo branches. The [RE-273–275 item/time pass](research/CHAT_ITEMS_TIME.md) adds packed ammunition fields, sampled names and countdown branches. PROV-002 remains open for full output/reset coverage. PROV-001 remains open for complete scheduling and platform coverage.
|
| 37 |
|
| 38 |
Start with the open questions raised by the current binary evidence. These tasks depend on the linked reports, not on a working rewrite.
|
| 39 |
|
docs/RESEARCH_LEDGER.md
CHANGED
|
@@ -133,3 +133,7 @@ Completed bounded milestones RE-265–267 with `analyze_chat_delivery.py`: raw f
|
|
| 133 |
## 2026-10-11 — Chat expansion states and substitution keys
|
| 134 |
|
| 135 |
Completed bounded milestones RE-269–271 with `analyze_chat_expansion.py`: parser length/marker/prefix/tail paths, twelve key comparisons and byte-loop fixtures, 36 health/four hp cases and three momo NULL/write/wrap paths. EXP-001–003/F-061–063 distinguish supplied lengths/lookup returns from executed callees, capacity from preservation, and >=3 health death predicates from ==3 delivery predicates. Seven tests cover exact key evidence, comparator bounds, character/return/tail routing, length gates, labels and side-effect writes. RE-268 remains open; RE-272 tracks full buffer/parser/substitution/ownership/runtime/platform composition. No original executable, full QVM or recorded callee executes.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 133 |
## 2026-10-11 — Chat expansion states and substitution keys
|
| 134 |
|
| 135 |
Completed bounded milestones RE-269–271 with `analyze_chat_expansion.py`: parser length/marker/prefix/tail paths, twelve key comparisons and byte-loop fixtures, 36 health/four hp cases and three momo NULL/write/wrap paths. EXP-001–003/F-061–063 distinguish supplied lengths/lookup returns from executed callees, capacity from preservation, and >=3 health death predicates from ==3 delivery predicates. Seven tests cover exact key evidence, comparator bounds, character/return/tail routing, length gates, labels and side-effect writes. RE-268 remains open; RE-272 tracks full buffer/parser/substitution/ownership/runtime/platform composition. No original executable, full QVM or recorded callee executes.
|
| 136 |
+
|
| 137 |
+
## 2026-10-11 — Packed chat item fields and countdowns
|
| 138 |
+
|
| 139 |
+
Completed bounded milestones RE-273–275 with `analyze_chat_items_time.py`: 32 ammo/60 weapon/82 timer slices, thirty file-backed name-word samples and nine game-item mode/bit paths. ITEM-001–003/F-064–066 retain NULL/duplicate samples, explicit table-extent uncertainty, cvar identity and expired-countdown fallback distinctions. Added restricted signed/unsigned right shifts and truncation-based signed remainder to the bounded evaluator. Eight tests check arithmetic failure domains, byte masks, sampled pointers/bounds, death overrides and countdown/type boundaries. RE-272 remains open; RE-276 tracks full producer/table/timer ownership and formatter/runtime/platform behavior. No original process/full QVM or recorded callee executes.
|
docs/REVERSE_ENGINEERING_TODO.md
CHANGED
|
@@ -322,3 +322,8 @@ Purpose: turn the supplied installation into an evidence-backed specification of
|
|
| 322 |
- [x] RE-270 Recover all twelve substitution key comparisons and actual ASCII case-insensitive comparator fixtures; retain every lookup body ([EXP-002](research/CHAT_EXPANSION.md)).
|
| 323 |
- [x] RE-271 Recover health/hp label thresholds/death overrides and momo buffer clear, optional referenced-object writes and zero return ([EXP-003](research/CHAT_EXPANSION.md)).
|
| 324 |
- [ ] RE-272 Compose parser copy/length/append/lookup with exact capacities, prefix consumption and aliases; recover item/ammo tables, time cvars/arithmetic, location/leader/crosshair callees and momo object ownership/consumers; validate original-runtime and platform fixtures.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 322 |
- [x] RE-270 Recover all twelve substitution key comparisons and actual ASCII case-insensitive comparator fixtures; retain every lookup body ([EXP-002](research/CHAT_EXPANSION.md)).
|
| 323 |
- [x] RE-271 Recover health/hp label thresholds/death overrides and momo buffer clear, optional referenced-object writes and zero return ([EXP-003](research/CHAT_EXPANSION.md)).
|
| 324 |
- [ ] RE-272 Compose parser copy/length/append/lookup with exact capacities, prefix consumption and aliases; recover item/ammo tables, time cvars/arithmetic, location/leader/crosshair callees and momo object ownership/consumers; validate original-runtime and platform fixtures.
|
| 325 |
+
|
| 326 |
+
- [x] RE-273 Recover bounded selected packed-word addressing, clips/bullets masks/shifts and dead-state replacement arguments ([ITEM-001](research/CHAT_ITEMS_TIME.md)).
|
| 327 |
+
- [x] RE-274 Recover weapon-name pointer expression, explicitly sampled indices 0–29 including NULL/duplicate entries, and game-item mode/bit branches ([ITEM-002](research/CHAT_ITEMS_TIME.md)).
|
| 328 |
+
- [x] RE-275 Connect gametime to registered timelimit and recover bounded game/round countdown arithmetic, fallback literals and game-type gates ([ITEM-003](research/CHAT_ITEMS_TIME.md)).
|
| 329 |
+
- [ ] RE-276 Recover complete packed-state producers/selector bounds, stride-432 table layout/extent/legal IDs/consumers, helper-36717 semantics, timer globals/cvar lifecycle and full formatter/original-runtime/platform fixtures.
|
docs/research/CHAT_EXPANSION.md
CHANGED
|
@@ -48,3 +48,5 @@ The `momo` match clears shared buffer byte zero and reads sender-record pointer
|
|
| 48 |
## Remaining work and Rust dependencies
|
| 49 |
|
| 50 |
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.
|
|
|
|
|
|
|
|
|
| 48 |
## Remaining work and Rust dependencies
|
| 49 |
|
| 50 |
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.
|
| 51 |
+
|
| 52 |
+
The [ITEM-001–003 follow-up](CHAT_ITEMS_TIME.md) 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.
|
docs/research/CHAT_ITEMS_TIME.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Chat ammunition, item names and countdown substitutions
|
| 2 |
+
|
| 3 |
+
Static pass, 2026-10-11. Reproduce with `python3 scripts/analyze_chat_items_time.py`; check with `python3 scripts/test_chat_items_time.py`. [Generated evidence](chat-items-time.json) retains verified qagame provenance, the lookup partition, cvar identity, 30 sampled name words, 32 ammunition, 60 weapon, 82 timer and nine game-item paths. No original process or full QVM executes. Recorded callees return zero placeholders; formatter arguments are evidence, not generated text. This extends the [expansion key inventory](CHAT_EXPANSION.md).
|
| 4 |
+
|
| 5 |
+
## ITEM-001: selected packed word and ammunition bytes
|
| 6 |
+
|
| 7 |
+
The `clips`, `bullets` and `weapon` branches follow object offset **520** to a record. Record word **144** selects a four-byte word at **record + 312 + 4 * field144**. No selector bounds check appears in these branch bodies. Complete record layout and every producer of the selector/packed word remain open.
|
| 8 |
+
|
| 9 |
+
The formatter requests are:
|
| 10 |
+
|
| 11 |
+
| Key | Selected packed-word extraction | Initial template |
|
| 12 |
+
| --- | --- | --- |
|
| 13 |
+
| `clips` | `(word & 0xff000000) >> 24`, unsigned right shift | `%i` |
|
| 14 |
+
| `bullets` | `(word & 0x0000ff00) >> 8`, signed right shift after masking | `%i` |
|
| 15 |
+
| `weapon` | `word & 0xff`, then name-pointer lookup below | `%s` |
|
| 16 |
+
|
| 17 |
+
Both numerical results lie in 0–255 for the evaluated instruction domain. The byte at bits 16–23 is not used by these substitutions; this pass assigns no meaning to it. Fixtures use 0, 0x01020304, 0xfffe001d and -1, selected word index 2, and four raw state/override combinations.
|
| 18 |
+
|
| 19 |
+
After formatting the extracted value, `clips` and `bullets` request replacement `0` when record offset **4324** is nonzero or record offset **4 >=3**. `weapon` requests `None` under the same predicate. As with health/hp, this comparison differs from delivery's offset-4 **==3** dead flag. Numeric labels suggest ammunition counts but do not establish inventory capacity, reload consumption, sentinel conventions or valid packed states.
|
| 20 |
+
|
| 21 |
+
The bounded evaluator now supports signed/unsigned right shifts restricted to counts 0–31, and signed remainder using truncation toward zero. Tests distinguish arithmetic/logical shift, positive/negative remainder and reject divide-by-zero, INT_MIN/-1 and unsupported shift counts. This remains a synthetic evaluator, not proof of original interpreter/JIT semantics.
|
| 22 |
+
|
| 23 |
+
## ITEM-002: name-pointer samples and game-item selection
|
| 24 |
+
|
| 25 |
+
The weapon branch loads a pointer from **4020 + 432 * low_byte** and forwards it to `%s` with output buffer 35738436 and capacity 150. The collector samples **indices 0–29 only** from file-backed initialized words, checking bounds, string pointers and termination. This sample does not establish the table's extent or legal weapon IDs. There is no visible low-byte bounds check before the lookup in this branch; the legal domain must be recovered from packed-field producers and the complete table.
|
| 26 |
+
|
| 27 |
+
| Index | Supplied name | Index | Supplied name |
|
| 28 |
+
| ---: | --- | ---: | --- |
|
| 29 |
+
| 0 | None | 15 | AK103 7.62mm |
|
| 30 |
+
| 1 | Knife | 16 | Bomb |
|
| 31 |
+
| 2 | Beretta 92G | 17 | IMI Negev |
|
| 32 |
+
| 3 | IMI DE .50 AE | 18 | **NULL pointer** |
|
| 33 |
+
| 4 | Franchi SPAS12 | 19 | M4A1 |
|
| 34 |
+
| 5 | HK MP5K | 20 | Glock 18 |
|
| 35 |
+
| 6 | HK UMP45 | 21 | Colt 1911 |
|
| 36 |
+
| 7 | HK69 40mm | 22 | Ingram Mac 11 |
|
| 37 |
+
| 8 | ZM LR300 | 23 | FR-F1 |
|
| 38 |
+
| 9 | HK G36 | 24 | Benelli M4 Super 90 |
|
| 39 |
+
| 10 | HK PSG1 | 25 | FN P90 |
|
| 40 |
+
| 11 | HE Grenade | 26 | Magnum |
|
| 41 |
+
| 12 | Flash Grenade | 27 | FS TOD-50 |
|
| 42 |
+
| 13 | Smoke Grenade | 28 | None |
|
| 43 |
+
| 14 | Remington SR8 | 29 | Kick |
|
| 44 |
+
|
| 45 |
+
Indices 0/28 point to the same literal `None`; index 18 is a zero pointer, not that string. Its synthetic live-state path forwards zero to the formatter. This does not establish actual NULL formatting or that index 18 is reachable in normal gameplay. Sixty bounded paths sample all thirty indices with raw states 0/3; other packed bytes are deliberately nonzero to check low-byte extraction.
|
| 46 |
+
|
| 47 |
+
The `gameitem` branch selects copy-helper-36185 source `flag` for game-type **7**, and `bomb` for **8**. Otherwise helper **36717** supplies a value whose least-significant bit (mask **1**) chooses **`coinbird` when set**, **`cat-bird` when clear**. Whole-branch fixtures record the helper with placeholder zero; isolated post-call fixtures explicitly supply 0/1/2/3/-1. Neither random-number generation nor original game-mode identities/distribution are established here.
|
| 48 |
+
|
| 49 |
+
## ITEM-003: game-time and round-time branches
|
| 50 |
+
|
| 51 |
+
The `gametime` branch reads integer mirror **34088124**, identified through the verified main registry as **timelimit** at mirror base 34088112, supplied default **20**, raw flags **1029**. Registration defaults do not establish the active runtime value. In the bounded 32-bit arithmetic domain it computes:
|
| 52 |
+
|
| 53 |
+
```
|
| 54 |
+
remaining = wrap32(timelimit_integer * 60000 - wrap32(now - start))
|
| 55 |
+
now = global 34992532
|
| 56 |
+
start = global 34992544
|
| 57 |
+
```
|
| 58 |
+
|
| 59 |
+
Positive remaining requests `%01d:%02d` with `remaining / 60000` minutes and `(remaining / 1000) % 60` seconds. Nonpositive remaining requests **`no timelimit`**, including the exhausted-countdown path. There is no separate test for timelimit==0 in this slice: a future start can affect the arithmetic. At one minute, now=59999/start=0 still takes the positive branch with arguments **0/0**; now=60000 reaches the literal fallback. No formatting callee runs, so these are argument values rather than verified screen text.
|
| 60 |
+
|
| 61 |
+
Ten fixtures cover default, zero/negative limits, exact expiry, future start, multiplication overflow and signed time wrap. Synthetic limit 35792 wraps its minute multiplication negative and chooses the fallback; legal cvar bounds and original arithmetic conformance remain open.
|
| 62 |
+
|
| 63 |
+
The `roundtime` branch computes signed wrapped **global 34992548 - now**. It requests the same minutes/seconds format only when the result is positive **and game type is one of 4/5/8/10**. All other observed paths request **`no roundtime`**. Seventy-two fixtures cover game types 0–11 and deltas -1/0/1/59999/60000/61000. The deadline's complete initialization/update/reset ownership and game-type enumeration are not recovered here.
|
| 64 |
+
|
| 65 |
+
## Remaining work and rewrite dependencies
|
| 66 |
+
|
| 67 |
+
RE-273–275 record these bounded milestones; RE-272 remains open. RE-276 adds full packed-state producers, selector bounds, complete stride-432 table layout/extent/consumers and legal IDs, helper 36717 semantics, timer global/cvar lifecycle and original-runtime/platform fixtures. Rust must retain separate packed bytes and signed countdown predicates while avoiding an invented NULL-name policy or treating the thirty sampled words as a complete item table. Output and gameplay parity depend on the remaining producer, formatting and timing evidence.
|
docs/research/README.md
CHANGED
|
@@ -38,10 +38,11 @@ python3 scripts/test_selector_bounds.py
|
|
| 38 |
python3 scripts/test_chat_paths.py
|
| 39 |
python3 scripts/test_chat_delivery.py
|
| 40 |
python3 scripts/test_chat_expansion.py
|
|
|
|
| 41 |
python3 scripts/storage_policy.py check --require-setup
|
| 42 |
```
|
| 43 |
|
| 44 |
-
The pipeline runs `fetch_analysis_references.py`, `analyze_qvm.py`, `analyze_native.py`, `analyze_vm_abi.py`, `analyze_native_dispatch.py`, `analyze_vm_extensions.py`, `analyze_native_metadata.py`, `analyze_native_coverage.py`, `analyze_auth_drop.py`, `analyze_deferred_auth.py`, `analyze_engine_control.py`, `analyze_frame_timing.py`, `analyze_event_queues.py`, `analyze_auth_announcements.py`, `analyze_game_cvars.py`, `analyze_cvar_changes.py`, `analyze_auth_queries.py`, `analyze_selector_bounds.py`, `analyze_chat_paths.py`, `analyze_chat_delivery.py`, and `
|
| 45 |
|
| 46 |
Generated JSON/JSONL and compressed evidence are regenerated in place; keep prose notes in Markdown. Rerunning the four analysis stages reproduced every generated report and gzip artifact byte-for-byte on the same toolchain. gzip timestamps and stored filenames are fixed. Different objdump versions can produce different disassembly text, so tool version and uncompressed evidence hashes are recorded. Reports are UTF-8 normal Git text, with individual files below the storage policy's 2 MiB limit.
|
| 47 |
|
|
@@ -85,3 +86,5 @@ The extension collector adds five focused tests for native tail calls, command-r
|
|
| 85 |
[Chat policy, formats and delivery calls](CHAT_DELIVERY.md) adds RE-265–267: 512 bounded policy paths, mode/formatter/control-byte evidence and echo/fanout calls. Reproduce with `python3 scripts/analyze_chat_delivery.py`; check with `python3 scripts/test_chat_delivery.py`. Field/global lifetime, actual formatted output and runtime delivery remain open.
|
| 86 |
|
| 87 |
[Radio/chat expansion states and keys](CHAT_EXPANSION.md) adds RE-269–271: marker and length branches, all twelve comparison keys, health labels and momo side-effect writes. Reproduce with `python3 scripts/analyze_chat_expansion.py`; check with `python3 scripts/test_chat_expansion.py`. Full composed parser output, buffer bounds and the remaining substitution bodies stay open.
|
|
|
|
|
|
|
|
|
| 38 |
python3 scripts/test_chat_paths.py
|
| 39 |
python3 scripts/test_chat_delivery.py
|
| 40 |
python3 scripts/test_chat_expansion.py
|
| 41 |
+
python3 scripts/test_chat_items_time.py
|
| 42 |
python3 scripts/storage_policy.py check --require-setup
|
| 43 |
```
|
| 44 |
|
| 45 |
+
The pipeline runs `fetch_analysis_references.py`, `analyze_qvm.py`, `analyze_native.py`, `analyze_vm_abi.py`, `analyze_native_dispatch.py`, `analyze_vm_extensions.py`, `analyze_native_metadata.py`, `analyze_native_coverage.py`, `analyze_auth_drop.py`, `analyze_deferred_auth.py`, `analyze_engine_control.py`, `analyze_frame_timing.py`, `analyze_event_queues.py`, `analyze_auth_announcements.py`, `analyze_game_cvars.py`, `analyze_cvar_changes.py`, `analyze_auth_queries.py`, `analyze_selector_bounds.py`, `analyze_chat_paths.py`, `analyze_chat_delivery.py`, `analyze_chat_expansion.py`, and `analyze_chat_items_time.py` in that order. Python 3.9+ and **LLVM objdump** with x86 ELF/COFF/Mach-O support are required. This pass used Apple LLVM 17.0.0 on macOS arm64; it disassembled x86 images without running them. Upstream references are downloaded only if absent, checked against the committed SHA-256 manifest, and cached under `.cache/research/upstream/`. A clone also needs `git lfs pull` to obtain the derived artifacts/extracted members.
|
| 46 |
|
| 47 |
Generated JSON/JSONL and compressed evidence are regenerated in place; keep prose notes in Markdown. Rerunning the four analysis stages reproduced every generated report and gzip artifact byte-for-byte on the same toolchain. gzip timestamps and stored filenames are fixed. Different objdump versions can produce different disassembly text, so tool version and uncompressed evidence hashes are recorded. Reports are UTF-8 normal Git text, with individual files below the storage policy's 2 MiB limit.
|
| 48 |
|
|
|
|
| 86 |
[Chat policy, formats and delivery calls](CHAT_DELIVERY.md) adds RE-265–267: 512 bounded policy paths, mode/formatter/control-byte evidence and echo/fanout calls. Reproduce with `python3 scripts/analyze_chat_delivery.py`; check with `python3 scripts/test_chat_delivery.py`. Field/global lifetime, actual formatted output and runtime delivery remain open.
|
| 87 |
|
| 88 |
[Radio/chat expansion states and keys](CHAT_EXPANSION.md) adds RE-269–271: marker and length branches, all twelve comparison keys, health labels and momo side-effect writes. Reproduce with `python3 scripts/analyze_chat_expansion.py`; check with `python3 scripts/test_chat_expansion.py`. Full composed parser output, buffer bounds and the remaining substitution bodies stay open.
|
| 89 |
+
|
| 90 |
+
[Chat ammunition, item names and countdowns](CHAT_ITEMS_TIME.md) adds RE-273–275: packed byte extraction, thirty sampled name words/game-item branches and timer/cvar/gate evidence. Reproduce with `python3 scripts/analyze_chat_items_time.py`; check with `python3 scripts/test_chat_items_time.py`. Complete item tables/producers and timing/output/runtime contracts remain open.
|
docs/research/chat-items-time.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
scripts/analyze_binaries.py
CHANGED
|
@@ -14,7 +14,7 @@ def main():
|
|
| 14 |
'analyze_deferred_auth.py', 'analyze_engine_control.py', 'analyze_frame_timing.py',
|
| 15 |
'analyze_event_queues.py', 'analyze_auth_announcements.py', 'analyze_game_cvars.py',
|
| 16 |
'analyze_cvar_changes.py', 'analyze_auth_queries.py', 'analyze_selector_bounds.py',
|
| 17 |
-
'analyze_chat_paths.py', 'analyze_chat_delivery.py', 'analyze_chat_expansion.py'):
|
| 18 |
subprocess.run([sys.executable, str(Path('scripts')/script)], check=True)
|
| 19 |
|
| 20 |
|
|
|
|
| 14 |
'analyze_deferred_auth.py', 'analyze_engine_control.py', 'analyze_frame_timing.py',
|
| 15 |
'analyze_event_queues.py', 'analyze_auth_announcements.py', 'analyze_game_cvars.py',
|
| 16 |
'analyze_cvar_changes.py', 'analyze_auth_queries.py', 'analyze_selector_bounds.py',
|
| 17 |
+
'analyze_chat_paths.py', 'analyze_chat_delivery.py', 'analyze_chat_expansion.py', 'analyze_chat_items_time.py'):
|
| 18 |
subprocess.run([sys.executable, str(Path('scripts')/script)], check=True)
|
| 19 |
|
| 20 |
|
scripts/analyze_chat_items_time.py
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Recover packed chat ammo/item arguments and bounded timer substitutions."""
|
| 3 |
+
import json
|
| 4 |
+
import struct
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
from analyze_deferred_auth import partition_function, trace_slice
|
| 7 |
+
from analyze_game_cvars import vm_string
|
| 8 |
+
from analyze_qvm import decode,write_json
|
| 9 |
+
from analyze_vm_extensions import verified
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def name_candidates(data,h,count=30):
|
| 13 |
+
"""Read an explicitly sampled domain; this does not establish table length."""
|
| 14 |
+
if not 0<=count<=256:raise ValueError('sample outside low-byte domain')
|
| 15 |
+
rows=[]
|
| 16 |
+
for index in range(count):
|
| 17 |
+
address=4020+432*index
|
| 18 |
+
if address+4>h['data_length']:raise ValueError('sample word outside initialized data')
|
| 19 |
+
if h['data_offset']+address+4>len(data):raise ValueError('truncated sampled word')
|
| 20 |
+
pointer=struct.unpack_from('<I',data,h['data_offset']+address)[0]
|
| 21 |
+
rows.append({'index':index,'word_address':address,'pointer':pointer,
|
| 22 |
+
'name':vm_string(data,h,pointer) if pointer else None})
|
| 23 |
+
return rows
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def main():
|
| 27 |
+
q=json.loads(Path('docs/research/qvm/qagame.json').read_text())
|
| 28 |
+
data=verified(Path(q['source']['member']['output']).read_bytes(),q['sha256']);h,ins=decode(data)
|
| 29 |
+
c=json.loads(Path('docs/research/game-cvars.json').read_text())
|
| 30 |
+
if c['sha256']!=q['sha256']:raise ValueError('cvar provenance changed')
|
| 31 |
+
timelimit=next(r for r in c['registry']['rows'] if r['mirror_address']+12==34088124)
|
| 32 |
+
if timelimit['name']!='timelimit':raise ValueError('timer cvar identity changed')
|
| 33 |
+
rows=name_candidates(data,h)
|
| 34 |
+
entity,record=0x10000,0x20000
|
| 35 |
+
ammo=[]
|
| 36 |
+
for key,start in [('clips',185415),('bullets',185502)]:
|
| 37 |
+
for packed in [0,0x01020304,0xfffe001d,-1]:
|
| 38 |
+
for state,override in [(0,0),(3,0),(4,0),(0,1)]:
|
| 39 |
+
t=trace_slice(ins,start,186431,{entity+520:record,record+144:2,
|
| 40 |
+
record+312+4*2:packed,record+4:state,record+4324:override},{1144:entity})
|
| 41 |
+
ammo.append({'key':key,'selected_word_index':2,'packed_word':packed,'raw_state':state,
|
| 42 |
+
'override':override,'trace':t})
|
| 43 |
+
weapons=[]
|
| 44 |
+
for row in rows:
|
| 45 |
+
index=row['index'];packed=0xaabbcc00|index
|
| 46 |
+
for state in [0,3]:
|
| 47 |
+
t=trace_slice(ins,185589,186431,{entity+520:record,record+144:1,record+316:packed,
|
| 48 |
+
record+4:state,record+4324:0,row['word_address']:row['pointer']},{1144:entity})
|
| 49 |
+
weapons.append({'low_byte':index,'packed_word':packed,'raw_state':state,'trace':t})
|
| 50 |
+
game=[]
|
| 51 |
+
for minutes,now,start in [(20,1000,0),(0,0,0),(-1,0,0),(1,0,0),(1,59999,0),(1,60000,0),
|
| 52 |
+
(1,60001,0),(1,1000,2000),(35792,0,0),(1,2147483640,-2147483640)]:
|
| 53 |
+
t=trace_slice(ins,185781,186431,{34088124:minutes,34992532:now,34992544:start},{})
|
| 54 |
+
game.append({'timelimit_integer':minutes,'now':now,'start':start,'trace':t})
|
| 55 |
+
round_time=[]
|
| 56 |
+
for gt in range(0,12):
|
| 57 |
+
for delta in [-1,0,1,59999,60000,61000]:
|
| 58 |
+
t=trace_slice(ins,185850,186431,{34090788:gt,34992548:100000+delta,34992532:100000},{})
|
| 59 |
+
round_time.append({'gametype':gt,'deadline_minus_now':delta,'trace':t})
|
| 60 |
+
item=[]
|
| 61 |
+
for gt in [0,7,8,9]:
|
| 62 |
+
t=trace_slice(ins,185706,186431,{34090788:gt},{})
|
| 63 |
+
item.append({'gametype':gt,'trace':t})
|
| 64 |
+
random_tail=[]
|
| 65 |
+
for value in [0,1,2,3,-1]:
|
| 66 |
+
t=trace_slice(ins,185742,186431,{}, {68:value})
|
| 67 |
+
random_tail.append({'supplied_helper_36717_return':value,'trace':t})
|
| 68 |
+
addresses=[78068,78066,78042,78037,77972,77959,77936,78014,78009,78000,77991]
|
| 69 |
+
out={'source':q['source'],'sha256':q['sha256'],'timelimit_registry':timelimit,
|
| 70 |
+
'sampled_name_words':rows,'sample_domain':{'first':0,'last':29,'complete_table':False},
|
| 71 |
+
'ammo_traces':ammo,'weapon_traces':weapons,'gametime_traces':game,'roundtime_traces':round_time,
|
| 72 |
+
'gameitem_traces':item,'gameitem_bit_traces':random_tail,
|
| 73 |
+
'strings':{str(a):vm_string(data,h,a) for a in addresses},
|
| 74 |
+
'lookup_function':partition_function(ins,185212,data,h),
|
| 75 |
+
'limitations':'Static file-backed samples and bounded synthetic slices, not original runtime/full '
|
| 76 |
+
'QVM execution. CALLs have zero placeholder returns; helper-36717 bit-tail inputs are supplied, '
|
| 77 |
+
'not RNG outputs. Formatter calls are arguments, not text. The name-word sample does not establish '
|
| 78 |
+
'table extent, record layout or legal weapon IDs. Packed-field producers, timer global writers, '
|
| 79 |
+
'cvar update/wrap behavior, item ownership and platform parity remain open.'}
|
| 80 |
+
write_json(Path('docs/research/chat-items-time.json'),out)
|
| 81 |
+
print(f'Recovered {len(ammo)} ammo, {len(weapons)} weapon and {len(game)+len(round_time)} timer slices')
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
if __name__=='__main__':main()
|
scripts/analyze_deferred_auth.py
CHANGED
|
@@ -65,20 +65,22 @@ def trace_slice(instructions, start, stop, memory, locals_map, limit=256, byte_m
|
|
| 65 |
elif op in ('ADD', 'SUB', 'MULI'):
|
| 66 |
right, left = stack.pop(), stack.pop()
|
| 67 |
stack.append(signed32(left+right if op == 'ADD' else left-right if op == 'SUB' else left*right))
|
| 68 |
-
elif op
|
| 69 |
right, left = stack.pop(), stack.pop()
|
| 70 |
if not 0 <= right < 32:
|
| 71 |
raise ValueError('shift outside bounded evaluator domain')
|
| 72 |
-
|
|
|
|
| 73 |
elif op == 'BAND':
|
| 74 |
right, left = stack.pop(), stack.pop()
|
| 75 |
stack.append(signed32(left & right))
|
| 76 |
-
elif op
|
| 77 |
right, left = signed32(stack.pop()), signed32(stack.pop())
|
| 78 |
if right == 0 or (left == -(2**31) and right == -1):
|
| 79 |
raise ValueError('division outside bounded evaluator domain')
|
| 80 |
quotient = abs(left) // abs(right)
|
| 81 |
-
|
|
|
|
| 82 |
elif op in ('EQ', 'NE', 'LTI', 'LEI', 'GTI', 'GEI'):
|
| 83 |
right, left = signed32(stack.pop()), signed32(stack.pop())
|
| 84 |
condition = {'EQ': left == right, 'NE': left != right, 'LTI': left < right,
|
|
|
|
| 65 |
elif op in ('ADD', 'SUB', 'MULI'):
|
| 66 |
right, left = stack.pop(), stack.pop()
|
| 67 |
stack.append(signed32(left+right if op == 'ADD' else left-right if op == 'SUB' else left*right))
|
| 68 |
+
elif op in ('LSH', 'RSHI', 'RSHU'):
|
| 69 |
right, left = stack.pop(), stack.pop()
|
| 70 |
if not 0 <= right < 32:
|
| 71 |
raise ValueError('shift outside bounded evaluator domain')
|
| 72 |
+
shifted = left << right if op == 'LSH' else signed32(left) >> right if op == 'RSHI' else (left & 0xffffffff) >> right
|
| 73 |
+
stack.append(signed32(shifted))
|
| 74 |
elif op == 'BAND':
|
| 75 |
right, left = stack.pop(), stack.pop()
|
| 76 |
stack.append(signed32(left & right))
|
| 77 |
+
elif op in ('DIVI', 'MODI'):
|
| 78 |
right, left = signed32(stack.pop()), signed32(stack.pop())
|
| 79 |
if right == 0 or (left == -(2**31) and right == -1):
|
| 80 |
raise ValueError('division outside bounded evaluator domain')
|
| 81 |
quotient = abs(left) // abs(right)
|
| 82 |
+
quotient = -quotient if (left < 0) != (right < 0) else quotient
|
| 83 |
+
stack.append(quotient if op == 'DIVI' else left - quotient*right)
|
| 84 |
elif op in ('EQ', 'NE', 'LTI', 'LEI', 'GTI', 'GEI'):
|
| 85 |
right, left = signed32(stack.pop()), signed32(stack.pop())
|
| 86 |
condition = {'EQ': left == right, 'NE': left != right, 'LTI': left < right,
|
scripts/test_chat_items_time.py
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Packed-field, time-boundary and fail-closed evaluator fixtures."""
|
| 3 |
+
import json
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
import struct
|
| 6 |
+
import unittest
|
| 7 |
+
from analyze_chat_items_time import name_candidates
|
| 8 |
+
from analyze_deferred_auth import trace_slice
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
def operation(op,left,right):
|
| 12 |
+
ins=[{'index':i,'opcode':o,'operand':v} for i,(o,v) in enumerate([
|
| 13 |
+
('CONST',left),('CONST',right),(op,None)])]
|
| 14 |
+
return trace_slice(ins,0,3,{}, {},capture_stack=True)['stack'][0]
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
class ChatItemsTimeTests(unittest.TestCase):
|
| 18 |
+
@classmethod
|
| 19 |
+
def setUpClass(cls):cls.r=json.loads(Path('docs/research/chat-items-time.json').read_text())
|
| 20 |
+
|
| 21 |
+
def test_shift_sign_and_invalid_domains(self):
|
| 22 |
+
self.assertEqual(operation('RSHU',-1,24),255)
|
| 23 |
+
self.assertEqual(operation('RSHI',-1,24),-1)
|
| 24 |
+
self.assertEqual(operation('RSHI',0xff00,8),255)
|
| 25 |
+
for op in ['RSHI','RSHU']:
|
| 26 |
+
for shift in [-1,32]:
|
| 27 |
+
with self.assertRaises(ValueError):operation(op,1,shift)
|
| 28 |
+
|
| 29 |
+
def test_signed_remainder_semantics_and_invalid_division(self):
|
| 30 |
+
for a,b,expected in [(61,60,1),(-61,60,-1),(61,-60,1),(-61,-60,-1)]:
|
| 31 |
+
self.assertEqual(operation('MODI',a,b),expected)
|
| 32 |
+
self.assertEqual(operation('DIVI',a,b)*b+expected,a)
|
| 33 |
+
for op in ['MODI','DIVI']:
|
| 34 |
+
for a,b in [(1,0),(-2**31,-1)]:
|
| 35 |
+
with self.assertRaises(ValueError):operation(op,a,b)
|
| 36 |
+
|
| 37 |
+
def test_packed_ammo_masks_and_death_override(self):
|
| 38 |
+
for x in self.r['ammo_traces']:
|
| 39 |
+
calls=x['trace']['calls'];expected=(x['packed_word']>> (24 if x['key']=='clips' else 8))&255
|
| 40 |
+
self.assertEqual(calls[0]['args']['20'],expected)
|
| 41 |
+
self.assertEqual(calls[0]['args']['16'],78068)
|
| 42 |
+
self.assertEqual(len(calls),2 if x['raw_state']>=3 or x['override'] else 1)
|
| 43 |
+
if len(calls)==2:self.assertEqual(calls[1]['args']['16'],78066)
|
| 44 |
+
|
| 45 |
+
def test_weapon_low_byte_name_word_and_null(self):
|
| 46 |
+
rows={r['index']:r for r in self.r['sampled_name_words']}
|
| 47 |
+
self.assertEqual(len(rows),30);self.assertFalse(self.r['sample_domain']['complete_table'])
|
| 48 |
+
self.assertEqual(rows[18]['pointer'],0);self.assertIsNone(rows[18]['name'])
|
| 49 |
+
self.assertEqual(rows[0]['name'],rows[28]['name'])
|
| 50 |
+
self.assertEqual(rows[29]['name'],'Kick')
|
| 51 |
+
for x in self.r['weapon_traces']:
|
| 52 |
+
self.assertEqual(x['packed_word']&255,x['low_byte'])
|
| 53 |
+
calls=x['trace']['calls'];row=rows[x['low_byte']]
|
| 54 |
+
self.assertEqual(row['word_address'],4020+432*x['low_byte'])
|
| 55 |
+
self.assertEqual(calls[0]['args']['20'],row['pointer'])
|
| 56 |
+
self.assertEqual(calls[0]['args']['16'],78042)
|
| 57 |
+
if x['raw_state']==3:self.assertEqual(calls[1]['args']['16'],78037)
|
| 58 |
+
else:self.assertEqual(len(calls),1)
|
| 59 |
+
|
| 60 |
+
def test_sample_bounds_and_bad_pointers(self):
|
| 61 |
+
data=bytearray(5000);h={'data_offset':0,'data_length':5000,'literal_length':0}
|
| 62 |
+
self.assertIsNone(name_candidates(data,h,1)[0]['name'])
|
| 63 |
+
for count in [-1,257]:
|
| 64 |
+
with self.assertRaises(ValueError):name_candidates(data,h,count)
|
| 65 |
+
with self.assertRaises(ValueError):name_candidates(data,{**h,'data_length':4023},1)
|
| 66 |
+
with self.assertRaises(ValueError):name_candidates(data[:4023],h,1)
|
| 67 |
+
struct.pack_into('<I',data,4020,5001)
|
| 68 |
+
with self.assertRaises(ValueError):name_candidates(data,h,1)
|
| 69 |
+
struct.pack_into('<I',data,4020,4999);data[4999]=65
|
| 70 |
+
with self.assertRaises(ValueError):name_candidates(data,h,1)
|
| 71 |
+
|
| 72 |
+
def test_gametime_cvar_and_boundaries(self):
|
| 73 |
+
self.assertEqual(self.r['timelimit_registry']['name'],'timelimit')
|
| 74 |
+
self.assertEqual(self.r['timelimit_registry']['default'],'20')
|
| 75 |
+
expected=[(19,59),None,None,(1,0),(0,0),None,None,(1,1),None,(1,0)]
|
| 76 |
+
for x,result in zip(self.r['gametime_traces'],expected):
|
| 77 |
+
calls=x['trace']['calls'];self.assertEqual(len(calls),1)
|
| 78 |
+
a=calls[0]['args']
|
| 79 |
+
if result is None:self.assertEqual(a['16'],77959)
|
| 80 |
+
else:self.assertEqual((a['16'],a['20'],a['24']),(77972,*result))
|
| 81 |
+
self.assertEqual(self.r['strings']['77959'],'no timelimit')
|
| 82 |
+
|
| 83 |
+
def test_roundtime_positive_gate_and_game_types(self):
|
| 84 |
+
for x in self.r['roundtime_traces']:
|
| 85 |
+
calls=x['trace']['calls'];self.assertEqual(len(calls),1);a=calls[0]['args']
|
| 86 |
+
delta=x['deadline_minus_now'];active=delta>0 and x['gametype'] in [4,5,8,10]
|
| 87 |
+
if active:self.assertEqual((a['16'],a['20'],a['24']),(77972,delta//60000,(delta//1000)%60))
|
| 88 |
+
else:self.assertEqual(a['16'],77936)
|
| 89 |
+
self.assertEqual(self.r['strings']['77936'],'no roundtime')
|
| 90 |
+
|
| 91 |
+
def test_gameitem_type_and_supplied_bit_tail(self):
|
| 92 |
+
for x in self.r['gameitem_traces']:
|
| 93 |
+
calls=x['trace']['calls']
|
| 94 |
+
expected={7:78014,8:78009}.get(x['gametype'],77991)
|
| 95 |
+
self.assertEqual(calls[-1]['args']['12'],expected)
|
| 96 |
+
self.assertEqual([c['target'] for c in calls], [36185] if x['gametype'] in [7,8] else [36717,36185])
|
| 97 |
+
for x in self.r['gameitem_bit_traces']:
|
| 98 |
+
a=x['trace']['calls'][0]['args']
|
| 99 |
+
self.assertEqual(a['12'],78000 if x['supplied_helper_36717_return']&1 else 77991)
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
if __name__=='__main__':unittest.main()
|