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

qvm: hitscan rejection sampling

Browse files
docs/ASSET_PIPELINE_TODO.md CHANGED
@@ -511,3 +511,6 @@ Research progress: RE-609 [report](research/QVM_SPREAD_PARAMETERS.md); remaining
511
 
512
 
513
  Research progress: RE-610 [report](research/QVM_SPREAD_SPEED.md); remaining native/content/runtime/platform scope stays open.
 
 
 
 
511
 
512
 
513
  Research progress: RE-610 [report](research/QVM_SPREAD_SPEED.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.
docs/FINDINGS.md CHANGED
@@ -869,3 +869,6 @@ Bounded qagame paths recover bomb-plant/inactivity correction requests, direct f
869
 
870
 
871
  **RE-610 — Hitscan movement speed normalization and airborne floor:** 290493 normalizes velocity, applies an airborne ID14/23 floor below224.4, caps at340, then converts speed before its200 split. [Evidence](research/QVM_SPREAD_SPEED.md).
 
 
 
 
869
 
870
 
871
  **RE-610 — Hitscan movement speed normalization and airborne floor:** 290493 normalizes velocity, applies an airborne ID14/23 floor below224.4, caps at340, then converts speed before its200 split. [Evidence](research/QVM_SPREAD_SPEED.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).
docs/PROVISIONAL_TODO.md CHANGED
@@ -831,3 +831,6 @@ Research progress: RE-609 [report](research/QVM_SPREAD_PARAMETERS.md); remaining
831
 
832
 
833
  Research progress: RE-610 [report](research/QVM_SPREAD_SPEED.md); remaining native/content/runtime/platform scope stays open.
 
 
 
 
831
 
832
 
833
  Research progress: RE-610 [report](research/QVM_SPREAD_SPEED.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.
docs/RESEARCH_LEDGER.md CHANGED
@@ -737,3 +737,8 @@ Full-pool retry relaxes the recent-free guard; source count1022 enters diagnosti
737
  ## 2026-10-20 — RE-610 Hitscan movement speed normalization and airborne floor (separate task commit)
738
 
739
  290493 normalizes velocity, applies an airborne ID14/23 floor below224.4, caps at340, then converts speed before its200 split. 48 velocity/ID/ground fixtures reproduce source-order f32 math independently, including the discontinuity at224.4. Full suite: 941 tests pass. Report regenerates byte-identically (101488 bytes). Storage/filter, links and whitespace checks pass. No assets/Rust runtime/shared VM evaluator changed; native/reference/legal/full platform scope remains open.
 
 
 
 
 
 
737
  ## 2026-10-20 — RE-610 Hitscan movement speed normalization and airborne floor (separate task commit)
738
 
739
  290493 normalizes velocity, applies an airborne ID14/23 floor below224.4, caps at340, then converts speed before its200 split. 48 velocity/ID/ground fixtures reproduce source-order f32 math independently, including the discontinuity at224.4. Full suite: 941 tests pass. Report regenerates byte-identically (101488 bytes). Storage/filter, links and whitespace checks pass. No assets/Rust runtime/shared VM evaluator changed; native/reference/legal/full platform scope remains open.
740
+
741
+
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.
docs/REVERSE_ENGINEERING_TODO.md CHANGED
@@ -874,3 +874,6 @@ Purpose: turn the supplied installation into an evidence-backed specification of
874
 
875
 
876
  - [x] RE-610 Hitscan movement speed normalization and airborne floor ([report](research/QVM_SPREAD_SPEED.md)).
 
 
 
 
874
 
875
 
876
  - [x] RE-610 Hitscan movement speed normalization and airborne floor ([report](research/QVM_SPREAD_SPEED.md)).
877
+
878
+
879
+ - [x] RE-611 Hitscan rejection sampling and RNG consumption ([report](research/QVM_SPREAD_SAMPLING.md)).
docs/research/QVM_SPREAD_SAMPLING.md ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ # Hitscan rejection sampling and RNG consumption
2
+
3
+ RE-611. Reproduce with `python3 scripts/analyze_qvm_spread_sampling.py`; check with `python3 scripts/test_qvm_spread_sampling.py`. The [machine report](qvm-spread-sampling.json) retains source identity and bounded fixtures.
4
+
5
+ The sampler executes actual290995–291078 with actual36717 for every call. Two sums of centered low15-bit samples form x/y; both radius boundaries are strict. Source291079 loops back to290995 when rejected; the bounded harness restarts at that exact point and retains RNG state. It stops at291081 when accepted. The actual parameter/speed prefixes establish local40; the intervening290969–290994 branch only alters local44 and is outside this sampler, so this does not establish the later trigonometric offset path. Seed0 remains0, produces x=y=−1 and rejects radius²2 forever in the source; the harness reports budget exhaustion after32 attempts, never a successful return. This does not prove zero is a legal runtime seed. Tests reconstruct all candidate draws, f32 components, radius and final RNG state independently. Native trig, endpoint and contact remain pending.
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
@@ -432,3 +432,6 @@ The extension collector adds five focused tests for native tail calls, command-r
432
 
433
 
434
  [RE-610: Hitscan movement speed normalization and airborne floor](QVM_SPREAD_SPEED.md). Reproduce with `python3 scripts/analyze_qvm_spread_speed.py`; check with `python3 scripts/test_qvm_spread_speed.py`.
 
 
 
 
432
 
433
 
434
  [RE-610: Hitscan movement speed normalization and airborne floor](QVM_SPREAD_SPEED.md). Reproduce with `python3 scripts/analyze_qvm_spread_speed.py`; check with `python3 scripts/test_qvm_spread_speed.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`.
docs/research/qvm-spread-sampling.json ADDED
The diff for this file is too large to render. See raw diff
 
scripts/analyze_qvm_spread_sampling.py ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Bounded rejection sampling using actual source RNG and explicit attempt limit."""
3
+ import itertools
4
+ from pathlib import Path
5
+ from qvm_research_support import source,execute
6
+ from qvm_server_fire_support import seed
7
+ from analyze_qvm_spread_speed import speed
8
+ from analyze_qvm_spread_parameters import LOCAL
9
+ from analyze_qvm_server_shot_setup import random_word
10
+ from analyze_qvm_acceleration import PS
11
+ from analyze_qvm import write_json
12
+
13
+ def sample(ins,m,attempt_limit=32):
14
+ if not 1<=attempt_limit<=64:raise ValueError('Attempt limit outside1..64')
15
+ m,p=speed(ins,m);before=m[21044]&0xffffffff;attempts=[]
16
+ def rng(i,m,a,c):m,r=random_word(i,m);return m,c,r['result'],r
17
+ for _ in range(attempt_limit):
18
+ m,_,r=execute(ins,290995,291081,m,(0,0,0),LOCAL,{36717:rng},boundaries=[291079]);accepted=r['stop']==291081
19
+ attempts.append({'draws':[child['effect']['result'] for child in r['children']],'seed_after':m[21044]&0xffffffff,'x':m[LOCAL+28]&0xffffffff,'y':m[LOCAL+32]&0xffffffff,'radius_squared':m[LOCAL+128]&0xffffffff,'accepted':accepted,'execution':{'calls':r['calls'],'trace_length':r['trace_length'],'trace_sha256':r['trace_sha256']}})
20
+ if accepted:break
21
+ return m,{'speed':p,'initial_seed':before,'final_seed':m[21044]&0xffffffff,'attempt_limit':attempt_limit,'threshold_input':m[LOCAL+40]&0xffffffff,'attempts':attempts,'status':'accepted' if accepted else 'attempt-budget-exhausted'}
22
+
23
+ def main():
24
+ q,raw,h,ins=source();rows=[]
25
+ for rng,counter in itertools.product([0,1,0x12345678,0xffffffff],[0,10,100000]):
26
+ m=seed(raw,h,22);m[PS+216]=counter;m[21044]=rng;rows.append(sample(ins,m)[1])
27
+ write_json(Path('docs/research/qvm-spread-sampling.json'),{'source':q['source'],'sha256':q['sha256'],'probes':rows,'limits':'Actual290995–291080 with actual36717 per draw after actual parameter/speed prefixes. At most32 candidate attempts; budget exhaustion is not a QVM return. No global RNG seeding legality/native trig/endpoint/trace/damage inferred.'})
28
+ if __name__=='__main__':main()
scripts/test_qvm_spread_sampling.py ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json,unittest
2
+ from pathlib import Path
3
+ from test_qvm_ground import word,n,f
4
+ from test_qvm_server_shot_setup import random_oracle
5
+ class SpreadSamplingTests(unittest.TestCase):
6
+ @classmethod
7
+ def setUpClass(cls):cls.r=json.loads(Path('docs/research/qvm-spread-sampling.json').read_text())
8
+ def test_independent_rng_and_rejection_oracle(self):
9
+ for p in self.r['probes']:
10
+ seed=p['initial_seed']
11
+ for a in p['attempts']:
12
+ draws=[]
13
+ for _ in range(4):seed,v=random_oracle(seed);draws.append(v)
14
+ xy=[f(f(f(float(draws[j])*n(939524352))-.5)+f(f(float(draws[j+1])*n(939524352))-.5)) for j in [0,2]];radius=f(f(xy[0]*xy[0])+f(xy[1]*xy[1]));accepted=radius<1 and radius>f(n(p['threshold_input'])*n(1041865114));self.assertEqual(a['draws'],draws);self.assertEqual((a['x'],a['y'],a['radius_squared']),(*map(word,xy),word(radius)));self.assertEqual(a['accepted'],accepted);self.assertEqual(a['seed_after'],seed)
15
+ self.assertEqual(p['final_seed'],seed)
16
+ def test_only_final_candidate_can_be_accepted(self):
17
+ for p in self.r['probes']:
18
+ self.assertFalse(any(a['accepted'] for a in p['attempts'][:-1]));self.assertEqual(p['status']=='accepted',p['attempts'][-1]['accepted'])
19
+ def test_zero_seed_is_explicit_budget_boundary(self):
20
+ for p in self.r['probes']:
21
+ if p['initial_seed']==0:self.assertEqual(p['status'],'attempt-budget-exhausted');self.assertEqual(len(p['attempts']),32);self.assertEqual(p['final_seed'],0);self.assertTrue(all(n(a['radius_squared'])==2 for a in p['attempts']))
22
+ def test_actual_rng_calls_per_attempt(self):
23
+ for p in self.r['probes']:
24
+ for a in p['attempts']:self.assertEqual([c['target'] for c in a['execution']['calls']],[36717]*4)
25
+ if __name__=='__main__':unittest.main()