qvm: shot direction frame
Browse files- docs/ASSET_PIPELINE_TODO.md +3 -0
- docs/FINDINGS.md +3 -0
- docs/PROVISIONAL_TODO.md +3 -0
- docs/RESEARCH_LEDGER.md +5 -0
- docs/REVERSE_ENGINEERING_TODO.md +3 -0
- docs/research/QVM_SHOT_FRAME.md +7 -0
- docs/research/README.md +3 -0
- docs/research/qvm-shot-frame.json +0 -0
- scripts/analyze_qvm_shot_frame.py +29 -0
- scripts/test_qvm_shot_frame.py +28 -0
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()
|