nmarshall commited on
Commit
a0f9a6d
·
1 Parent(s): cd3dd4c

qvm: shot direction frame

Browse files
docs/ASSET_PIPELINE_TODO.md CHANGED
@@ -514,3 +514,6 @@ Research progress: RE-610 [report](research/QVM_SPREAD_SPEED.md); remaining nati
514
 
515
 
516
  Research progress: RE-611 [report](research/QVM_SPREAD_SAMPLING.md); remaining native/content/runtime/platform scope stays open.
 
 
 
 
514
 
515
 
516
  Research progress: RE-611 [report](research/QVM_SPREAD_SAMPLING.md); remaining native/content/runtime/platform scope stays open.
517
+
518
+
519
+ Research progress: RE-612 [report](research/QVM_SHOT_FRAME.md); remaining native/content/runtime/platform scope stays open.
docs/FINDINGS.md CHANGED
@@ -872,3 +872,6 @@ Bounded qagame paths recover bomb-plant/inactivity correction requests, direct f
872
 
873
 
874
  **RE-611 — Hitscan rejection sampling and RNG consumption:** Each spread candidate consumes four actual36717 draws; acceptance requires radius²<1 and radius²>local40×0.15. Zero RNG state never escapes its rejection loop. [Evidence](research/QVM_SPREAD_SAMPLING.md).
 
 
 
 
872
 
873
 
874
  **RE-611 — Hitscan rejection sampling and RNG consumption:** Each spread candidate consumes four actual36717 draws; acceptance requires radius²<1 and radius²>local40×0.15. Zero RNG state never escapes its rejection loop. [Evidence](research/QVM_SPREAD_SAMPLING.md).
875
+
876
+
877
+ **RE-612 — Shot body table copies and direction frame construction:** 295018 copies four initialized tables, resolves the first matching weapon slot, normalizes input direction and builds a minimum-axis perpendicular/cross frame. [Evidence](research/QVM_SHOT_FRAME.md).
docs/PROVISIONAL_TODO.md CHANGED
@@ -834,3 +834,6 @@ Research progress: RE-610 [report](research/QVM_SPREAD_SPEED.md); remaining nati
834
 
835
 
836
  Research progress: RE-611 [report](research/QVM_SPREAD_SAMPLING.md); remaining native/content/runtime/platform scope stays open.
 
 
 
 
834
 
835
 
836
  Research progress: RE-611 [report](research/QVM_SPREAD_SAMPLING.md); remaining native/content/runtime/platform scope stays open.
837
+
838
+
839
+ Research progress: RE-612 [report](research/QVM_SHOT_FRAME.md); remaining native/content/runtime/platform scope stays open.
docs/RESEARCH_LEDGER.md CHANGED
@@ -742,3 +742,8 @@ Full-pool retry relaxes the recent-free guard; source count1022 enters diagnosti
742
  ## 2026-10-20 — RE-611 Hitscan rejection sampling and RNG consumption (separate task commit)
743
 
744
  Each spread candidate consumes four actual36717 draws; acceptance requires radius²<1 and radius²>local40×0.15. Zero RNG state never escapes its rejection loop. Independent PRNG/f32 sampling oracles check every draw and candidate across12 seed/counter fixtures, with explicit32-attempt exhaustion. Full suite: 945 tests pass. Report regenerates byte-identically (142282 bytes). Storage/filter, links and whitespace checks pass. No assets/Rust runtime/shared VM evaluator changed; native/reference/legal/full platform scope remains open.
 
 
 
 
 
 
742
  ## 2026-10-20 — RE-611 Hitscan rejection sampling and RNG consumption (separate task commit)
743
 
744
  Each spread candidate consumes four actual36717 draws; acceptance requires radius²<1 and radius²>local40×0.15. Zero RNG state never escapes its rejection loop. Independent PRNG/f32 sampling oracles check every draw and candidate across12 seed/counter fixtures, with explicit32-attempt exhaustion. Full suite: 945 tests pass. Report regenerates byte-identically (142282 bytes). Storage/filter, links and whitespace checks pass. No assets/Rust runtime/shared VM evaluator changed; native/reference/legal/full platform scope remains open.
745
+
746
+
747
+ ## 2026-10-20 — RE-612 Shot body table copies and direction frame construction (separate task commit)
748
+
749
+ 295018 copies four initialized tables, resolves the first matching weapon slot, normalizes input direction and builds a minimum-axis perpendicular/cross frame. 28 axis/tie/oblique/quantized/zero and slot/ID fixtures match independent f32 frame, lookup and complete table-copy oracles. Full suite: 948 tests pass. Report regenerates byte-identically (257716 bytes). Storage/filter, links and whitespace checks pass. No assets/Rust runtime/shared VM evaluator changed; native/reference/legal/full platform scope remains open.
docs/REVERSE_ENGINEERING_TODO.md CHANGED
@@ -877,3 +877,6 @@ Purpose: turn the supplied installation into an evidence-backed specification of
877
 
878
 
879
  - [x] RE-611 Hitscan rejection sampling and RNG consumption ([report](research/QVM_SPREAD_SAMPLING.md)).
 
 
 
 
877
 
878
 
879
  - [x] RE-611 Hitscan rejection sampling and RNG consumption ([report](research/QVM_SPREAD_SAMPLING.md)).
880
+
881
+
882
+ - [x] RE-612 Shot body table copies and direction frame construction ([report](research/QVM_SHOT_FRAME.md)).
docs/research/QVM_SHOT_FRAME.md ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ # Shot body table copies and direction frame construction
2
+
3
+ RE-612. Reproduce with `python3 scripts/analyze_qvm_shot_frame.py`; check with `python3 scripts/test_qvm_shot_frame.py`. The [machine report](qvm-shot-frame.json) retains source identity and bounded fixtures.
4
+
5
+ Actual295019–295494 retains initialized table provenance (32856→local956,12bytes;32868→696,240;33108→432,240;33348→936,16) and nine zeroed counters. Actual31691 searches16 slots for entity weaponID. The input direction is normalized with supplied f32 sqrt. Strict smaller-absolute-component tests choose a coordinate axis, so ties retain the earliest axis. Projection uses the literal squared reciprocal of the normalized vector squared length, then normalizes the perpendicular and computes direction×perpendicular. Tests retain this operation order rather than substituting a generic orthonormal-basis utility. Zero input takes the source zero-normal branch but would divide by zero during projection; the harness stops before that operation and makes no legal-domain or recovery claim. The nonzero path stops before295495 muzzle preparation, native trig, per-shot seed perturbation, traces and damage.
6
+
7
+ No original executable/full QVM/native collision/physics/rendering executes. Native responses and legal input domains are supplied or unverified where stated; full runtime/macOS/Linux Rust/Bevy/Solari gameplay remains open.
docs/research/README.md CHANGED
@@ -435,3 +435,6 @@ The extension collector adds five focused tests for native tail calls, command-r
435
 
436
 
437
  [RE-611: Hitscan rejection sampling and RNG consumption](QVM_SPREAD_SAMPLING.md). Reproduce with `python3 scripts/analyze_qvm_spread_sampling.py`; check with `python3 scripts/test_qvm_spread_sampling.py`.
 
 
 
 
435
 
436
 
437
  [RE-611: Hitscan rejection sampling and RNG consumption](QVM_SPREAD_SAMPLING.md). Reproduce with `python3 scripts/analyze_qvm_spread_sampling.py`; check with `python3 scripts/test_qvm_spread_sampling.py`.
438
+
439
+
440
+ [RE-612: Shot body table copies and direction frame construction](QVM_SHOT_FRAME.md). Reproduce with `python3 scripts/analyze_qvm_shot_frame.py`; check with `python3 scripts/test_qvm_shot_frame.py`.
docs/research/qvm-shot-frame.json ADDED
The diff for this file is too large to render. See raw diff
 
scripts/analyze_qvm_shot_frame.py ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """295018 table copies, actual weapon lookup and direction frame prefix."""
3
+ import itertools
4
+ from pathlib import Path
5
+ from qvm_research_support import source,execute,constants
6
+ from qvm_server_fire_support import seed,ENTITY,MUZZLE
7
+ from analyze_qvm_weapon_lookup import lookup
8
+ from analyze_qvm_acceleration import PS,word
9
+ from analyze_qvm_slide_prefix import vec
10
+ from analyze_qvm import write_json
11
+ LOCAL=0x79500000
12
+ DIRECTION=LOCAL+0x2000
13
+ COPIES=[(956,32856,12),(696,32868,240),(432,33108,240),(936,33348,16)]
14
+
15
+ def frame(ins,m):
16
+ m=dict(m);m[LOCAL+1108]=ENTITY;m[LOCAL+1112]=MUZZLE;m[LOCAL+1116]=DIRECTION;m[LOCAL+1120]=91;direction=vec(m,DIRECTION);slots=[m[PS+312+4*j]&0xffffffff for j in range(16)];m,_,prefix=execute(ins,295019,295311,m,(0,0,0),LOCAL,{31691:lookup});normal=vec(m,LOCAL+36);copies=[{'local':o,'source':a,'size':size,'words':[m[LOCAL+o+j]&0xffffffff for j in range(0,size,4)]} for o,a,size in COPIES];projection=None
17
+ if any(w&0x7fffffff for w in normal):m,_,projection=execute(ins,295311,295495,m,(0,0,0),LOCAL)
18
+ return m,{'input':direction,'weapon_id':m[ENTITY+192],'slots':slots,'lookup_slot':m[LOCAL+976],'packed_id':m[LOCAL+428],'normalized':normal,'axis':m[LOCAL+1048],'copies':copies,'zeroed_counters':[m[LOCAL+o] for o in range(980,1016,4)],'prefix':prefix,'projection':projection,'perpendicular':vec(m,LOCAL+48) if projection else None,'cross':vec(m,LOCAL+416) if projection else None,'status':'muzzle-pending' if projection else 'zero-direction-division-boundary'}
19
+
20
+ def main():
21
+ q,raw,h,ins=source();rows=[];table=constants(raw,h,range(32856,33364,4))
22
+ for ident,slot,direction in itertools.product([4,22],[0,15],[(1,0,0),(0,1,0),(0,0,1),(1,1,1),(.6,-.25,.8),(-4096,2048,1),(0,0,0)]):
23
+ m=seed(raw,h,ident);m.update(table)
24
+ for j in range(16):m[PS+312+4*j]=2
25
+ m[PS+312+4*slot]=ident|(1<<16)
26
+ for j,v in enumerate(direction):m[DIRECTION+4*j]=word(v)
27
+ rows.append(frame(ins,m)[1])
28
+ write_json(Path('docs/research/qvm-shot-frame.json'),{'source':q['source'],'sha256':q['sha256'],'table_words':{str(a):v for a,v in table.items()},'probes':rows,'limits':'Actual295019–295494 with actual31691 and supplied sqrt. Zero input stops before projection division by zero; no repair or legal-domain claim. No295018 muzzle/spread/native trace/damage.'})
29
+ if __name__=='__main__':main()
scripts/test_qvm_shot_frame.py ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json,math,unittest
2
+ from pathlib import Path
3
+ from test_qvm_ground import word,n,f
4
+
5
+ def dot(a,b):return f(f(f(a[0]*b[0])+f(a[1]*b[1]))+f(a[2]*b[2]))
6
+ def normalized(v):
7
+ length=f(math.sqrt(dot(v,v)));return [f(x*f(1/length)) for x in v] if length else v
8
+
9
+ def expected(p):
10
+ v=normalized(list(map(n,p['input'])));axis=min(range(3),key=lambda j:abs(v[j]))
11
+ if not any(v):return list(map(word,v)),axis,None,None
12
+ unit=[float(j==axis) for j in range(3)];scale=f(1/dot(v,v));scale=f(scale*scale);amount=f(dot(v,unit)*scale);side=normalized([f(u-f(amount*x)) for u,x in zip(unit,v)]);cross=[f(f(v[(j+1)%3]*side[(j+2)%3])-f(v[(j+2)%3]*side[(j+1)%3])) for j in range(3)];return list(map(word,v)),axis,list(map(word,side)),list(map(word,cross))
13
+ class ShotFrameTests(unittest.TestCase):
14
+ @classmethod
15
+ def setUpClass(cls):cls.r=json.loads(Path('docs/research/qvm-shot-frame.json').read_text())
16
+ def test_full_f32_frame_oracle(self):
17
+ self.assertEqual(len(self.r['probes']),28)
18
+ for p in self.r['probes']:self.assertEqual((p['normalized'],p['axis'],p['perpendicular'],p['cross']),expected(p))
19
+ def test_first16_lookup_and_table_copies(self):
20
+ for p in self.r['probes']:
21
+ self.assertEqual(p['lookup_slot'],next(j for j,v in enumerate(p['slots']) if v&255==p['weapon_id']));self.assertEqual(p['packed_id'],p['weapon_id']);self.assertEqual(p['zeroed_counters'],[0]*9)
22
+ for c in p['copies']:self.assertEqual(c['words'],[self.r['table_words'][str(c['source']+j)] for j in range(0,c['size'],4)])
23
+ def test_zero_boundary_has_no_projection_sqrt(self):
24
+ for p in self.r['probes']:
25
+ self.assertEqual([c['target'] for c in p['prefix']['calls']],[31691,-107])
26
+ if not any(p['input']):self.assertIsNone(p['projection']);self.assertEqual(p['status'],'zero-direction-division-boundary')
27
+ else:self.assertEqual([c['target'] for c in p['projection']['calls']],[-107]);self.assertEqual(p['status'],'muzzle-pending')
28
+ if __name__=='__main__':unittest.main()