gameplay: port scoreboard dispatch policy
Browse files- crates/urt-gameplay/src/bin/urt-lifecycle.rs +59 -0
- crates/urt-gameplay/src/lib.rs +2 -0
- crates/urt-gameplay/src/scoreboard.rs +84 -0
- docs/ASSET_PIPELINE_TODO.md +3 -0
- docs/FINDINGS.md +6 -0
- docs/PROVISIONAL_TODO.md +9 -0
- docs/RESEARCH_LEDGER.md +12 -0
- docs/REVERSE_ENGINEERING_TODO.md +6 -0
- docs/TESTING.md +5 -0
- docs/research/QVM_RUST_RANKING_PUBLICATION_BATCH.md +41 -0
- docs/research/README.md +6 -0
- docs/research/RUST_SCOREBOARD.md +9 -0
- docs/research/rust-health-impulse.json +1 -1
- docs/research/rust-scoreboard.json +0 -0
- scripts/analyze_rust_scoreboard.py +17 -0
- scripts/test_rust_scoreboard.py +14 -0
- xtask/checks.tsv +27 -0
- xtask/src/lib.rs +36 -0
crates/urt-gameplay/src/bin/urt-lifecycle.rs
CHANGED
|
@@ -341,6 +341,65 @@ fn run() -> Result<(), String> {
|
|
| 341 |
second.cached_ping as u32
|
| 342 |
);
|
| 343 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 344 |
"rank-mixed" if w.len() >= 4 && w[3] <= 64 => {
|
| 345 |
let n = w[3] as usize;
|
| 346 |
if w.len() != 4 + 12 * n {
|
|
|
|
| 341 |
second.cached_ping as u32
|
| 342 |
);
|
| 343 |
}
|
| 344 |
+
"scoreboard-recipients" if !w.is_empty() && w[0] <= 64 => {
|
| 345 |
+
let n = w[0] as usize;
|
| 346 |
+
if w.len() != 1 + n {
|
| 347 |
+
return Err("invalid scoreboard states".into());
|
| 348 |
+
}
|
| 349 |
+
let states: Vec<_> = w[1..].iter().map(|v| *v as i32).collect();
|
| 350 |
+
let r = urt_gameplay::scoreboard::recipients(&states)
|
| 351 |
+
.map_err(|e| format!("unsupported input: {e:?}"))?;
|
| 352 |
+
println!(
|
| 353 |
+
"{{\"recipients\":[{}]}}",
|
| 354 |
+
r.iter()
|
| 355 |
+
.map(|v| v.to_string())
|
| 356 |
+
.collect::<Vec<_>>()
|
| 357 |
+
.join(",")
|
| 358 |
+
);
|
| 359 |
+
}
|
| 360 |
+
"rank-publish" if w.len() >= 5 && w[3] <= 1 && w[4] <= 64 => {
|
| 361 |
+
let n = w[4] as usize;
|
| 362 |
+
if w.len() != 5 + 12 * n {
|
| 363 |
+
return Err("invalid ranking publication words".into());
|
| 364 |
+
}
|
| 365 |
+
let slots: Vec<_> = w[5..]
|
| 366 |
+
.chunks_exact(12)
|
| 367 |
+
.map(|v| urt_gameplay::ranking_population::Slot {
|
| 368 |
+
client: ranking_client(v),
|
| 369 |
+
entity_flags: v[10],
|
| 370 |
+
rank: v[11],
|
| 371 |
+
})
|
| 372 |
+
.collect();
|
| 373 |
+
let r = urt_gameplay::scoreboard::ranking_plan(
|
| 374 |
+
w[0] as i32,
|
| 375 |
+
&slots,
|
| 376 |
+
[w[1] as i32, w[2] as i32],
|
| 377 |
+
w[3] != 0,
|
| 378 |
+
)
|
| 379 |
+
.map_err(|e| format!("unsupported input: {e:?}"))?;
|
| 380 |
+
println!(
|
| 381 |
+
"{{\"order\":[{}],\"ranks\":[{}],\"globals\":{},\"recipients\":[{}]}}",
|
| 382 |
+
r.ranking
|
| 383 |
+
.population
|
| 384 |
+
.order
|
| 385 |
+
.iter()
|
| 386 |
+
.map(|v| v.to_string())
|
| 387 |
+
.collect::<Vec<_>>()
|
| 388 |
+
.join(","),
|
| 389 |
+
r.ranking
|
| 390 |
+
.ranks
|
| 391 |
+
.iter()
|
| 392 |
+
.map(|v| v.to_string())
|
| 393 |
+
.collect::<Vec<_>>()
|
| 394 |
+
.join(","),
|
| 395 |
+
population_json(&r.ranking.population),
|
| 396 |
+
r.recipients
|
| 397 |
+
.iter()
|
| 398 |
+
.map(|v| v.to_string())
|
| 399 |
+
.collect::<Vec<_>>()
|
| 400 |
+
.join(",")
|
| 401 |
+
);
|
| 402 |
+
}
|
| 403 |
"rank-mixed" if w.len() >= 4 && w[3] <= 64 => {
|
| 404 |
let n = w[3] as usize;
|
| 405 |
if w.len() != 4 + 12 * n {
|
crates/urt-gameplay/src/lib.rs
CHANGED
|
@@ -161,3 +161,5 @@ pub mod stat_totals;
|
|
| 161 |
pub mod rank_sort;
|
| 162 |
|
| 163 |
pub mod ranking_population;
|
|
|
|
|
|
|
|
|
| 161 |
pub mod rank_sort;
|
| 162 |
|
| 163 |
pub mod ranking_population;
|
| 164 |
+
|
| 165 |
+
pub mod scoreboard;
|
crates/urt-gameplay/src/scoreboard.rs
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
//! QVM6328 recipient policy and a pure captured-7805 ranking publication plan.
|
| 2 |
+
use crate::ranking_population::{self, PopulationError, RankedPopulation, Slot};
|
| 3 |
+
/// Includes connected spectators. Entity flags/activity do not gate6328.
|
| 4 |
+
pub fn recipients(states: &[i32]) -> Result<Vec<usize>, PopulationError> {
|
| 5 |
+
if states.len() > 64 {
|
| 6 |
+
return Err(PopulationError::TooManyClients);
|
| 7 |
+
}
|
| 8 |
+
Ok(states
|
| 9 |
+
.iter()
|
| 10 |
+
.enumerate()
|
| 11 |
+
.filter_map(|(id, &state)| (state == 2).then_some(id))
|
| 12 |
+
.collect())
|
| 13 |
+
}
|
| 14 |
+
#[derive(Clone, Debug, PartialEq, Eq)]
|
| 15 |
+
pub struct RankingPlan {
|
| 16 |
+
pub ranking: RankedPopulation,
|
| 17 |
+
pub recipients: Vec<usize>,
|
| 18 |
+
}
|
| 19 |
+
/// Pure fixture contract:7805 is captured without effects. A live caller must
|
| 20 |
+
/// execute its exit-rule effects before re-evaluating6328 connection states.
|
| 21 |
+
pub fn ranking_plan(
|
| 22 |
+
mode: i32,
|
| 23 |
+
slots: &[Slot],
|
| 24 |
+
scores: [i32; 2],
|
| 25 |
+
broadcast: bool,
|
| 26 |
+
) -> Result<RankingPlan, PopulationError> {
|
| 27 |
+
let ranking = ranking_population::ranked(mode, slots, scores)?;
|
| 28 |
+
let recipients = if broadcast {
|
| 29 |
+
recipients(
|
| 30 |
+
&slots
|
| 31 |
+
.iter()
|
| 32 |
+
.map(|s| s.client.connection)
|
| 33 |
+
.collect::<Vec<_>>(),
|
| 34 |
+
)?
|
| 35 |
+
} else {
|
| 36 |
+
vec![]
|
| 37 |
+
};
|
| 38 |
+
Ok(RankingPlan {
|
| 39 |
+
ranking,
|
| 40 |
+
recipients,
|
| 41 |
+
})
|
| 42 |
+
}
|
| 43 |
+
#[cfg(test)]
|
| 44 |
+
mod tests {
|
| 45 |
+
use super::*;
|
| 46 |
+
use crate::ranking::Client;
|
| 47 |
+
#[test]
|
| 48 |
+
fn exact_connection_gate_and_scan_order() {
|
| 49 |
+
assert_eq!(recipients(&[-1, 0, 1, 2, 3, 2]).unwrap(), [3, 5]);
|
| 50 |
+
assert_eq!(recipients(&[]).unwrap(), []);
|
| 51 |
+
}
|
| 52 |
+
#[test]
|
| 53 |
+
fn count_limit_and_all_connected() {
|
| 54 |
+
assert_eq!(recipients(&[2; 64]).unwrap(), (0..64).collect::<Vec<_>>());
|
| 55 |
+
assert_eq!(recipients(&[2; 65]), Err(PopulationError::TooManyClients));
|
| 56 |
+
}
|
| 57 |
+
#[test]
|
| 58 |
+
fn connected_spectator_plan_respects_gate_and_preserves_ranking() {
|
| 59 |
+
let c = Client {
|
| 60 |
+
spectator_state: 0,
|
| 61 |
+
follow_index: 0,
|
| 62 |
+
connection: 2,
|
| 63 |
+
team: 3,
|
| 64 |
+
spectator_time: 0,
|
| 65 |
+
time: 0,
|
| 66 |
+
class: 0,
|
| 67 |
+
score: 0,
|
| 68 |
+
deaths: 0,
|
| 69 |
+
awards: 0,
|
| 70 |
+
};
|
| 71 |
+
let s = [Slot {
|
| 72 |
+
client: c,
|
| 73 |
+
entity_flags: 8,
|
| 74 |
+
rank: 333,
|
| 75 |
+
}];
|
| 76 |
+
let yes = ranking_plan(0, &s, [0, 0], true).unwrap();
|
| 77 |
+
let no = ranking_plan(0, &s, [0, 0], false).unwrap();
|
| 78 |
+
assert_eq!(yes.recipients, [0]);
|
| 79 |
+
assert_eq!(no.recipients, []);
|
| 80 |
+
assert_eq!(yes.ranking, no.ranking);
|
| 81 |
+
assert_eq!(yes.ranking.ranks, [333]);
|
| 82 |
+
assert_eq!(yes.ranking.population.playing, 0);
|
| 83 |
+
}
|
| 84 |
+
}
|
docs/ASSET_PIPELINE_TODO.md
CHANGED
|
@@ -1024,3 +1024,6 @@ Research progress: RE-789 [report](research/QVM_RANK_LARGE_SORT.md); original as
|
|
| 1024 |
|
| 1025 |
|
| 1026 |
RE-780–789 uses original verified QVM bytes without changing assets. Shared byte-memory interfaces and remaining native/runtime conversions remain open. [Batch evidence](research/QVM_RESUMER_PARTITION_BATCH.md).
|
|
|
|
|
|
|
|
|
|
|
|
| 1024 |
|
| 1025 |
|
| 1026 |
RE-780–789 uses original verified QVM bytes without changing assets. Shared byte-memory interfaces and remaining native/runtime conversions remain open. [Batch evidence](research/QVM_RESUMER_PARTITION_BATCH.md).
|
| 1027 |
+
|
| 1028 |
+
|
| 1029 |
+
RE-791–800 uses verified original QVM bytes and updates code/docs only. No original assets changed; full runtime/conversion/platform acceptance remains open. [Batch evidence](research/QVM_RUST_RANKING_PUBLICATION_BATCH.md).
|
docs/FINDINGS.md
CHANGED
|
@@ -1397,3 +1397,9 @@ RE-780–789 exposes and bounds legacy resumer timing/byte-state flaws, migrates
|
|
| 1397 |
|
| 1398 |
|
| 1399 |
**RE-799:** 6328 dispatches179363 to every state2 client in scan order, including spectators, and now executes inside48 complete ranking caller fixtures. [Evidence](research/QVM_SCOREBOARD_BROADCAST.md).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1397 |
|
| 1398 |
|
| 1399 |
**RE-799:** 6328 dispatches179363 to every state2 client in scan order, including spectators, and now executes inside48 complete ranking caller fixtures. [Evidence](research/QVM_SCOREBOARD_BROADCAST.md).
|
| 1400 |
+
|
| 1401 |
+
|
| 1402 |
+
**RE-800:** Rust reproduces all6328 recipient requests and exact rank-plan outputs across69 source fixtures, including connected spectators and both broadcast gates. [Evidence](research/RUST_SCOREBOARD.md).
|
| 1403 |
+
|
| 1404 |
+
|
| 1405 |
+
RE-791–800 adds exact Rust source-sort/ranking parity, mixed count/prefix semantics, composed stat ratios and source/Rust scoreboard dispatch. [Batch evidence](research/QVM_RUST_RANKING_PUBLICATION_BATCH.md).
|
docs/PROVISIONAL_TODO.md
CHANGED
|
@@ -1401,3 +1401,12 @@ RE-798: 278834 now optionally composes actual team counters and both ratio refre
|
|
| 1401 |
|
| 1402 |
|
| 1403 |
RE-799: 6328 dispatches179363 to every state2 client in scan order, including spectators, and now executes inside48 complete ranking caller fixtures. Full runtime completion remains open. [Evidence](research/QVM_SCOREBOARD_BROADCAST.md).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1401 |
|
| 1402 |
|
| 1403 |
RE-799: 6328 dispatches179363 to every state2 client in scan order, including spectators, and now executes inside48 complete ranking caller fixtures. Full runtime completion remains open. [Evidence](research/QVM_SCOREBOARD_BROADCAST.md).
|
| 1404 |
+
|
| 1405 |
+
|
| 1406 |
+
RE-800: Rust reproduces all6328 recipient requests and exact rank-plan outputs across69 source fixtures, including connected spectators and both broadcast gates. Full runtime completion remains open. [Evidence](research/RUST_SCOREBOARD.md).
|
| 1407 |
+
|
| 1408 |
+
|
| 1409 |
+
RE-791–800 completes additional bounded source/Rust ranking and dispatch contracts. Remaining legacy traces, native serialization, runtime integration and platform acceptance stay open. [Batch evidence](research/QVM_RUST_RANKING_PUBLICATION_BATCH.md).
|
| 1410 |
+
|
| 1411 |
+
|
| 1412 |
+
RE-791–800 validation: 1,562 Python and102 Rust tests pass;226 affected producers reproduce byte-identically. Exact Rust sorting/ranking and source/Rust broadcast contracts advance the bounded rewrite. Native exit-rule/serialization effects, remaining tracing consumers, full runtime integration and Linux/macOS Bevy/Solari acceptance remain open. [Evidence](research/QVM_RUST_RANKING_PUBLICATION_BATCH.md).
|
docs/RESEARCH_LEDGER.md
CHANGED
|
@@ -1659,3 +1659,15 @@ Rust matches exact order, rank words and count globals for144 complete mixed/eli
|
|
| 1659 |
## 2026-10-26 — RE-799 Actual scoreboard broadcast dispatch and full ranking caller gate
|
| 1660 |
|
| 1661 |
6328 dispatches179363 to every state2 client in scan order, including spectators, and now executes inside48 complete ranking caller fixtures. Focused `cargo xtask test-group scoreboard-broadcast` passes (6 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.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1659 |
## 2026-10-26 — RE-799 Actual scoreboard broadcast dispatch and full ranking caller gate
|
| 1660 |
|
| 1661 |
6328 dispatches179363 to every state2 client in scan order, including spectators, and now executes inside48 complete ranking caller fixtures. Focused `cargo xtask test-group scoreboard-broadcast` passes (6 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.
|
| 1662 |
+
|
| 1663 |
+
|
| 1664 |
+
## 2026-10-26 — RE-800 Rust scoreboard recipient policy and bounded ranking publication plan
|
| 1665 |
+
|
| 1666 |
+
Rust reproduces all6328 recipient requests and exact rank-plan outputs across69 source fixtures, including connected spectators and both broadcast gates. Focused `cargo xtask test-group rust-scoreboard` 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.
|
| 1667 |
+
|
| 1668 |
+
RE-800 final focused recheck passes10 Python tests and deterministic report/storage/link/whitespace checks after batch documentation and explicit formatted-source registry edges. The scoped xtask registry integration test also passes, verifying all ten new source-report mappings and new module selection paths. Full validation starts only after commit ten.
|
| 1669 |
+
|
| 1670 |
+
|
| 1671 |
+
## 2026-10-26 — RE-791–800 full batch validation
|
| 1672 |
+
|
| 1673 |
+
After exactly ten task commits from `c6b4a1eb`, the single `cargo xtask test-full --since c6b4a1eb` run passed 1,562 Python tests (516+346+331+369) and102 Rust tests (10 assets,64 gameplay,28 xtask). All226 affected report producers regenerated twice byte-identically. Storage policy,359 documentation files and whitespace checks passed; regeneration left the working tree clean. Final documentation-only notes are checked separately and amended into task ten without repeating the full suite. [Batch contracts and RE-801 work](research/QVM_RUST_RANKING_PUBLICATION_BATCH.md).
|
docs/REVERSE_ENGINEERING_TODO.md
CHANGED
|
@@ -1447,3 +1447,9 @@ Purpose: turn the supplied installation into an evidence-backed specification of
|
|
| 1447 |
|
| 1448 |
|
| 1449 |
- [x] RE-799 Actual scoreboard broadcast dispatch and full ranking caller gate ([evidence](research/QVM_SCOREBOARD_BROADCAST.md)).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1447 |
|
| 1448 |
|
| 1449 |
- [x] RE-799 Actual scoreboard broadcast dispatch and full ranking caller gate ([evidence](research/QVM_SCOREBOARD_BROADCAST.md)).
|
| 1450 |
+
|
| 1451 |
+
|
| 1452 |
+
- [x] RE-800 Rust scoreboard recipient policy and bounded ranking publication plan ([evidence](research/RUST_SCOREBOARD.md)).
|
| 1453 |
+
|
| 1454 |
+
|
| 1455 |
+
- [ ] RE-801 Continue legacy/shared-byte tracing audits, exit-rule7805 and scoreboard179363 effects/serialization, complete stat and runtime ownership integration, native sort variants and Linux/macOS Bevy/Solari validation. [Next tasks](research/QVM_RUST_RANKING_PUBLICATION_BATCH.md#next-work--re-801).
|
docs/TESTING.md
CHANGED
|
@@ -113,3 +113,8 @@ Focused groups include `rank-comparator`, `rank-small-sort`, `ranking-complete`,
|
|
| 113 |
Use `cargo xtask test-group resumer-audit`, `return-placement`, `reentry-witness` or `execute-reentry` for inventory and synthetic trace contracts. Source migrations use `notification-reentry`, `award-reentry`, `counter-reentry` and `movement-reentry`; sorting uses `rank-partition-helpers` and `rank-large-sort`. Each name is a separate argument after `test-group`. Groups follow downstream dependency closure, so shared constants/helpers can select additional source and Rust parity reports. The static inventory scanner has explicit edges from current non-test Python scripts; register newly added scan inputs. Unknown changes retain full-suite fallback.
|
| 114 |
|
| 115 |
For ten-task batches, run focused validation before each commit, then `cargo xtask test-full --since <batch-base>` once after commit ten. Review all regenerated report diffs and record the outcome in the batch document before pushing. See [RE-780–789](research/QVM_RESUMER_PARTITION_BATCH.md).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 113 |
Use `cargo xtask test-group resumer-audit`, `return-placement`, `reentry-witness` or `execute-reentry` for inventory and synthetic trace contracts. Source migrations use `notification-reentry`, `award-reentry`, `counter-reentry` and `movement-reentry`; sorting uses `rank-partition-helpers` and `rank-large-sort`. Each name is a separate argument after `test-group`. Groups follow downstream dependency closure, so shared constants/helpers can select additional source and Rust parity reports. The static inventory scanner has explicit edges from current non-test Python scripts; register newly added scan inputs. Unknown changes retain full-suite fallback.
|
| 114 |
|
| 115 |
For ten-task batches, run focused validation before each commit, then `cargo xtask test-full --since <batch-base>` once after commit ten. Review all regenerated report diffs and record the outcome in the batch document before pushing. See [RE-780–789](research/QVM_RESUMER_PARTITION_BATCH.md).
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
## Ranking composition groups
|
| 119 |
+
|
| 120 |
+
Additional focused groups: `child-word-memory`, `ranking-large-caller`, `ranking-population`, `rust-rank-sort`, `rust-ranking-complete`, `rust-ranking-population`, `rust-ranking-mixed`, `stat-composition`, `scoreboard-broadcast` and `rust-scoreboard`. Invoke each as `cargo xtask test-group <name>`. Rust groups refresh scoped gameplay test-count metadata. New source-report paths assembled from strings have explicit registry edges to their parity producers; the registry regression verifies those selections. [Batch contracts and limits](research/QVM_RUST_RANKING_PUBLICATION_BATCH.md).
|
docs/research/QVM_RUST_RANKING_PUBLICATION_BATCH.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Ranking caller composition, Rust sorting and scoreboard dispatch
|
| 2 |
+
|
| 3 |
+
RE-791–800; baseline `c6b4a1eb`. Ten task commits, focused `cargo xtask` validation before each, one full-suite run after commit ten. Original assets remain unchanged.
|
| 4 |
+
|
| 5 |
+
| Task | Result | Evidence |
|
| 6 |
+
| --- | --- | --- |
|
| 7 |
+
| RE-791 | Explicit child word-to-byte synchronization in corrected resumers | [Child memory](QVM_CHILD_WORD_MEMORY.md) |
|
| 8 |
+
| RE-792 | Complete5864 with actual sort through64 eligible clients | [Large caller](QVM_RANKING_LARGE_CALLER.md) |
|
| 9 |
+
| RE-793 | Mixed population counters and positional rank-prefix behavior | [Population](QVM_RANKING_POPULATION.md) |
|
| 10 |
+
| RE-794 | Rust source-shaped aligned sort, including exact unstable ties | [Rust sort](RUST_RANK_SORT.md) |
|
| 11 |
+
| RE-795 | Rust complete eligible ranking composition | [Rust caller](RUST_RANKING_COMPLETE.md) |
|
| 12 |
+
| RE-796 | Rust collection and all eight source count globals | [Rust population](RUST_RANKING_POPULATION.md) |
|
| 13 |
+
| RE-797 | Rust mixed population sorting and rank-word composition | [Rust mixed caller](RUST_RANKING_MIXED.md) |
|
| 14 |
+
| RE-798 | Actual team counters and ratio refreshes inside method updates | [Stat composition](QVM_STAT_COMPOSITION.md) |
|
| 15 |
+
| RE-799 | Actual6328 broadcast and full ranking caller gate | [Broadcast](QVM_SCOREBOARD_BROADCAST.md) |
|
| 16 |
+
| RE-800 | Rust recipient policy and bounded publication plan | [Rust scoreboard](RUST_SCOREBOARD.md) |
|
| 17 |
+
|
| 18 |
+
## Findings
|
| 19 |
+
|
| 20 |
+
The source sort's equal-key swaps are observable. Rust follows its insertion, pivot, median/ninther, partition, recursive and tail-loop paths, matching exact source permutations rather than substituting a stable host sort. The raw comparator is still not an Ord contract: connecting/deferred prefilters can be asymmetric. The mixed-population implementation executes the source-shaped algorithm without using Rust's sorting APIs.
|
| 21 |
+
|
| 22 |
+
The source collects nonzero connection states, counts connected non-spectators separately from playing state2 records, and excludes bot-flagged records only from human/team-human totals. First-playing identifiers can name bots. Ordinary rank assignment writes a positional prefix of the sorted collected array; deferred fixtures demonstrate that this prefix can differ from the active-client set. Team modes assign every collected slot, including spectators/connecting clients. Inactive and other untouched rank words preserve caller state.
|
| 23 |
+
|
| 24 |
+
Broadcast6328 filters only connection state2, so connected spectators receive requests even when playing count is zero. Full caller fixtures execute this dispatch after a captured7805 exit-rule call. Rust's publication plan has the same capture assumption: it does not implement7805 effects or179363 scoreboard formatting/sends. A runtime must apply exit-rule effects before reselecting recipients.
|
| 25 |
+
|
| 26 |
+
Explicit child-word synchronization makes changed/new aligned words visible to subsequent byte loads while preserving unchanged parent byte overlays. It rejects unsupported/deleted words and requires corrected reentry. It does not unify every STORE1/LOAD4/child byte effect into a complete shared VM memory model. The asymmetric64-record source sort fixtures also require more than512 bounded segments; only that sort opts into4096, below the shared configurable cap8192. This allowance does not establish native runtime cost.
|
| 27 |
+
|
| 28 |
+
## Validation and selection
|
| 29 |
+
|
| 30 |
+
Each focused group regenerates its selected reports twice, runs affected tests and checks storage, documentation links and whitespace. Scoped gameplay tests accompany Rust changes. Explicit source-report edges cover the formatted paths used by the new parity producers; the xtask registry regression checks every mapping as well as module-to-parity and scoped-test-metadata selection.
|
| 31 |
+
|
| 32 |
+
After exactly ten task commits, the single `cargo xtask test-full --since c6b4a1eb` run passed **1,562 Python tests and 102 Rust tests** (10 assets, 64 gameplay, 28 xtask). All 226 affected report producers regenerated twice byte-identically. Storage policy, links across 359 documentation files and whitespace guards passed. Regeneration left the working tree clean, with no additional report refreshes to review. Final validation notes are checked with documentation-only xtask selection and included by amending the tenth commit; the full suite is not repeated. Native executable, Linux/macOS gameplay and interactive Bevy/Solari acceptance remain open.
|
| 33 |
+
|
| 34 |
+
## Next work — RE-801
|
| 35 |
+
|
| 36 |
+
- Continue auditing the remaining156 default/literal legacy resumer expressions and three unresolved mode expressions.22 computed ranges still require manual review; inventory categories overlap and are not counts of proven bugs.
|
| 37 |
+
- Implement an explicit child byte-write interface and unify supported byte/word reads and writes before claiming a complete shared VM model. Retain fail-closed handling for unsupported overlap, deletion or callback boundaries.
|
| 38 |
+
- Recover7805 exit-rule effects and179363 scoreboard body, formatting limits, native send ABI and actual serialized messages. Validate state changes between ranking and broadcast.
|
| 39 |
+
- Extend stat-update compositions through awards285227 and leaderboard283622; bound divisor costs and integrate the resulting state with runtime entity/client ownership.
|
| 40 |
+
- Recover byte/unaligned sort variants and validate native ABI/error domains. Expand original/native trace comparison beyond the finite synthetic QVM fixtures.
|
| 41 |
+
- Connect the pure Rust contracts to the game runtime; continue world collision, movement, combat, protocol, maps/models/materials/audio and rendering. Validate native macOS/Linux builds and interactive Bevy/Solari acceptance.
|
docs/research/README.md
CHANGED
|
@@ -987,3 +987,9 @@ The extension collector adds five focused tests for native tail calls, command-r
|
|
| 987 |
|
| 988 |
|
| 989 |
[RE-799: Actual scoreboard broadcast dispatch and full ranking caller gate](QVM_SCOREBOARD_BROADCAST.md).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 987 |
|
| 988 |
|
| 989 |
[RE-799: Actual scoreboard broadcast dispatch and full ranking caller gate](QVM_SCOREBOARD_BROADCAST.md).
|
| 990 |
+
|
| 991 |
+
|
| 992 |
+
[RE-800: Rust scoreboard recipient policy and bounded ranking publication plan](RUST_SCOREBOARD.md).
|
| 993 |
+
|
| 994 |
+
|
| 995 |
+
[RE-791–800 batch: Rust ranking and publication](QVM_RUST_RANKING_PUBLICATION_BATCH.md).
|
docs/research/RUST_SCOREBOARD.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Rust scoreboard recipient policy and bounded ranking publication plan
|
| 2 |
+
|
| 3 |
+
RE-800. Reproduce with `python3 scripts/analyze_rust_scoreboard.py`; focused validation: `cargo xtask test-group rust-scoreboard`. [Machine report](rust-scoreboard.json).
|
| 4 |
+
|
| 5 |
+
The recipient policy filters only connection state2 in scan order and bounds safe slices at64. A pure ranking plan composes the recovered mixed-population ranking contract with this gate.21 standalone and48 full source fixtures check exact order, ranks, globals and recipients. Unit tests cover signed connection states,64/65 boundaries and spectator rank preservation.7805 exit-rule effects and179363 scoreboard body/native sends remain unimplemented; a live runtime must execute exit-rule effects before selecting fresh recipients.
|
| 6 |
+
|
| 7 |
+
Original executable/native runtime and complete Linux/macOS Rust/Bevy/Solari acceptance remain unverified.
|
| 8 |
+
|
| 9 |
+
Formatted input paths in all five new Rust parity producers are declared explicitly in the xtask registry. The registry regression verifies each source-report-to-producer/test mapping without full fallback.
|
docs/research/rust-health-impulse.json
CHANGED
|
@@ -2985,7 +2985,7 @@
|
|
| 2985 |
"exit_code": 2
|
| 2986 |
}
|
| 2987 |
],
|
| 2988 |
-
"rust_unit_tests":
|
| 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": 64,
|
| 2989 |
"source_report": "docs/research/qvm-health-impulse.json",
|
| 2990 |
"source_report_sha256": "625fd8064004ea6dbdf398d51d0af844d9c635ff04c0a0790886dddb6ab81e71"
|
| 2991 |
}
|
docs/research/rust-scoreboard.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
scripts/analyze_rust_scoreboard.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
import hashlib,json
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
from analyze_rust_ranking import words
|
| 5 |
+
from rust_lifecycle_support import binary,result,invoke
|
| 6 |
+
from qvm_notification_support import POOL
|
| 7 |
+
from analyze_qvm import write_json
|
| 8 |
+
|
| 9 |
+
def ids(p):return [(r['arguments']['8']-POOL)//880 for r in p['requests']]
|
| 10 |
+
def main():
|
| 11 |
+
b=binary();p=Path('docs/research/qvm-scoreboard-broadcast.json');data=p.read_bytes();source=json.loads(data);rows=[];callers=[]
|
| 12 |
+
for s in source['probes']:rows.append({'states':s['states'],'source':ids(s),'rust':result(b,'scoreboard-recipients',[len(s['states']),*s['states']])})
|
| 13 |
+
for s in source['callers']:
|
| 14 |
+
a=s['input'];c=a['clients'];r=result(b,'rank-publish',[a['mode'],*a['team_scores'],a['blocked'],len(c),*[v for client in c for v in [*words(client),client.get('flags',0),77]]]);callers.append({'input':a,'source_order':s['order'],'source_ranks':s['ranks'],'source_globals':s['globals'],'source_recipients':[id for effect in s['broadcasts'] for id in ids(effect)],'rust':r})
|
| 15 |
+
bad=invoke(b,'scoreboard-recipients',[65],check=False);assert bad.returncode
|
| 16 |
+
write_json(Path('docs/research/rust-scoreboard.json'),{'source_report':str(p),'source_report_sha256':hashlib.sha256(data).hexdigest(),'probes':rows,'callers':callers,'rejections':[bad.returncode],'limits':'Rust6328 state2 recipient policy and captured-7805 ranking publication plan match21 standalone and48 full source callers. Negative raw count represented by empty slice. No native scoreboard body179363/text/send execution; real7805 effects must precede fresh recipient selection in a runtime.'})
|
| 17 |
+
if __name__=='__main__':main()
|
scripts/test_rust_scoreboard.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import hashlib,json,unittest
|
| 2 |
+
from pathlib import Path
|
| 3 |
+
class RustScoreboardTests(unittest.TestCase):
|
| 4 |
+
@classmethod
|
| 5 |
+
def setUpClass(cls):cls.r=json.loads(Path('docs/research/rust-scoreboard.json').read_text())
|
| 6 |
+
def test_raw_source_and_independent_connection_policy(self):
|
| 7 |
+
self.assertEqual(len(self.r['probes']),21)
|
| 8 |
+
for p in self.r['probes']:self.assertEqual(p['rust']['recipients'],p['source']);self.assertEqual(p['source'],[j for j,state in enumerate(p['states']) if state==2])
|
| 9 |
+
def test_full_rank_plan_exact_source_outputs_and_gate(self):
|
| 10 |
+
self.assertEqual(len(self.r['callers']),48)
|
| 11 |
+
for p in self.r['callers']:
|
| 12 |
+
r=p['rust'];self.assertEqual(r['order'],p['source_order']);self.assertEqual(r['ranks'],p['source_ranks']);self.assertEqual(r['globals'],p['source_globals']);self.assertEqual(r['recipients'],p['source_recipients']);self.assertEqual(r['recipients'],[j for j,c in enumerate(p['input']['clients']) if c['468']==2] if p['input']['blocked'] else [])
|
| 13 |
+
def test_identity_and_count_rejection(self):self.assertEqual(hashlib.sha256(Path(self.r['source_report']).read_bytes()).hexdigest(),self.r['source_report_sha256']);self.assertTrue(all(self.r['rejections']))
|
| 14 |
+
if __name__=='__main__':unittest.main()
|
xtask/checks.tsv
CHANGED
|
@@ -1862,3 +1862,30 @@ edge docs/research/qvm-scoreboard-broadcast.json scripts/test_qvm_scoreboard_bro
|
|
| 1862 |
group scoreboard-broadcast scripts/analyze_qvm_scoreboard_broadcast.py
|
| 1863 |
edge scripts/analyze_qvm_scoreboard_broadcast.py scripts/analyze_qvm_resumer_audit.py
|
| 1864 |
edge scripts/qvm_scoreboard_broadcast_support.py scripts/analyze_qvm_resumer_audit.py
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1862 |
group scoreboard-broadcast scripts/analyze_qvm_scoreboard_broadcast.py
|
| 1863 |
edge scripts/analyze_qvm_scoreboard_broadcast.py scripts/analyze_qvm_resumer_audit.py
|
| 1864 |
edge scripts/qvm_scoreboard_broadcast_support.py scripts/analyze_qvm_resumer_audit.py
|
| 1865 |
+
edge Cargo.toml scripts/analyze_rust_scoreboard.py
|
| 1866 |
+
edge Cargo.lock scripts/analyze_rust_scoreboard.py
|
| 1867 |
+
edge crates/urt-gameplay/Cargo.toml scripts/analyze_rust_scoreboard.py
|
| 1868 |
+
edge crates/urt-gameplay/src/lib.rs scripts/analyze_rust_scoreboard.py
|
| 1869 |
+
edge crates/urt-gameplay/src/bin/urt-lifecycle.rs scripts/analyze_rust_scoreboard.py
|
| 1870 |
+
edge crates/urt-gameplay/src/scoreboard.rs scripts/analyze_rust_scoreboard.py
|
| 1871 |
+
edge crates/urt-gameplay/src/ranking_population.rs scripts/analyze_rust_scoreboard.py
|
| 1872 |
+
edge crates/urt-gameplay/src/rank_sort.rs scripts/analyze_rust_scoreboard.py
|
| 1873 |
+
edge crates/urt-gameplay/src/ranking.rs scripts/analyze_rust_scoreboard.py
|
| 1874 |
+
edge crates/urt-gameplay/src/scoreboard.rs scripts/analyze_rust_health_impulse.py
|
| 1875 |
+
edge crates/urt-gameplay/src/ranking.rs scripts/analyze_rust_ranking_population.py
|
| 1876 |
+
group gameplay scripts/analyze_rust_scoreboard.py
|
| 1877 |
+
group rust-scoreboard scripts/analyze_rust_health_impulse.py
|
| 1878 |
+
report scripts/analyze_rust_scoreboard.py docs/research/rust-scoreboard.json
|
| 1879 |
+
edge docs/research/rust-scoreboard.json scripts/test_rust_scoreboard.py
|
| 1880 |
+
group rust-scoreboard scripts/analyze_rust_scoreboard.py
|
| 1881 |
+
edge scripts/analyze_rust_scoreboard.py scripts/analyze_qvm_resumer_audit.py
|
| 1882 |
+
edge docs/research/qvm-rank-small-sort.json scripts/analyze_rust_rank_sort.py
|
| 1883 |
+
edge docs/research/qvm-rank-large-sort.json scripts/analyze_rust_rank_sort.py
|
| 1884 |
+
edge docs/research/qvm-ranking-complete.json scripts/analyze_rust_ranking_complete.py
|
| 1885 |
+
edge docs/research/qvm-ranking-large-caller.json scripts/analyze_rust_ranking_complete.py
|
| 1886 |
+
edge docs/research/qvm-ranking-setup.json scripts/analyze_rust_ranking_population.py
|
| 1887 |
+
edge docs/research/qvm-ranking-large-caller.json scripts/analyze_rust_ranking_population.py
|
| 1888 |
+
edge docs/research/qvm-ranking-population.json scripts/analyze_rust_ranking_population.py
|
| 1889 |
+
edge docs/research/qvm-ranking-large-caller.json scripts/analyze_rust_ranking_mixed.py
|
| 1890 |
+
edge docs/research/qvm-ranking-population.json scripts/analyze_rust_ranking_mixed.py
|
| 1891 |
+
edge docs/research/qvm-scoreboard-broadcast.json scripts/analyze_rust_scoreboard.py
|
xtask/src/lib.rs
CHANGED
|
@@ -1043,6 +1043,7 @@ mod integration_tests {
|
|
| 1043 |
("ranking", "ranking"),
|
| 1044 |
("rank_sort", "rank_sort"),
|
| 1045 |
("ranking_population", "ranking_population"),
|
|
|
|
| 1046 |
("leaderboard", "leaderboard"),
|
| 1047 |
("recipient_wire", "recipient_wire"),
|
| 1048 |
] {
|
|
@@ -1072,6 +1073,41 @@ mod integration_tests {
|
|
| 1072 |
.contains(&format!("scripts/test_rust_{producer}.py"))
|
| 1073 |
);
|
| 1074 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1075 |
// Validate the entire registry DAG, including multi-output native/QVM writers.
|
| 1076 |
g.producers_in_order(&g.outputs.keys().cloned().collect())
|
| 1077 |
.unwrap();
|
|
|
|
| 1043 |
("ranking", "ranking"),
|
| 1044 |
("rank_sort", "rank_sort"),
|
| 1045 |
("ranking_population", "ranking_population"),
|
| 1046 |
+
("scoreboard", "scoreboard"),
|
| 1047 |
("leaderboard", "leaderboard"),
|
| 1048 |
("recipient_wire", "recipient_wire"),
|
| 1049 |
] {
|
|
|
|
| 1073 |
.contains(&format!("scripts/test_rust_{producer}.py"))
|
| 1074 |
);
|
| 1075 |
}
|
| 1076 |
+
// Formatted source-report paths are explicit registry inputs. Each
|
| 1077 |
+
// must select its Rust parity report without full fallback.
|
| 1078 |
+
for (source, target) in [
|
| 1079 |
+
("qvm-rank-small-sort", "rank_sort"),
|
| 1080 |
+
("qvm-rank-large-sort", "rank_sort"),
|
| 1081 |
+
("qvm-ranking-complete", "ranking_complete"),
|
| 1082 |
+
("qvm-ranking-large-caller", "ranking_complete"),
|
| 1083 |
+
("qvm-ranking-setup", "ranking_population"),
|
| 1084 |
+
("qvm-ranking-large-caller", "ranking_population"),
|
| 1085 |
+
("qvm-ranking-population", "ranking_population"),
|
| 1086 |
+
("qvm-ranking-large-caller", "ranking_mixed"),
|
| 1087 |
+
("qvm-ranking-population", "ranking_mixed"),
|
| 1088 |
+
("qvm-scoreboard-broadcast", "scoreboard"),
|
| 1089 |
+
] {
|
| 1090 |
+
let selected = plan(
|
| 1091 |
+
&g,
|
| 1092 |
+
&[format!("docs/research/{source}.json")]
|
| 1093 |
+
.into_iter()
|
| 1094 |
+
.collect(),
|
| 1095 |
+
false,
|
| 1096 |
+
root,
|
| 1097 |
+
)
|
| 1098 |
+
.unwrap();
|
| 1099 |
+
assert!(selected.fallback.is_empty());
|
| 1100 |
+
assert!(
|
| 1101 |
+
selected
|
| 1102 |
+
.producers
|
| 1103 |
+
.contains(&format!("scripts/analyze_rust_{target}.py"))
|
| 1104 |
+
);
|
| 1105 |
+
assert!(
|
| 1106 |
+
selected
|
| 1107 |
+
.tests
|
| 1108 |
+
.contains(&format!("scripts/test_rust_{target}.py"))
|
| 1109 |
+
);
|
| 1110 |
+
}
|
| 1111 |
// Validate the entire registry DAG, including multi-output native/QVM writers.
|
| 1112 |
g.producers_in_order(&g.outputs.keys().cloned().collect())
|
| 1113 |
.unwrap();
|