nmarshall commited on
Commit
31716f1
·
1 Parent(s): e0fd654

gameplay: integrate queried fallback spawn selection

Browse files
crates/urt-gameplay/src/bin/urt-camera-world.rs CHANGED
@@ -305,6 +305,65 @@ fn run() -> Result<String, String> {
305
  }
306
  }
307
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
308
  "spawn-camera" => {
309
  if w.len() < 9 || w[1] > 1 || w[2] > 1024 {
310
  return Err("invalid camera header".into());
@@ -536,3 +595,28 @@ fn spawn_records(w: &[u32], p: &mut usize, n: usize) -> Result<Vec<SpawnEntity>,
536
  }
537
  Ok(c)
538
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
305
  }
306
  }
307
 
308
+ "queried-spawn" => {
309
+ if w.len() < 5 || w[4] > 1024 {
310
+ return Err("invalid spawn header".into());
311
+ }
312
+ let mut state = w[0];
313
+ let avoid = w[1..4].try_into().unwrap();
314
+ let n = w[4] as usize;
315
+ let mut p = 5;
316
+ let world = spawn_records(&w, &mut p, n)?;
317
+ let bound_words = w.get(p..p + 6).ok_or("missing query bounds")?;
318
+ p += 6;
319
+ let bounds = std::array::from_fn(|h| {
320
+ std::array::from_fn(|j| f32::from_bits(bound_words[h * 3 + j]))
321
+ });
322
+ let profile = *w.get(p).ok_or("missing query profile")?;
323
+ p += 1;
324
+ let mut queries = vec![];
325
+ let mut client_words = vec![0; 1024];
326
+ client_words[901] = u32::MAX;
327
+ let mut math = math_calls(&w, &mut p)?;
328
+ if p != w.len() {
329
+ return Err("trailing spawn words".into());
330
+ }
331
+ let r = fallback_spawn_queried(
332
+ &world,
333
+ avoid,
334
+ &mut state,
335
+ &mut math,
336
+ SpawnQueryWorld {
337
+ bounds,
338
+ client_words: &client_words,
339
+ },
340
+ |j, b| {
341
+ let indices = query_results(j, profile);
342
+ queries.push(query_json(j, b, &indices));
343
+ Ok(indices)
344
+ },
345
+ );
346
+ math.finish().map_err(|e| format!("{e:?}"))?;
347
+ let payload = match r {
348
+ Ok(r) => Ok(format!(
349
+ "{{\"result\":{},\"rng_state\":{state},\"fatal\":false,\"candidates\":{},\"distances\":{},\"origin\":{},\"angles\":{}}}",
350
+ r.index,
351
+ array(&r.candidates.iter().map(|j| *j as u32).collect::<Vec<_>>()),
352
+ array(&r.distances),
353
+ array(&r.view.origin),
354
+ array(&r.view.angles)
355
+ )),
356
+ Err(Error::NoSpawn) => Ok(format!(
357
+ "{{\"result\":-1,\"rng_state\":{state},\"fatal\":true,\"candidates\":[],\"distances\":[],\"origin\":[305419896,305419896,305419896],\"angles\":[305419896,305419896,305419896]}}"
358
+ )),
359
+ Err(e) => Err(format!("{e:?}")),
360
+ }?;
361
+ Ok(format!(
362
+ "{{\"queries\":[{}],\"selection\":{payload}}}",
363
+ queries.join(",")
364
+ ))
365
+ }
366
+
367
  "spawn-camera" => {
368
  if w.len() < 9 || w[1] > 1 || w[2] > 1024 {
369
  return Err("invalid camera header".into());
 
595
  }
596
  Ok(c)
597
  }
598
+
599
+ fn query_results(j: usize, profile: u32) -> Vec<usize> {
600
+ match profile {
601
+ 0 => vec![],
602
+ 1 => vec![900],
603
+ 2 => vec![900, 901],
604
+ _ => {
605
+ if j % 2 == 1 {
606
+ vec![901]
607
+ } else {
608
+ vec![900, 900]
609
+ }
610
+ }
611
+ }
612
+ }
613
+ fn query_json(j: usize, b: urt_gameplay::native_policy::SpawnBox, indices: &[usize]) -> String {
614
+ format!(
615
+ "{{\"index\":{j},\"mins\":{},\"maxs\":{},\"capacity\":{},\"indices\":{},\"blocked\":{}}}",
616
+ array(&b.mins.map(f32::to_bits)),
617
+ array(&b.maxs.map(f32::to_bits)),
618
+ b.capacity,
619
+ array(&indices.iter().map(|v| *v as u32).collect::<Vec<_>>()),
620
+ u32::from(indices.contains(&901))
621
+ )
622
+ }
crates/urt-gameplay/src/camera_world.rs CHANGED
@@ -599,6 +599,20 @@ pub fn fallback_spawn<M: AngleMath>(
599
  avoid: [u32; 3],
600
  state: &mut u32,
601
  math: &mut M,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
602
  ) -> Result<SpawnSelection, Error> {
603
  bounds(world.len(), world.len(), None)?;
604
  let mut ranked = vec![];
@@ -611,7 +625,7 @@ pub fn fallback_spawn<M: AngleMath>(
611
  continue;
612
  }
613
  first.get_or_insert(j);
614
- if e.blocked {
615
  continue;
616
  }
617
  let mut d = [0f32; 3];
@@ -834,3 +848,107 @@ mod composed_spawn_tests {
834
  assert_eq!(state, 9);
835
  }
836
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
599
  avoid: [u32; 3],
600
  state: &mut u32,
601
  math: &mut M,
602
+ ) -> Result<SpawnSelection, Error> {
603
+ fallback_spawn_with_check(world, avoid, state, math, |_, e| Ok(e.blocked))
604
+ }
605
+ /// Run overlap checks only at the original class-filter boundary, interleaved
606
+ /// with each accepted candidate's distance evaluation.
607
+ pub fn fallback_spawn_with_check<
608
+ M: AngleMath,
609
+ F: FnMut(usize, &SpawnEntity) -> Result<bool, Error>,
610
+ >(
611
+ world: &[SpawnEntity],
612
+ avoid: [u32; 3],
613
+ state: &mut u32,
614
+ math: &mut M,
615
+ mut blocked: F,
616
  ) -> Result<SpawnSelection, Error> {
617
  bounds(world.len(), world.len(), None)?;
618
  let mut ranked = vec![];
 
625
  continue;
626
  }
627
  first.get_or_insert(j);
628
+ if blocked(j, e)? {
629
  continue;
630
  }
631
  let mut d = [0f32; 3];
 
848
  assert_eq!(state, 9);
849
  }
850
  }
851
+
852
+ #[derive(Clone, Copy)]
853
+ pub struct SpawnQueryWorld<'a> {
854
+ pub bounds: [[f32; 3]; 2],
855
+ pub client_words: &'a [u32],
856
+ }
857
+ fn overlap_check<F: FnMut(usize, crate::native_policy::SpawnBox) -> Result<Vec<usize>, Error>>(
858
+ j: usize,
859
+ e: &SpawnEntity,
860
+ scene: SpawnQueryWorld<'_>,
861
+ query: &mut F,
862
+ ) -> Result<bool, Error> {
863
+ let bounds = crate::native_policy::spawn_box(e.origin.map(f32::from_bits), scene.bounds)
864
+ .map_err(|_| Error::FloatDomain)?;
865
+ let results = query(j, bounds)?;
866
+ crate::native_policy::spawn_overlap(
867
+ &results,
868
+ i32::try_from(results.len()).map_err(|_| Error::InvalidCount)?,
869
+ scene.client_words,
870
+ )
871
+ .map_err(|_| Error::InvalidCount)
872
+ }
873
+ pub fn fallback_spawn_queried<
874
+ M: AngleMath,
875
+ F: FnMut(usize, crate::native_policy::SpawnBox) -> Result<Vec<usize>, Error>,
876
+ >(
877
+ world: &[SpawnEntity],
878
+ avoid: [u32; 3],
879
+ state: &mut u32,
880
+ math: &mut M,
881
+ scene: SpawnQueryWorld<'_>,
882
+ mut query: F,
883
+ ) -> Result<SpawnSelection, Error> {
884
+ fallback_spawn_with_check(world, avoid, state, math, |j, e| {
885
+ overlap_check(j, e, scene, &mut query)
886
+ })
887
+ }
888
+ #[cfg(test)]
889
+ mod query_spawn_tests {
890
+ use super::*;
891
+ #[test]
892
+ fn filtered_entities_do_not_query() {
893
+ let e = SpawnEntity {
894
+ class: HashEntity {
895
+ in_use: 0,
896
+ class_present: true,
897
+ class_hash: (-103258081i32) as u32,
898
+ },
899
+ blocked: false,
900
+ origin: [0; 3],
901
+ angles: [0; 3],
902
+ };
903
+ let mut state = 7;
904
+ let mut queried = false;
905
+ assert_eq!(
906
+ fallback_spawn_queried(
907
+ &[e],
908
+ [0; 3],
909
+ &mut state,
910
+ &mut HostMath,
911
+ SpawnQueryWorld {
912
+ bounds: [[0.; 3]; 2],
913
+ client_words: &[]
914
+ },
915
+ |_, _| {
916
+ queried = true;
917
+ Ok(vec![])
918
+ }
919
+ ),
920
+ Err(Error::NoSpawn)
921
+ );
922
+ assert!(!queried);
923
+ assert_eq!(state, 7);
924
+ }
925
+ #[test]
926
+ fn query_error_preserves_rng() {
927
+ let e = SpawnEntity {
928
+ class: HashEntity {
929
+ in_use: 1,
930
+ class_present: true,
931
+ class_hash: (-103258081i32) as u32,
932
+ },
933
+ blocked: false,
934
+ origin: [0; 3],
935
+ angles: [0; 3],
936
+ };
937
+ let mut state = 7;
938
+ assert_eq!(
939
+ fallback_spawn_queried(
940
+ &[e],
941
+ [0; 3],
942
+ &mut state,
943
+ &mut HostMath,
944
+ SpawnQueryWorld {
945
+ bounds: [[0.; 3]; 2],
946
+ client_words: &[]
947
+ },
948
+ |_, _| Ok(vec![0])
949
+ ),
950
+ Err(Error::InvalidCount)
951
+ );
952
+ assert_eq!(state, 7);
953
+ }
954
+ }
docs/FINDINGS.md CHANGED
@@ -1652,3 +1652,6 @@ RE-813–822 replaces the scoreboard decimal assumption with actual37914 executi
1652
 
1653
 
1654
  **RE-884:** 64 actual168114/167638 compositions replace supplied telefrag booleans with original query/filter execution. Native -33 spatial result lists are supplied, including empty/nonclient/blocked/mixed/duplicate results. Class filtering determines query order; accepted candidates then consume modeled sqrt before RNG, preserving64-cap and all-blocked/no-spawn behavior. Native broadphase, math and runtime ownership remain open. [Evidence](research/QVM_QUERIED_SPAWN.md).
 
 
 
 
1652
 
1653
 
1654
  **RE-884:** 64 actual168114/167638 compositions replace supplied telefrag booleans with original query/filter execution. Native -33 spatial result lists are supplied, including empty/nonclient/blocked/mixed/duplicate results. Class filtering determines query order; accepted candidates then consume modeled sqrt before RNG, preserving64-cap and all-blocked/no-spawn behavior. Native broadphase, math and runtime ownership remain open. [Evidence](research/QVM_QUERIED_SPAWN.md).
1655
+
1656
+
1657
+ **RE-885:** Rust matches64 original168114/167638 compositions and their ordered box query transcripts. The typed spatial-query callback runs only after class filtering and before each candidate distance. Existing boolean-based callers retain behavior. Query/domain errors preserve RNG; native broadphase/math and external callback effects remain open. [Evidence](research/RUST_QUERIED_SPAWN.md).
docs/PROVISIONAL_TODO.md CHANGED
@@ -1683,3 +1683,6 @@ Next batch RE-884: integrate collision queries and recovered callbacks; recover
1683
 
1684
 
1685
  RE-884: 64 actual168114/167638 compositions replace supplied telefrag booleans with original query/filter execution. Native -33 spatial result lists are supplied, including empty/nonclient/blocked/mixed/duplicate results. Class filtering determines query order; accepted candidates then consume modeled sqrt before RNG, preserving64-cap and all-blocked/no-spawn behavior. Native broadphase, math and runtime ownership remain open. Full runtime completion remains open. [Evidence](research/QVM_QUERIED_SPAWN.md).
 
 
 
 
1683
 
1684
 
1685
  RE-884: 64 actual168114/167638 compositions replace supplied telefrag booleans with original query/filter execution. Native -33 spatial result lists are supplied, including empty/nonclient/blocked/mixed/duplicate results. Class filtering determines query order; accepted candidates then consume modeled sqrt before RNG, preserving64-cap and all-blocked/no-spawn behavior. Native broadphase, math and runtime ownership remain open. Full runtime completion remains open. [Evidence](research/QVM_QUERIED_SPAWN.md).
1686
+
1687
+
1688
+ RE-885: Rust matches64 original168114/167638 compositions and their ordered box query transcripts. The typed spatial-query callback runs only after class filtering and before each candidate distance. Existing boolean-based callers retain behavior. Query/domain errors preserve RNG; native broadphase/math and external callback effects remain open. Full runtime completion remains open. [Evidence](research/RUST_QUERIED_SPAWN.md).
docs/RESEARCH_LEDGER.md CHANGED
@@ -2130,3 +2130,8 @@ After the tenth commit, `cargo xtask test-full --since 40af75ff` passed with the
2130
  ## 2026-10-28 — RE-884 Original fallback spawn with actual overlap queries
2131
 
2132
  64 actual168114/167638 compositions replace supplied telefrag booleans with original query/filter execution. Native -33 spatial result lists are supplied, including empty/nonclient/blocked/mixed/duplicate results. Class filtering determines query order; accepted candidates then consume modeled sqrt before RNG, preserving64-cap and all-blocked/no-spawn behavior. Native broadphase, math and runtime ownership remain open. Focused `cargo xtask test-group qvm-queried-spawn` passes (10 Python tests); selected reports regenerate twice deterministically, with storage, documentation links and whitespace guards. Full suite deferred to task ten. Original assets unchanged; source fixtures do not establish complete native/runtime parity.
 
 
 
 
 
 
2130
  ## 2026-10-28 — RE-884 Original fallback spawn with actual overlap queries
2131
 
2132
  64 actual168114/167638 compositions replace supplied telefrag booleans with original query/filter execution. Native -33 spatial result lists are supplied, including empty/nonclient/blocked/mixed/duplicate results. Class filtering determines query order; accepted candidates then consume modeled sqrt before RNG, preserving64-cap and all-blocked/no-spawn behavior. Native broadphase, math and runtime ownership remain open. Focused `cargo xtask test-group qvm-queried-spawn` passes (10 Python tests); selected reports regenerate twice deterministically, with storage, documentation links and whitespace guards. Full suite deferred to task ten. Original assets unchanged; source fixtures do not establish complete native/runtime parity.
2133
+
2134
+
2135
+ ## 2026-10-28 — RE-885 Rust queried fallback spawn policy
2136
+
2137
+ Rust matches64 original168114/167638 compositions and their ordered box query transcripts. The typed spatial-query callback runs only after class filtering and before each candidate distance. Existing boolean-based callers retain behavior. Query/domain errors preserve RNG; native broadphase/math and external callback effects remain open. Focused `cargo xtask test-group rust-queried-spawn` passes (11 Python tests); selected reports regenerate twice deterministically, with storage, documentation links and whitespace guards. Full suite deferred to task ten. Original assets unchanged; source fixtures do not establish complete native/runtime parity.
docs/REVERSE_ENGINEERING_TODO.md CHANGED
@@ -1720,3 +1720,6 @@ Purpose: turn the supplied installation into an evidence-backed specification of
1720
 
1721
 
1722
  - [x] RE-884 Original fallback spawn with actual overlap queries ([evidence](research/QVM_QUERIED_SPAWN.md)).
 
 
 
 
1720
 
1721
 
1722
  - [x] RE-884 Original fallback spawn with actual overlap queries ([evidence](research/QVM_QUERIED_SPAWN.md)).
1723
+
1724
+
1725
+ - [x] RE-885 Rust queried fallback spawn policy ([evidence](research/RUST_QUERIED_SPAWN.md)).
docs/research/README.md CHANGED
@@ -1255,3 +1255,6 @@ The extension collector adds five focused tests for native tail calls, command-r
1255
 
1256
 
1257
  [RE-884: Original fallback spawn with actual overlap queries](QVM_QUERIED_SPAWN.md).
 
 
 
 
1255
 
1256
 
1257
  [RE-884: Original fallback spawn with actual overlap queries](QVM_QUERIED_SPAWN.md).
1258
+
1259
+
1260
+ [RE-885: Rust queried fallback spawn policy](RUST_QUERIED_SPAWN.md).
docs/research/RUST_QUERIED_SPAWN.md ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ # Rust queried fallback spawn policy
2
+
3
+ RE-885. Reproduce with `python3 scripts/analyze_rust_queried_spawn.py`; focused validation: `cargo xtask test-group rust-queried-spawn`. [Machine report](rust-queried-spawn.json).
4
+
5
+ Rust matches64 original168114/167638 compositions and their ordered box query transcripts. The typed spatial-query callback runs only after class filtering and before each candidate distance. Existing boolean-based callers retain behavior. Query/domain errors preserve RNG; native broadphase/math and external callback effects remain open.
6
+
7
+ Original executable/native runtime and complete Linux/macOS Rust/Bevy/Solari acceptance remain unverified.
docs/research/rust-health-impulse.json CHANGED
@@ -2985,7 +2985,7 @@
2985
  "exit_code": 2
2986
  }
2987
  ],
2988
- "rust_unit_tests": 140,
2989
  "source_report": "docs/research/qvm-health-impulse.json",
2990
  "source_report_sha256": "625fd8064004ea6dbdf398d51d0af844d9c635ff04c0a0790886dddb6ab81e71"
2991
  }
 
2985
  "exit_code": 2
2986
  }
2987
  ],
2988
+ "rust_unit_tests": 142,
2989
  "source_report": "docs/research/qvm-health-impulse.json",
2990
  "source_report_sha256": "625fd8064004ea6dbdf398d51d0af844d9c635ff04c0a0790886dddb6ab81e71"
2991
  }
docs/research/rust-queried-spawn.json ADDED
The diff for this file is too large to render. See raw diff
 
scripts/analyze_rust_queried_spawn.py ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ import hashlib,json
3
+ from pathlib import Path
4
+ from rust_camera_world_support import binary,result,invoke
5
+ from analyze_qvm import write_json
6
+ from qvm_spawn_support import entities
7
+ def encode(s):
8
+ a=s['input'];c=entities(a['count'],a['pattern']);out=[a['seed'],*a['avoid'],len(c)]
9
+ for e in c:out.extend([e['active'],e['name'],e['hash'],0,*e['origin'],*e['angles']])
10
+ out.extend([*a['bounds'],a['profile'],len(s['math'])])
11
+ for m in s['math']:out.extend([m['target'],m['arguments']['8'],0,m['result']])
12
+ return out
13
+ def project(s):return {'selection':{k:s[k] for k in ['result','rng_state','fatal','candidates','distances','origin','angles']},'queries':[{k:v for k,v in q.items() if k!='trace_sha256'} for q in s['queries']]}
14
+
15
+ def main():
16
+ b=binary();p=Path('docs/research/qvm-queried-spawn.json');data=p.read_bytes();rows=[]
17
+ for s in json.loads(data)['probes']:rows.append({'source':project(s),'rust':result(b,'queried-spawn',encode(s))})
18
+ write_json(Path('docs/research/rust-queried-spawn.json'),{'source_report':str(p),'source_report_sha256':hashlib.sha256(data).hexdigest(),'probes':rows,'limits':'Rust matches64 original168114/167638 compositions and their ordered box query transcripts. The typed spatial-query callback runs only after class filtering and before each candidate distance. Existing boolean-based callers retain behavior. Query/domain errors preserve RNG; native broadphase/math and external callback effects remain open.'})
19
+ if __name__=='__main__':main()
scripts/test_rust_queried_spawn.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import hashlib,json,unittest
2
+ from pathlib import Path
3
+ class PortTests(unittest.TestCase):
4
+ def test_all_original_cases(self):
5
+ r=json.loads(Path('docs/research/rust-queried-spawn.json').read_text());src=json.loads(Path(r['source_report']).read_text());self.assertEqual(len(r['probes']),len(src['probes']));self.assertGreater(len(r['probes']),20)
6
+ for p in r['probes']:self.assertEqual(p['rust'],p['source'])
7
+ def test_source_identity(self):
8
+ r=json.loads(Path('docs/research/rust-queried-spawn.json').read_text());self.assertEqual(hashlib.sha256(Path(r['source_report']).read_bytes()).hexdigest(),r['source_report_sha256'])
9
+ if __name__=='__main__':unittest.main()
xtask/checks.tsv CHANGED
@@ -2612,3 +2612,18 @@ edge docs/research/qvm-queried-spawn.json scripts/test_qvm_queried_spawn.py
2612
  group qvm-queried-spawn scripts/analyze_qvm_queried_spawn.py
2613
  edge scripts/analyze_qvm_queried_spawn.py scripts/analyze_qvm_resumer_audit.py
2614
  edge scripts/qvm_spawn_query_support.py scripts/analyze_qvm_resumer_audit.py
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2612
  group qvm-queried-spawn scripts/analyze_qvm_queried_spawn.py
2613
  edge scripts/analyze_qvm_queried_spawn.py scripts/analyze_qvm_resumer_audit.py
2614
  edge scripts/qvm_spawn_query_support.py scripts/analyze_qvm_resumer_audit.py
2615
+ edge Cargo.toml scripts/analyze_rust_queried_spawn.py
2616
+ edge Cargo.lock scripts/analyze_rust_queried_spawn.py
2617
+ edge crates/urt-gameplay/Cargo.toml scripts/analyze_rust_queried_spawn.py
2618
+ edge crates/urt-gameplay/src/lib.rs scripts/analyze_rust_queried_spawn.py
2619
+ edge crates/urt-gameplay/src/camera_world.rs scripts/analyze_rust_queried_spawn.py
2620
+ edge crates/urt-gameplay/src/bin/urt-camera-world.rs scripts/analyze_rust_queried_spawn.py
2621
+ edge docs/research/qvm-queried-spawn.json scripts/analyze_rust_queried_spawn.py
2622
+ group rust-queried-spawn scripts/analyze_rust_health_impulse.py
2623
+ group gameplay scripts/analyze_rust_queried_spawn.py
2624
+ edge crates/urt-gameplay/src/native_policy.rs scripts/analyze_rust_queried_spawn.py
2625
+ group rust-queried-spawn scripts/analyze_rust_spawn_fallback.py
2626
+ report scripts/analyze_rust_queried_spawn.py docs/research/rust-queried-spawn.json
2627
+ edge docs/research/rust-queried-spawn.json scripts/test_rust_queried_spawn.py
2628
+ group rust-queried-spawn scripts/analyze_rust_queried_spawn.py
2629
+ edge scripts/analyze_rust_queried_spawn.py scripts/analyze_qvm_resumer_audit.py