nmarshall commited on
Commit
187c949
·
1 Parent(s): ab95057

qvm: animation tables

Browse files
docs/ASSET_PIPELINE_TODO.md CHANGED
@@ -266,3 +266,5 @@ Research progress: [QVM content/ground producers](research/QVM_GROUND.md) comple
266
  Research progress: [QVM posture bounds and transitions](research/QVM_POSTURE.md) completes bounded RE-441–443. RE-440/444 retain native collision/callee effects/resource and semantic producers/full state composition/runtime/platform work; broad game/rendering milestones remain open.
267
 
268
  Research progress: [QVM animation helpers and contacts](research/QVM_ANIMATION_CONTACTS.md) completes bounded RE-445–447. RE-444/448 retain native collision/resource/semantic producers, live animation tables/contact consumers/full state/runtime/platform work; broad game/rendering milestones remain open.
 
 
 
266
  Research progress: [QVM posture bounds and transitions](research/QVM_POSTURE.md) completes bounded RE-441–443. RE-440/444 retain native collision/callee effects/resource and semantic producers/full state composition/runtime/platform work; broad game/rendering milestones remain open.
267
 
268
  Research progress: [QVM animation helpers and contacts](research/QVM_ANIMATION_CONTACTS.md) completes bounded RE-445–447. RE-444/448 retain native collision/resource/semantic producers, live animation tables/contact consumers/full state/runtime/platform work; broad game/rendering milestones remain open.
269
+
270
+ Research progress: [QVM animation assets and tables](research/QVM_ANIMATION_TABLES.md) completes bounded RE-449–451: verified variants/symbols/model selectors, loader records/partial EOF and source-derived timed-helper bridges. RE-448/452 retain native preprocessing/live tables/playback/contact/full-state/runtime/platform work; broad game/rendering milestones remain open.
docs/FINDINGS.md CHANGED
@@ -528,3 +528,9 @@ Bounded qagame paths recover bomb-plant/inactivity correction requests, direct f
528
  **F-194 — Forced and timed animation ordering:**39525 clears the leg timer before its guarded child unless raw animation equals the request;39552 clears torso timer, calls its child, then assigns a supplied table word at base35018540+selector*2560+request*32+28 even when the child rejects. Ten posture bridges apply selected child effects and caller timer200 stores. Actual tables/playback/full state remain open. [ANIM-002](research/QVM_ANIMATION_CONTACTS.md).
529
 
530
  **F-195 — Contact registration:**39253 ignores entity1022 and count==32, scans for duplicates, then appends and increments the frame count. Bounded fixtures preserve every untouched slot and accept1023/synthetic-1 on the append path; real contact generation, invalid counts and list consumers remain open. [CONTACT-001](research/QVM_ANIMATION_CONTACTS.md).
 
 
 
 
 
 
 
528
  **F-194 — Forced and timed animation ordering:**39525 clears the leg timer before its guarded child unless raw animation equals the request;39552 clears torso timer, calls its child, then assigns a supplied table word at base35018540+selector*2560+request*32+28 even when the child rejects. Ten posture bridges apply selected child effects and caller timer200 stores. Actual tables/playback/full state remain open. [ANIM-002](research/QVM_ANIMATION_CONTACTS.md).
529
 
530
  **F-195 — Contact registration:**39253 ignores entity1022 and count==32, scans for duplicates, then appends and increments the frame count. Bounded fixtures preserve every untouched slot and accept1023/synthetic-1 on the append path; real contact generation, invalid counts and list consumers remain open. [CONTACT-001](research/QVM_ANIMATION_CONTACTS.md).
531
+
532
+ **F-196 — Shipped animation symbol/model mapping:** two separately retained byte-identical76-row configs include one verified header; QVM registration maps Athena to selector0 and Orion to1. Definitions name IDs25/36/37 as LEGS_SWIM/LEGS_STANDCR/LEGS_CRSTAND. IDs1/77/78/79 are absent from both configs; runtime initialization remains open. [TABLE-001](research/QVM_ANIMATION_TABLES.md).
533
+
534
+ **F-197 — Animation loader records and partial success:**20297 writes32-byte records, integer1000/FPS intervals and duration interval×(count+1), leaves+24 untouched, and consumes three other values without storing. Supplied EOF retains partial writes and closes with return1; failed open returns0. Native preprocessing and malformed-input behavior remain unverified. [TABLE-002](research/QVM_ANIMATION_TABLES.md).
535
+
536
+ **F-198 — Source-derived duration consumers:**152 bridges transfer verified asset-derived duration words at actual model table selectors into39552 and its evaluated child. Stand/crouch transition rows derive480/360, but the earlier posture caller separately writes200; complete playback/state/runtime remains open. [TABLE-003](research/QVM_ANIMATION_TABLES.md).
docs/PROVISIONAL_TODO.md CHANGED
@@ -578,3 +578,5 @@ Research progress: [QVM content/ground producers](research/QVM_GROUND.md) comple
578
  Research progress: [QVM posture bounds and transitions](research/QVM_POSTURE.md) completes bounded RE-441–443. RE-440/444 retain native collision/callee effects/resource and semantic producers/full state composition/runtime/platform work; broad game/rendering milestones remain open.
579
 
580
  Research progress: [QVM animation helpers and contacts](research/QVM_ANIMATION_CONTACTS.md) completes bounded RE-445–447. RE-444/448 retain native collision/resource/semantic producers, live animation tables/contact consumers/full state/runtime/platform work; broad game/rendering milestones remain open.
 
 
 
578
  Research progress: [QVM posture bounds and transitions](research/QVM_POSTURE.md) completes bounded RE-441–443. RE-440/444 retain native collision/callee effects/resource and semantic producers/full state composition/runtime/platform work; broad game/rendering milestones remain open.
579
 
580
  Research progress: [QVM animation helpers and contacts](research/QVM_ANIMATION_CONTACTS.md) completes bounded RE-445–447. RE-444/448 retain native collision/resource/semantic producers, live animation tables/contact consumers/full state/runtime/platform work; broad game/rendering milestones remain open.
581
+
582
+ Research progress: [QVM animation assets and tables](research/QVM_ANIMATION_TABLES.md) completes bounded RE-449–451: verified variants/symbols/model selectors, loader records/partial EOF and source-derived timed-helper bridges. RE-448/452 retain native preprocessing/live tables/playback/contact/full-state/runtime/platform work; broad game/rendering milestones remain open.
docs/RESEARCH_LEDGER.md CHANGED
@@ -331,3 +331,7 @@ Completed bounded RE-441–443 with `analyze_qvm_posture.py`: verified shipped q
331
  ## 2026-10-15 — Game-QVM animation helper effects and contact registration
332
 
333
  Completed bounded RE-445–447 with `analyze_qvm_animation_contacts.py`: verified seven shipped qagame helper partitions,576 animation probes,30 contact-list probes and ten posture request bridges. ANIM-001–002/CONTACT-001/F-193–195 recover direct leg/torso type/timer guards, raw equality asymmetry, bit128 toggle/OR and continuation masks; forced timer-reset-before-child ordering, timed torso table address arithmetic/final store; and contact sentinel1022, count==32 capacity, signed duplicate scan and append/count effects. Known animation children39338/39383 execute as bounded synthetic instruction slices with separate local frames; selected posture bridges evaluate39525 requests and caller timer200 stores. Actual animation table words are supplied, with selectors/duration/request legality and live table producers unresolved. Contact fixtures cover counts0–32 only; invalid counts, native contact generation and entity/list-consumer effects remain open. No complete QVM/original executable/native collision/rendering executes. The shared evaluator adds signed32 BXOR without changing existing opcode paths. Eight independent tests check all policies/mutations/nested calls, equality/toggle/reset asymmetries, timed address/unconditional store, contacts/canaries, selected posture compositions, signed/high-bit XOR/local isolation, provenance/complete partitions and scope. All503 tests pass. Direct acceleration/ground/posture reports regenerate byte-identically; the new report reproduces twice byte-identically (606122 bytes). Storage/filter setup, documentation links and whitespace checks pass. RE-444 remains open; RE-448 tracks live animation tables/durations/selector-request domains, semantic/type/timer producers, contact reset/consumers/entity/invalid-count/alias behavior, full movement state composition and gameplay/macOS/Linux/Rust/Bevy/Solari compatibility; packet/entity/player/snapshot/rendering milestones remain open.
 
 
 
 
 
331
  ## 2026-10-15 — Game-QVM animation helper effects and contact registration
332
 
333
  Completed bounded RE-445–447 with `analyze_qvm_animation_contacts.py`: verified seven shipped qagame helper partitions,576 animation probes,30 contact-list probes and ten posture request bridges. ANIM-001–002/CONTACT-001/F-193–195 recover direct leg/torso type/timer guards, raw equality asymmetry, bit128 toggle/OR and continuation masks; forced timer-reset-before-child ordering, timed torso table address arithmetic/final store; and contact sentinel1022, count==32 capacity, signed duplicate scan and append/count effects. Known animation children39338/39383 execute as bounded synthetic instruction slices with separate local frames; selected posture bridges evaluate39525 requests and caller timer200 stores. Actual animation table words are supplied, with selectors/duration/request legality and live table producers unresolved. Contact fixtures cover counts0–32 only; invalid counts, native contact generation and entity/list-consumer effects remain open. No complete QVM/original executable/native collision/rendering executes. The shared evaluator adds signed32 BXOR without changing existing opcode paths. Eight independent tests check all policies/mutations/nested calls, equality/toggle/reset asymmetries, timed address/unconditional store, contacts/canaries, selected posture compositions, signed/high-bit XOR/local isolation, provenance/complete partitions and scope. All503 tests pass. Direct acceleration/ground/posture reports regenerate byte-identically; the new report reproduces twice byte-identically (606122 bytes). Storage/filter setup, documentation links and whitespace checks pass. RE-444 remains open; RE-448 tracks live animation tables/durations/selector-request domains, semantic/type/timer producers, contact reset/consumers/entity/invalid-count/alias behavior, full movement state composition and gameplay/macOS/Linux/Rust/Bevy/Solari compatibility; packet/entity/player/snapshot/rendering milestones remain open.
334
+
335
+ ## 2026-10-15 — Verified player animation assets, loader records and timed consumers
336
+
337
+ Completed bounded RE-449–451 with `analyze_qvm_animation_tables.py`: verifies all observed player animation config/header payloads against extraction manifests, retains both identical4131-byte config variants, recovers literal registration of Athena selector0/Orion selector1 and source-level animation IDs, and evaluates loader20297 using supplied token integer values from a strict observed-format parser. TABLE-001–003/F-196–198 identify76 distinct rows per model and missing1/77/78/79; recover32-byte record stores, integer1000/FPS intervals, duration interval×(count+1), ignored upper/lower/hit tokens and untouched+24; and connect152 source-derived duration words at actual registered table selectors into the previously recovered timed-animation helper/child. Eleven prefix/failed-open probes recover partial writes, close/return1 on opened-source EOF and return0 on failed open; two synthetic FPS probes cover negative truncation and above1000 yielding zero interval. Zero FPS is an evaluator rejection, not a runtime crash claim. Native preprocessing/token ABI, original executable/full QVM/playback/rendering do not execute. Strict parser is not a general preprocessor; header ambiguity, missing-slot initialization/live table writers, selectors/request legality and malformed/runtime/platform behavior remain open. Eight independent tests verify source identities/variant retention, literal registration, symbols/missing indices, every loaded record and token request/canary, partial/EOF/failed-open effects, signed/zero-FPS boundary,152 timed-consumer bridges, parser failure domains/partitions/scope. All511 tests pass. Direct animation-contact/posture reports regenerate byte-identically; new report reproduces twice byte-identically (622268 bytes). Storage/filter setup, documentation links and whitespace checks pass. No original assets or existing analyzer/evaluator code changed. RE-448 remains open; RE-452 tracks native preprocessing/search/includes/error semantics, missing-index/+24 initialization/live writers, selectors, playback/interpolation/loop/flip/model/skeleton/Bevy conversion and full state/gameplay/macOS/Linux/Solari validation. Resource producers, contact consumers and packet/entity/player/snapshot/rendering milestones remain open.
docs/REVERSE_ENGINEERING_TODO.md CHANGED
@@ -542,3 +542,8 @@ Purpose: turn the supplied installation into an evidence-backed specification of
542
  - [x] RE-446 Recover forced leg reset/child ordering, timed torso address/store behavior and selected posture-to-animation child compositions ([ANIM-002](research/QVM_ANIMATION_CONTACTS.md)).
543
  - [x] RE-447 Recover bounded contact-list sentinel/capacity/dedup/append policy and untouched slot canaries ([CONTACT-001](research/QVM_ANIMATION_CONTACTS.md)).
544
  - [ ] RE-448 Recover animation table allocation/loading/durations/selector-request legality and type/timer/flag producers; contact count resets/list consumers/entity effects/invalid-count and alias domains; compose complete posture/ground/list/animation/movement state and validate runtime/macOS/Linux before Rust/Bevy/Solari claims. Continue packet/entity/player/snapshot/rendering work.
 
 
 
 
 
 
542
  - [x] RE-446 Recover forced leg reset/child ordering, timed torso address/store behavior and selected posture-to-animation child compositions ([ANIM-002](research/QVM_ANIMATION_CONTACTS.md)).
543
  - [x] RE-447 Recover bounded contact-list sentinel/capacity/dedup/append policy and untouched slot canaries ([CONTACT-001](research/QVM_ANIMATION_CONTACTS.md)).
544
  - [ ] RE-448 Recover animation table allocation/loading/durations/selector-request legality and type/timer/flag producers; contact count resets/list consumers/entity effects/invalid-count and alias domains; compose complete posture/ground/list/animation/movement state and validate runtime/macOS/Linux before Rust/Bevy/Solari claims. Continue packet/entity/player/snapshot/rendering work.
545
+
546
+ - [x] RE-449 Verify all observed player-animation asset variants/header symbols and recover literal model table registration/selector mapping ([TABLE-001](research/QVM_ANIMATION_TABLES.md)).
547
+ - [x] RE-450 Recover bounded helper20297 token/store layout, integer timing/duration formula and EOF/partial-write/open-failure behavior with verified-row and synthetic-prefix probes ([TABLE-002](research/QVM_ANIMATION_TABLES.md)).
548
+ - [x] RE-451 Bridge all152 asset-derived duration words at registered model selectors into the recovered timed-animation helper/child ([TABLE-003](research/QVM_ANIMATION_TABLES.md)).
549
+ - [ ] RE-452 Recover native token/preprocessor ABI/search/include/define/error behavior, missing-index/+24 initialization/later table writers, selector/request legality and frame/interpolation/loop/flip playback consumers; map model/skeleton/Bevy conversion and compose complete posture/ground/contact/animation state. Validate gameplay/macOS/Linux/Solari; resource production and packet/entity/player/snapshot/rendering work remain open.
docs/research/QVM_ANIMATION_CONTACTS.md CHANGED
@@ -41,3 +41,5 @@ The prior ground helper records39253(trace entity), so these instructions explai
41
  Eight tests independently verify all576 animation policies/results and nested requests, direct-setter asymmetry/masked continuation, reset-before-type ordering/raw request bits, timed address arithmetic/unconditional duration store,30 contact list/canary outcomes, ten posture request/timer compositions, XOR/local-frame behavior, source hash/complete partitions and scope. Direct posture/ground/scale reports are regenerated unchanged; the new report reproduces twice byte-identically. Full-suite and storage/link/whitespace outcomes are recorded in the ledger.
42
 
43
  RE-444 remains open for native collision/resource/semantic/full-state work. RE-448 tracks animation table allocation/loading and real duration/selector/request domains, type/timer/flag producers, frame contact count reset/list consumers/entity effects, invalid-count/alias domains, complete posture/ground/list/animation/movement composition and runtime/macOS/Linux validation before Rust/Bevy/Solari compatibility claims. Packet/entity/player/snapshot/rendering milestones remain open.
 
 
 
41
  Eight tests independently verify all576 animation policies/results and nested requests, direct-setter asymmetry/masked continuation, reset-before-type ordering/raw request bits, timed address arithmetic/unconditional duration store,30 contact list/canary outcomes, ten posture request/timer compositions, XOR/local-frame behavior, source hash/complete partitions and scope. Direct posture/ground/scale reports are regenerated unchanged; the new report reproduces twice byte-identically. Full-suite and storage/link/whitespace outcomes are recorded in the ledger.
42
 
43
  RE-444 remains open for native collision/resource/semantic/full-state work. RE-448 tracks animation table allocation/loading and real duration/selector/request domains, type/timer/flag producers, frame contact count reset/list consumers/entity effects, invalid-count/alias domains, complete posture/ground/list/animation/movement composition and runtime/macOS/Linux validation before Rust/Bevy/Solari compatibility claims. Packet/entity/player/snapshot/rendering milestones remain open.
44
+
45
+ Follow-up: [verified animation-table recovery](QVM_ANIMATION_TABLES.md) derives actual registration selectors, source symbol names and152 duration words from both preserved player config variants, then evaluates timed-helper consumers (RE-449–451). Native preprocessing/live table writers/playback and contact consumers remain open under RE-448/452.
docs/research/QVM_ANIMATION_TABLES.md ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Game-QVM animation assets, table loader and duration consumers
2
+
3
+ RE-449–451 / F-196–198. [qvm-animation-tables.json](qvm-animation-tables.json) connects verified extracted player configs and their header to shipped qagame registration/helper20297 and the previously recovered timed-animation consumer. Reproduce with `python3 scripts/analyze_qvm_animation_tables.py`; check with `python3 scripts/test_qvm_animation_tables.py`.
4
+
5
+ The report preserves both asset variants and provenance,76 rows per config, complete loader/registration instruction partitions, eleven prefix/load-failure probes, two signed-FPS probes and152 asset-duration bridges. The ordinary Git report contains derived analysis; original assets remain in their existing extracted locations under the repository's asset filters. No assets are modified or newly copied by this analysis.
6
+
7
+ This is bounded synthetic instruction evaluation. The native token preprocessor and its ABI, original executable, complete QVM, playback and rendering do not execute. A strict parser recognizes only the observed numeric definitions, one include and nine-column rows; it supplies token integer values and handle/token success/EOF to the recorded callback boundaries. It is not a general C preprocessor. Actual filesystem search precedence, live table allocation/changes and invalid input/runtime behavior remain open.
8
+
9
+ ## TABLE-001 — Verified variants, symbols and model registration (RE-449)
10
+
11
+ All extraction manifests are searched for `models/animdef.h` and player `animation.cfg` members, retaining archive/member identity and checking each extracted payload against its recorded SHA-256. The observed header has one variant. The two configs are:
12
+
13
+ | Asset path | Archive/member | Model selector from QVM registration |
14
+ | --- | --- | --- |
15
+ | models/players/athena/animation.cfg | zUrT43_005.pk3 /648 | 0 |
16
+ | models/players/orion/animation.cfg | zUrT43_007.pk3 /3 | 1 |
17
+
18
+ Their4131-byte payloads share SHA-256 `ef9a02aac32a97b4712c7bcd3ef4996ebf8b3b283746d67d7223dd23131cfa50`, but remain separate source records. The include header comes from zUrT43_008.pk3 member1; its SHA-256 and member metadata are retained separately. Multiple header variants currently cause an explicit ambiguity error rather than silently choosing one. Additional configuration paths without known registration require further analysis.
19
+
20
+ Helper20280 records helper20297 with the literal Orion path at VM40563 and destination35021100, then the literal Athena path at40527 and destination35018540. It discards each returned value. Since the timed consumer uses base35018540 plus selector*2560, these calls establish Orion selector1 and Athena selector0 for these registered tables. The header declares `MAX_PLAYER_ANIMATIONS=80`;80 records ×32 bytes matches the2560 stride. Dynamic selector producers and legal ranges still need recovery.
21
+
22
+ The observed definitions give the following source-level names for earlier numeric findings:
23
+
24
+ | ID | Header name | Connection |
25
+ | --- | --- | --- |
26
+ | 25 | LEGS_SWIM | Posture helper's masked-animation low-height branch |
27
+ | 26/28/30/32 | LEGS_JUMP / LEGS_JUMPRT / LEGS_BACKJUMP / LEGS_BACKJUMPRT | Earlier ground kick animation requests |
28
+ | 36/37 | LEGS_STANDCR / LEGS_CRSTAND | Posture entry/exit requests |
29
+ | 45 | TORSO_USE | Example timed torso row |
30
+
31
+ This strengthens animation ID names using shipped definitions; it does not by itself establish every surrounding state/flag meaning or gameplay reachability. Both configs contain76 distinct indexed rows, omitting1 (`BOTH_WALLJUMP`) and77/78/79 (dual torso definitions). They need not be laid out in numeric ID order; symbol expansion determines the write index.
32
+
33
+ ## TABLE-002 — Record layout, integer timing and partial-load behavior (RE-450)
34
+
35
+ Helper20297 requests encoded target-579(path). A supplied zero handle returns0 without a close request. With a nonzero handle, it repeatedly requests target-581(handle, local token buffer). The integer word at buffer+8 supplies the animation index, then eight data tokens. The report supplies these values from the verified config/header parsing; native preprocessing/conversion semantics remain unverified.
36
+
37
+ For each record at destination + index*32, observed writes are:
38
+
39
+ | Byte offset | Derived value |
40
+ | --- | --- |
41
+ | +0 | Start frame (second token) |
42
+ | +4 | Frame count (third token) |
43
+ | +8 | Loop value (fifth token) |
44
+ | +12 /+16 | Signed integer division1000/FPS (fourth token), truncating toward zero in the evaluator |
45
+ | +20 | Flip value (sixth token) |
46
+ | +24 | No write in this loader |
47
+ | +28 | word[+16] + word[+12] × word[+4] |
48
+
49
+ The remaining upper/lower/hit tokens are consumed but not stored by this helper. Their names come from the asset column comment; other consumers may use related data. No animation-index bounds check appears before the address arithmetic in this helper. The offline config parser restricts observed rows to0–79 and rejects duplicates; this validation is an analysis constraint, not a claimed guard in the original game.
50
+
51
+ The duration expression is an initial interval plus one interval per counted frame, hence interval×(count+1), with QVM integer operations. For these small source values there is no overflow. FPS30 yields33, rather than a fractional33.333… interval. Synthetic FPS-30 yields-33 and FPS2000 yields0. Zero FPS reaches the evaluator's explicit division rejection; original error/crash behavior and preprocessing/legal-input guarantees remain unverified.
52
+
53
+ Every token request can end the loop. With an opened source, EOF triggers target-580(handle), then return1—even if EOF occurs partway through a row. Earlier stores are retained; no rollback or record completion check occurs here. Eleven prefix probes cover zero through nine tokens plus failed open. In a complete nine-token row the three ignored values still must be read before +28 is assigned. Absent indices and +24 retain test sentinels, which prove lack of writes and are not a claim about runtime contents. A guard record beyond the80 analyzed slots remains unchanged.
54
+
55
+ ## TABLE-003 — Asset-derived duration to timed helper (RE-451)
56
+
57
+ For every loaded row in each config, one bridge passes the actual derived +28 word into helper39552 at that asset's registered model selector and animation index. The supplied address exactly matches destination + index*32 +28. All152 bridges evaluate the previously recovered torso setter child and final timer assignment, replacing arbitrary duration examples with source-derived table words for these requests.
58
+
59
+ Examples shared by these identical configs:
60
+
61
+ | ID/name | Start/count/FPS | Interval | Derived duration |
62
+ | --- | --- | --- | --- |
63
+ | 25 LEGS_SWIM | 440 /30 /30 | 33 | 1023 |
64
+ | 36 LEGS_STANDCR | 600 /11 /25 | 40 | 480 |
65
+ | 37 LEGS_CRSTAND | 611 /8 /25 | 40 | 360 |
66
+ | 45 TORSO_USE | 384 /37 /45 | 22 | 836 |
67
+
68
+ Passing all rows into the timed helper is a synthetic address/value compatibility check, not evidence that every row is requested as a torso animation during gameplay. It does not claim the posture caller uses table durations: that caller explicitly writes timer200 after helper39525, as already recovered. Playback interpolation, frame/loop/flip consumers and rendering remain unresolved.
69
+
70
+ ## Validation and next tasks
71
+
72
+ Eight independent tests check QVM/asset hashes and retained variant identity, literal registration destinations/selector mapping, definitions/row coverage/missing indices, every loaded record/token request and canary, partial writes/EOF/failed-open behavior, signed/zero-FPS model boundaries, all152 duration/child-helper bridges, strict parser failures, instruction partitions and explicit scope. Direct animation-contact and posture dependencies regenerate unchanged; the new report reproduces twice byte-identically. Full-suite/storage/link/whitespace outcomes are recorded in the ledger.
73
+
74
+ RE-448 remains open for live allocation/producers, contact consumers and full state. RE-452 tracks native token/preprocessor ABI/search/include/define/error behavior, missing-index/+24 initialization and any later table writers, selector/request legality, animation frame/interpolation/loop/flip playback consumers, model/skeleton mapping and Bevy conversion, full posture/ground/contact/animation composition and gameplay/macOS/Linux/Solari validation. Resource production and broader packet/entity/player/snapshot/rendering milestones remain open.
docs/research/QVM_POSTURE.md CHANGED
@@ -43,3 +43,5 @@ Eight tests check all420 transitions against an independent branch/arithmetic or
43
  RE-440 remains open for native collision and full state composition. RE-444 tracks actual overlap-trace ABI/results, helper39525 effects, resource producers/reset/ranges, animation/type/flag/height meanings, complete frame/PS/content/ground/friction composition and gameplay/macOS/Linux validation before Rust/Bevy/Solari compatibility claims. Packet/entity/player/snapshot and rendering milestones remain open.
44
 
45
  Follow-up: [animation-helper recovery](QVM_ANIMATION_CONTACTS.md) evaluates39525 and its guarded child at ten selected posture request boundaries, then applies the caller timer200 store (RE-446). Other native/collision/resource/full-state effects remain open under RE-444/448.
 
 
 
43
  RE-440 remains open for native collision and full state composition. RE-444 tracks actual overlap-trace ABI/results, helper39525 effects, resource producers/reset/ranges, animation/type/flag/height meanings, complete frame/PS/content/ground/friction composition and gameplay/macOS/Linux validation before Rust/Bevy/Solari compatibility claims. Packet/entity/player/snapshot and rendering milestones remain open.
44
 
45
  Follow-up: [animation-helper recovery](QVM_ANIMATION_CONTACTS.md) evaluates39525 and its guarded child at ten selected posture request boundaries, then applies the caller timer200 store (RE-446). Other native/collision/resource/full-state effects remain open under RE-444/448.
46
+
47
+ Follow-up: [shipped animation definitions](QVM_ANIMATION_TABLES.md) name25 as LEGS_SWIM and36/37 as LEGS_STANDCR/LEGS_CRSTAND. The loader derives480/360 for those transition rows; this does not change the posture caller's explicit timer200 stores. Full state/playback remains open.
docs/research/README.md CHANGED
@@ -179,3 +179,5 @@ The extension collector adds five focused tests for native tail calls, command-r
179
  [QVM posture bounds, transitions and consumer bridges](QVM_POSTURE.md) adds RE-441–443. Reproduce with `python3 scripts/analyze_qvm_posture.py`; check with `python3 scripts/test_qvm_posture.py`. Trace/sqrt results are supplied and only selected state transfers into consumers; native collision/callees/full runtime remain open.
180
 
181
  [QVM animation helpers and contact registration](QVM_ANIMATION_CONTACTS.md) adds RE-445–447. Reproduce with `python3 scripts/analyze_qvm_animation_contacts.py`; check with `python3 scripts/test_qvm_animation_contacts.py`. Animation children are evaluated, table duration words supplied, and selected posture requests composed; full collision/runtime remains open.
 
 
 
179
  [QVM posture bounds, transitions and consumer bridges](QVM_POSTURE.md) adds RE-441–443. Reproduce with `python3 scripts/analyze_qvm_posture.py`; check with `python3 scripts/test_qvm_posture.py`. Trace/sqrt results are supplied and only selected state transfers into consumers; native collision/callees/full runtime remain open.
180
 
181
  [QVM animation helpers and contact registration](QVM_ANIMATION_CONTACTS.md) adds RE-445–447. Reproduce with `python3 scripts/analyze_qvm_animation_contacts.py`; check with `python3 scripts/test_qvm_animation_contacts.py`. Animation children are evaluated, table duration words supplied, and selected posture requests composed; full collision/runtime remains open.
182
+
183
+ [QVM animation assets, table loader and duration consumers](QVM_ANIMATION_TABLES.md) adds RE-449–451. Reproduce with `python3 scripts/analyze_qvm_animation_tables.py`; check with `python3 scripts/test_qvm_animation_tables.py`. Verified assets supply token integer values; complete native preprocessing, live playback and runtime remain open.
docs/research/qvm-animation-tables.json ADDED
The diff for this file is too large to render. See raw diff
 
scripts/analyze_qvm_animation_tables.py ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Verified player animation assets, bounded loader effects and duration consumers."""
3
+ import json
4
+ from pathlib import Path
5
+ import re
6
+ from analyze_bsp_references import trace_fields
7
+ from analyze_deferred_auth import trace_slice
8
+ from analyze_qvm import decode,write_json
9
+ from analyze_qvm_animation_contacts import animation_probe
10
+ from analyze_vm_extensions import verified
11
+
12
+ LOCAL=0x49000000
13
+ TABLE=35018540
14
+ TOKEN_CALLS=[20324,20343,20368,20395,20438,20465,20492,20507,20522]
15
+ BOUNDARIES=[20305]+[i+2 for i in TOKEN_CALLS]+[20584]
16
+ SENTINEL=0x13579
17
+
18
+
19
+ def sources():
20
+ found=[]
21
+ for path in sorted(Path('docs/extraction/archives').glob('*.jsonl')):
22
+ rows=[json.loads(line) for line in path.read_text().splitlines()];archive=rows[0]
23
+ for member in rows[1:]:
24
+ name=member['path']
25
+ if not (name=='models/animdef.h' or re.fullmatch(r'models/players/[^/]+/animation\.cfg',name)):continue
26
+ payload=verified(Path(member['output']).read_bytes(),member['sha256']);found.append(({'archive':archive['archive'],'archive_sha256':archive['archive_sha256'],'member':member},payload))
27
+ return found
28
+
29
+
30
+ def uncomment(text):return re.sub(r'//[^\n]*|/\*.*?\*/','',text,flags=re.S)
31
+
32
+
33
+ def definitions(payload):
34
+ result={}
35
+ for line in uncomment(payload.decode('ascii')).splitlines():
36
+ if not line.strip():continue
37
+ match=re.fullmatch(r'\s*#define\s+(\w+)\s+(-?\d+)\s*',line)
38
+ if not match:raise ValueError('unsupported animation definition')
39
+ if match[1] in result:raise ValueError('duplicate animation definition')
40
+ result[match[1]]=int(match[2])
41
+ return result
42
+
43
+
44
+ def config_rows(payload,defs):
45
+ rows=[];includes=[];seen=set()
46
+ for line in uncomment(payload.decode('ascii')).splitlines():
47
+ if not line.strip():continue
48
+ if line.lstrip().startswith('#'):
49
+ match=re.fullmatch(r'\s*#include\s+"([^"]+)"\s*',line)
50
+ if not match:raise ValueError('unsupported animation directive')
51
+ includes.append(match[1]);continue
52
+ fields=line.split()
53
+ if len(fields)!=9 or fields[0] not in defs:raise ValueError('unsupported animation row')
54
+ index=defs[fields[0]]
55
+ if not 0<=index<defs['MAX_PLAYER_ANIMATIONS'] or index in seen:raise ValueError('invalid or duplicate animation index')
56
+ values=[int(v) for v in fields[1:]];seen.add(index);rows.append({'name':fields[0],'index':index,'values':values})
57
+ if includes!=['models/animdef.h']:raise ValueError('unsupported animation include chain')
58
+ return includes,rows
59
+
60
+
61
+ def loader_probe(ins,tokens,handle=77,table_address=TABLE):
62
+ m={LOCAL+1120:0x51000000,LOCAL+1124:table_address,LOCAL+24:0}
63
+ for i in range(81*8):m[table_address+i*4]=SENTINEL
64
+ pc=20298;calls=[];trace=[];token_index=0;returned=None
65
+ while returned is None:
66
+ results={20303:handle,**{i:1 if token_index<len(tokens) else 0 for i in TOKEN_CALLS}}
67
+ t=trace_slice(ins,20298,20586,m,{},resume_at=pc,stop_targets=[b for b in BOUNDARIES if b!=pc],local_base=LOCAL,call_results=results,halt_on_leave=True,capture_stack=True,limit=512);m=t['memory'];trace+=t['trace'];calls+=t['calls'];pc=t['stop']
68
+ for c in t['calls']:
69
+ if c['instruction'] in TOKEN_CALLS and token_index<len(tokens):m[LOCAL+24]=tokens[token_index];token_index+=1
70
+ if t['trace'] and ins[t['trace'][-1]]['opcode']=='LEAVE':returned=t['stack'][-1]
71
+ changed={str(index):[m[table_address+index*32+o] for o in range(0,32,4)] for index in range(80) if any(m[table_address+index*32+o]!=SENTINEL for o in range(0,32,4))}
72
+ return {'table_address':table_address,'input_tokens':tokens,'supplied_handle_word':handle,'consumed_tokens':token_index,'return_word':returned,'changed_records':changed,'guard_record':[m[table_address+80*32+o] for o in range(0,32,4)],'calls':calls,**trace_fields({'trace':trace},False)}
73
+
74
+
75
+ def main():
76
+ q=json.loads(Path('docs/research/qvm/qagame.json').read_text());raw=verified(Path(q['source']['member']['output']).read_bytes(),q['sha256']);_,ins=decode(raw);assets=sources();headers=[(s,p) for s,p in assets if s['member']['path']=='models/animdef.h']
77
+ if len(headers)!=1:raise ValueError('ambiguous animation header variants')
78
+ header,defs_payload=headers[0];defs=definitions(defs_payload);configs=[];bridges=[]
79
+ registration=trace_slice(ins,20281,20295,{},{});registrations=[];data_offset=int.from_bytes(raw[16:20],'little')
80
+ for c in registration['calls']:
81
+ address=c['args'][8];name=raw[data_offset+address:].split(b'\0',1)[0].decode('ascii');table=c['args'][12];registrations.append({'literal_address':address,'path':name,'table_address':table,'selector':(table-TABLE)//2560})
82
+ for source,payload in assets:
83
+ if source['member']['path']=='models/animdef.h':continue
84
+ registration_row=next(r for r in registrations if r['path']==source['member']['path']);includes,rows=config_rows(payload,defs);tokens=[v for row in rows for v in [row['index'],*row['values']]];loaded=loader_probe(ins,tokens,table_address=registration_row['table_address']);configs.append({'source':source,'registration':registration_row,'includes':includes,'rows':rows,'missing_indices':sorted(set(range(80))-{r['index'] for r in rows}),'loader_probe':loaded})
85
+ for row in rows:
86
+ index=row['index'];duration=loaded['changed_records'][str(index)][7]
87
+ for selector in [registration_row['selector']]:bridges.append({'asset_sha256':source['member']['sha256'],'archive':source['archive'],'animation_name':row['name'],'animation_index':index,'loaded_record_words':loaded['changed_records'][str(index)],'probe':animation_probe(ins,39552,request=index,selector=selector,duration=duration)})
88
+ fixture=[36,10,7,30,2,1,1,1,0];prefixes=[loader_probe(ins,fixture[:n]) for n in range(10)];prefixes.append(loader_probe(ins,fixture,handle=0));signed=[loader_probe(ins,[36,10,7,fps,2,1,1,1,0]) for fps in [-30,2000]]
89
+ out={'source':q['source'],'sha256':q['sha256'],'registration_instructions':ins[20280:20297],'loader_instructions':ins[20297:20587],'registration_calls':registration['calls'],'registrations':registrations,'header_source':header,'definitions':defs,'configs':configs,'prefix_probes':prefixes,'signed_fps_probes':signed,'duration_bridges':bridges,'limitations':'Verified extracted asset bytes and bounded synthetic QVM slices only; no original executable/full QVM/native token preprocessor/animation playback/rendering executes. Token integer values are supplied from a strict parser for the observed numeric defines/include/rows, not a general C preprocessor or native trap ABI. Header variants must be unambiguous. Handle/token success/EOF are supplied, and zero FPS reaches evaluator division rejection; real malformed asset/error behavior remains unverified. Table record+24 and absent indices retain synthetic sentinels, not claimed runtime values. Loaded duration words are transferred into prior timed-animation probes at the asset registration selectors; live allocation/other state/full runtime/macOS/Linux/Rust/Bevy/Solari remain open.'};write_json(Path('docs/research/qvm-animation-tables.json'),out);print('Recovered verified animation asset indices, bounded loader records and duration bridges')
90
+
91
+
92
+ if __name__=='__main__':main()
scripts/test_qvm_animation_tables.py ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Independent verified-asset, loader-record and duration-consumer checks."""
3
+ import hashlib
4
+ import json
5
+ from pathlib import Path
6
+ import unittest
7
+ from analyze_qvm import decode
8
+ from analyze_qvm_animation_tables import config_rows,definitions,loader_probe,SENTINEL
9
+ from test_qvm_animation_contacts import animation_oracle
10
+
11
+
12
+ def trunc_div(a,b):return (1 if (a<0)==(b<0) else -1)*(abs(a)//abs(b))
13
+
14
+
15
+ def row_oracle(row):
16
+ start,count,fps,loop,flip,upper,lower,hit=row['values'];interval=trunc_div(1000,fps);return [start,count,loop,interval,interval,flip,SENTINEL,interval*(count+1)]
17
+
18
+
19
+ class QvmAnimationTableTests(unittest.TestCase):
20
+ @classmethod
21
+ def setUpClass(cls):
22
+ cls.r=json.loads(Path('docs/research/qvm-animation-tables.json').read_text());q=json.loads(Path('docs/research/qvm/qagame.json').read_text());cls.raw=Path(q['source']['member']['output']).read_bytes();_,cls.ins=decode(cls.raw)
23
+
24
+ def test_all_source_identities_and_separate_identical_variants(self):
25
+ self.assertEqual(hashlib.sha256(self.raw).hexdigest(),self.r['sha256']);self.assertEqual(len(self.r['configs']),2)
26
+ for source in [self.r['header_source']]+[c['source'] for c in self.r['configs']]:self.assertEqual(hashlib.sha256(Path(source['member']['output']).read_bytes()).hexdigest(),source['member']['sha256'])
27
+ a,b=self.r['configs'];self.assertNotEqual(a['source']['archive'],b['source']['archive']);self.assertEqual(a['source']['member']['sha256'],b['source']['member']['sha256']);self.assertEqual(a['rows'],b['rows'])
28
+
29
+ def test_registration_literal_paths_table_stride_and_loader_requests(self):
30
+ self.assertEqual([(r['path'],r['table_address'],r['selector']) for r in self.r['registrations']],[('models/players/orion/animation.cfg',35021100,1),('models/players/athena/animation.cfg',35018540,0)])
31
+ offset=int.from_bytes(self.raw[16:20],'little')
32
+ for c,r in zip(self.r['registration_calls'],self.r['registrations']):self.assertEqual(c['target'],20297);self.assertEqual(c['args'],{'8':r['literal_address'],'12':r['table_address']});self.assertEqual(self.raw[offset+r['literal_address']:].split(b'\0',1)[0].decode(),r['path'])
33
+
34
+ def test_symbol_ids_missing_rows_and_known_posture_meanings(self):
35
+ d=self.r['definitions'];self.assertEqual(d['MAX_PLAYER_ANIMATIONS'],80)
36
+ for name,index in [('LEGS_SWIM',25),('LEGS_STANDCR',36),('LEGS_CRSTAND',37),('TORSO_USE',45)]:self.assertEqual(d[name],index)
37
+ for c in self.r['configs']:
38
+ self.assertEqual(len(c['rows']),76);self.assertEqual(c['missing_indices'],[1,77,78,79]);self.assertEqual(c['includes'],['models/animdef.h']);self.assertEqual(len({r['index'] for r in c['rows']}),76)
39
+ for row in c['rows']:self.assertEqual(d[row['name']],row['index'])
40
+
41
+ def test_every_loaded_record_and_token_request_count(self):
42
+ for c in self.r['configs']:
43
+ p=c['loader_probe'];self.assertEqual(p['table_address'],c['registration']['table_address']);self.assertEqual(p['return_word'],1);self.assertEqual(p['consumed_tokens'],76*9);self.assertEqual(len(p['changed_records']),76);self.assertEqual(p['guard_record'],[SENTINEL]*8)
44
+ for row in c['rows']:self.assertEqual(p['changed_records'][str(row['index'])],row_oracle(row))
45
+ self.assertEqual([c['target'] for c in p['calls']],[ -579]+[-581]*(76*9+1)+[-580]);self.assertEqual(p['calls'][0]['args'],{'8':0x51000000});self.assertEqual(p['calls'][-1]['args'],{'8':77})
46
+ self.assertTrue(all(call['args']=={'8':77,'12':0x49000010} for call in p['calls'][1:-1]))
47
+
48
+ def test_missing_tokens_preserve_partial_writes_and_success_close(self):
49
+ self.assertEqual(len(self.r['prefix_probes']),11)
50
+ for n,p in enumerate(self.r['prefix_probes'][:10]):
51
+ expected=[SENTINEL]*8
52
+ if n>=2:expected[0]=10
53
+ if n>=3:expected[1]=7
54
+ if n>=4:expected[3]=expected[4]=33
55
+ if n>=5:expected[2]=2
56
+ if n>=6:expected[5]=1
57
+ if n>=9:expected[7]=264
58
+ self.assertEqual(p['consumed_tokens'],n);self.assertEqual(p['changed_records'],{'36':expected} if n>=2 else {});self.assertEqual(p['return_word'],1);self.assertEqual(p['calls'][-1]['target'],-580)
59
+ p=self.r['prefix_probes'][-1];self.assertEqual(p['return_word'],0);self.assertEqual(p['changed_records'],{});self.assertEqual([c['target'] for c in p['calls']],[-579])
60
+
61
+ def test_signed_fps_truncation_and_zero_division_model_boundary(self):
62
+ for p in self.r['signed_fps_probes']:
63
+ fps=p['input_tokens'][3];interval=trunc_div(1000,fps);self.assertEqual(p['changed_records']['36'],[10,7,2,interval,interval,1,SENTINEL,interval*8])
64
+ with self.assertRaisesRegex(ValueError,'division outside bounded evaluator domain'):loader_probe(self.ins,[36,10,7,0,2,1,1,1,0])
65
+
66
+ def test_asset_duration_reaches_actual_selector_timed_helper(self):
67
+ self.assertEqual(len(self.r['duration_bridges']),152)
68
+ for b in self.r['duration_bridges']:
69
+ c=next(c for c in self.r['configs'] if c['source']['archive']==b['archive']);row=next(r for r in c['rows'] if r['index']==b['animation_index']);p=b['probe'];self.assertEqual(b['loaded_record_words'],row_oracle(row));self.assertEqual(p['request_word'],row['index']);self.assertEqual(p['table_selector_word'],c['registration']['selector']);self.assertEqual(p['supplied_table_address'],c['loader_probe']['table_address']+row['index']*32+28);self.assertEqual(p['supplied_duration_word'],row_oracle(row)[7]);self.assertEqual([p['result_legs_word'],p['result_torso_word'],p['result_leg_timer_word'],p['result_torso_timer_word']],list(animation_oracle(p)[:4]))
70
+
71
+ def test_strict_asset_parser_partitions_and_explicit_scope(self):
72
+ with self.assertRaisesRegex(ValueError,'unsupported animation definition'):definitions(b'#define A B\n')
73
+ with self.assertRaisesRegex(ValueError,'unsupported animation include chain'):config_rows(b'A 1 2 3 4 5 6 7 8\n',{'A':0,'MAX_PLAYER_ANIMATIONS':80})
74
+ with self.assertRaisesRegex(ValueError,'duplicate animation index'):config_rows(b'#include "models/animdef.h"\nA 1 2 3 4 5 6 7 8\nA 1 2 3 4 5 6 7 8',{'A':0,'MAX_PLAYER_ANIMATIONS':80})
75
+ self.assertEqual(self.r['loader_instructions'][0]['index'],20297);self.assertEqual(self.r['loader_instructions'][-1]['index'],20586)
76
+ for text in ['no original executable/full QVM/native token preprocessor','not a general C preprocessor','absent indices retain synthetic sentinels','real malformed asset/error behavior remains unverified']:self.assertIn(text,self.r['limitations'])
77
+
78
+
79
+ if __name__=='__main__':unittest.main()