gameplay: port complete bomb defuse callback
Browse files- crates/urt-gameplay/src/bin/urt-defuse.rs +93 -0
- crates/urt-gameplay/src/defuse.rs +191 -0
- docs/FINDINGS.md +2 -0
- docs/PROVISIONAL_TODO.md +2 -0
- docs/RESEARCH_LEDGER.md +4 -0
- docs/REVERSE_ENGINEERING_TODO.md +2 -0
- docs/research/README.md +2 -0
- docs/research/RUST_DEFUSE_COMPLETE.md +5 -0
- docs/research/rust-defuse-complete.json +0 -0
- docs/research/rust-health-impulse.json +1 -1
- scripts/analyze_rust_defuse_complete.py +19 -0
- scripts/test_rust_defuse_complete.py +9 -0
- xtask/checks.tsv +32 -0
- xtask/src/lib.rs +3 -0
crates/urt-gameplay/src/bin/urt-defuse.rs
CHANGED
|
@@ -84,6 +84,99 @@ fn main() {
|
|
| 84 |
let e = finish_defuse(&mut s, i).unwrap();
|
| 85 |
println!("{{{}}}", fields(s, &e));
|
| 86 |
}
|
|
|
|
| 87 |
_ => panic!("unknown policy"),
|
| 88 |
}
|
| 89 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 84 |
let e = finish_defuse(&mut s, i).unwrap();
|
| 85 |
println!("{{{}}}", fields(s, &e));
|
| 86 |
}
|
| 87 |
+
"full" => println!("{}", full(&w)),
|
| 88 |
_ => panic!("unknown policy"),
|
| 89 |
}
|
| 90 |
}
|
| 91 |
+
fn decode_full(w: &[u32]) -> (DefuseWorld, FullDefuseInput) {
|
| 92 |
+
use urt_gameplay::move_callbacks::ObjectiveClient;
|
| 93 |
+
assert_eq!(w.len(), 31);
|
| 94 |
+
let (state, base) = decode(w);
|
| 95 |
+
let selected = if w[27] == 1 { 2 } else { 1 };
|
| 96 |
+
let own_team = if w[19] == 0 { selected } else { w[19] };
|
| 97 |
+
let other_team = if w[24] == 0 { selected } else { w[24] };
|
| 98 |
+
let mut clients = vec![
|
| 99 |
+
ObjectiveClient {
|
| 100 |
+
connection: 2,
|
| 101 |
+
health: 1,
|
| 102 |
+
follow: 0,
|
| 103 |
+
team: selected,
|
| 104 |
+
busy: 0
|
| 105 |
+
};
|
| 106 |
+
(w[25] as usize).max(3)
|
| 107 |
+
];
|
| 108 |
+
clients[w[26] as usize] = ObjectiveClient {
|
| 109 |
+
connection: w[21],
|
| 110 |
+
health: w[22],
|
| 111 |
+
follow: w[23],
|
| 112 |
+
team: other_team,
|
| 113 |
+
busy: w[20],
|
| 114 |
+
};
|
| 115 |
+
clients[2] = ObjectiveClient {
|
| 116 |
+
connection: w[16],
|
| 117 |
+
health: w[17],
|
| 118 |
+
follow: w[18],
|
| 119 |
+
team: own_team,
|
| 120 |
+
busy: state.busy,
|
| 121 |
+
};
|
| 122 |
+
let mut bombs = vec![
|
| 123 |
+
DefuseBomb {
|
| 124 |
+
in_use: true,
|
| 125 |
+
has_class: true,
|
| 126 |
+
hash: 0,
|
| 127 |
+
point: [0; 3]
|
| 128 |
+
};
|
| 129 |
+
6
|
| 130 |
+
];
|
| 131 |
+
for j in [4, 5] {
|
| 132 |
+
bombs[j].hash = BOMB_CLASS_HASH;
|
| 133 |
+
bombs[j].point[0] = if j == 4 { w[28] } else { 30f32.to_bits() };
|
| 134 |
+
}
|
| 135 |
+
match w[29] {
|
| 136 |
+
1 => bombs[4].in_use = false,
|
| 137 |
+
2 => bombs[4].has_class = false,
|
| 138 |
+
3 => bombs[4].hash = 0,
|
| 139 |
+
_ => {}
|
| 140 |
+
}
|
| 141 |
+
if w[30] != 0 {
|
| 142 |
+
bombs[4].hash = 0;
|
| 143 |
+
bombs[5].hash = 0;
|
| 144 |
+
}
|
| 145 |
+
(
|
| 146 |
+
DefuseWorld {
|
| 147 |
+
state,
|
| 148 |
+
clients,
|
| 149 |
+
bombs,
|
| 150 |
+
actor_point: [0; 3],
|
| 151 |
+
},
|
| 152 |
+
FullDefuseInput {
|
| 153 |
+
base,
|
| 154 |
+
entity: w[10] != 0,
|
| 155 |
+
client: w[11] != 0,
|
| 156 |
+
enabled_a: w[12],
|
| 157 |
+
enabled_b: w[13],
|
| 158 |
+
start: w[14],
|
| 159 |
+
delay: w[15],
|
| 160 |
+
selector: w[27],
|
| 161 |
+
count: w[25] as usize,
|
| 162 |
+
},
|
| 163 |
+
)
|
| 164 |
+
}
|
| 165 |
+
fn full_fields(s: DefuseState, o: &DefuseOutcome) -> String {
|
| 166 |
+
format!(
|
| 167 |
+
r#"{},"lookups":[{}],"distances":{},"missing":{}"#,
|
| 168 |
+
fields(s, &o.effects),
|
| 169 |
+
o.lookups
|
| 170 |
+
.iter()
|
| 171 |
+
.map(|v| v.map(|i| i.to_string()).unwrap_or("null".into()))
|
| 172 |
+
.collect::<Vec<_>>()
|
| 173 |
+
.join(","),
|
| 174 |
+
list(&o.distances),
|
| 175 |
+
o.missing
|
| 176 |
+
)
|
| 177 |
+
}
|
| 178 |
+
fn full(w: &[u32]) -> String {
|
| 179 |
+
let (mut world, i) = decode_full(w);
|
| 180 |
+
let o = process_defuse(&mut world, i).unwrap();
|
| 181 |
+
format!("{{{}}}", full_fields(world.state, &o))
|
| 182 |
+
}
|
crates/urt-gameplay/src/defuse.rs
CHANGED
|
@@ -327,3 +327,194 @@ mod finish_tests {
|
|
| 327 |
assert_eq!(s.complete, 1);
|
| 328 |
}
|
| 329 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 327 |
assert_eq!(s.complete, 1);
|
| 328 |
}
|
| 329 |
}
|
| 330 |
+
|
| 331 |
+
pub const BOMB_CLASS_HASH: u32 = (-82007226i32) as u32;
|
| 332 |
+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
| 333 |
+
pub struct DefuseBomb {
|
| 334 |
+
pub in_use: bool,
|
| 335 |
+
pub has_class: bool,
|
| 336 |
+
pub hash: u32,
|
| 337 |
+
pub point: [u32; 3],
|
| 338 |
+
}
|
| 339 |
+
#[derive(Clone, Debug, PartialEq, Eq)]
|
| 340 |
+
pub struct DefuseWorld {
|
| 341 |
+
pub state: DefuseState,
|
| 342 |
+
pub clients: Vec<crate::move_callbacks::ObjectiveClient>,
|
| 343 |
+
pub bombs: Vec<DefuseBomb>,
|
| 344 |
+
pub actor_point: [u32; 3],
|
| 345 |
+
}
|
| 346 |
+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
| 347 |
+
pub struct FullDefuseInput {
|
| 348 |
+
pub base: DefuseInput,
|
| 349 |
+
pub entity: bool,
|
| 350 |
+
pub client: bool,
|
| 351 |
+
pub enabled_a: u32,
|
| 352 |
+
pub enabled_b: u32,
|
| 353 |
+
pub start: u32,
|
| 354 |
+
pub delay: u32,
|
| 355 |
+
pub selector: u32,
|
| 356 |
+
pub count: usize,
|
| 357 |
+
}
|
| 358 |
+
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
| 359 |
+
pub struct DefuseOutcome {
|
| 360 |
+
pub effects: Vec<DefuseEffect>,
|
| 361 |
+
pub lookups: Vec<Option<usize>>,
|
| 362 |
+
pub distances: Vec<u32>,
|
| 363 |
+
pub missing: bool,
|
| 364 |
+
}
|
| 365 |
+
fn distance(a: [u32; 3], b: [u32; 3]) -> Result<f32, Error> {
|
| 366 |
+
let mut d = [0.; 3];
|
| 367 |
+
for j in 0..3 {
|
| 368 |
+
let a = f32::from_bits(a[j]);
|
| 369 |
+
let b = f32::from_bits(b[j]);
|
| 370 |
+
if !a.is_finite() || !b.is_finite() {
|
| 371 |
+
return Err(Error::NonFinite);
|
| 372 |
+
}
|
| 373 |
+
d[j] = b - a;
|
| 374 |
+
}
|
| 375 |
+
let square = (d[0] * d[0] + d[1] * d[1]) + d[2] * d[2];
|
| 376 |
+
if !square.is_finite() {
|
| 377 |
+
return Err(Error::NonFinite);
|
| 378 |
+
}
|
| 379 |
+
Ok(square.sqrt())
|
| 380 |
+
}
|
| 381 |
+
/// Original153419 through the void-return/fatal boundary. Lookup uses supplied
|
| 382 |
+
/// entity identity and precomputed class hash. Missing bomb returns a diagnostic
|
| 383 |
+
/// boundary without invoking fatal2319. Native actions remain effect plans.
|
| 384 |
+
pub fn process_defuse(
|
| 385 |
+
world: &mut DefuseWorld,
|
| 386 |
+
input: FullDefuseInput,
|
| 387 |
+
) -> Result<DefuseOutcome, Error> {
|
| 388 |
+
use crate::move_callbacks::{Mode8EligibilityInput, mode8_eligible};
|
| 389 |
+
let mut out = DefuseOutcome::default();
|
| 390 |
+
let mut clients = world.clients.clone();
|
| 391 |
+
if let Some(own) = clients.get_mut(input.base.slot as usize) {
|
| 392 |
+
own.busy = world.state.busy;
|
| 393 |
+
}
|
| 394 |
+
if !mode8_eligible(
|
| 395 |
+
&clients,
|
| 396 |
+
Mode8EligibilityInput {
|
| 397 |
+
entity: input.entity,
|
| 398 |
+
client: input.client,
|
| 399 |
+
enabled_a: input.enabled_a,
|
| 400 |
+
enabled_b: input.enabled_b,
|
| 401 |
+
tick: input.base.tick,
|
| 402 |
+
start: input.start,
|
| 403 |
+
delay: input.delay,
|
| 404 |
+
complete: world.state.complete,
|
| 405 |
+
selector: input.selector,
|
| 406 |
+
self_index: input.base.slot as usize,
|
| 407 |
+
count: input.count,
|
| 408 |
+
},
|
| 409 |
+
)? {
|
| 410 |
+
return Ok(out);
|
| 411 |
+
}
|
| 412 |
+
let bomb = world
|
| 413 |
+
.bombs
|
| 414 |
+
.iter()
|
| 415 |
+
.position(|b| b.in_use && b.has_class && b.hash == BOMB_CLASS_HASH);
|
| 416 |
+
out.lookups.push(bomb);
|
| 417 |
+
let Some(index) = bomb else {
|
| 418 |
+
out.missing = true;
|
| 419 |
+
return Ok(out);
|
| 420 |
+
};
|
| 421 |
+
let d = distance(world.actor_point, world.bombs[index].point)?;
|
| 422 |
+
out.distances.push(d.to_bits());
|
| 423 |
+
let mut next = world.state;
|
| 424 |
+
out.effects = if d >= 75. || next.flags & 32768 == 0 {
|
| 425 |
+
cancel_defuse(&mut next, input.base)?
|
| 426 |
+
} else {
|
| 427 |
+
finish_defuse(&mut next, input.base)?
|
| 428 |
+
};
|
| 429 |
+
clients[input.base.slot as usize].busy = next.busy;
|
| 430 |
+
world.clients = clients;
|
| 431 |
+
world.state = next;
|
| 432 |
+
Ok(out)
|
| 433 |
+
}
|
| 434 |
+
#[cfg(test)]
|
| 435 |
+
mod full_tests {
|
| 436 |
+
use super::*;
|
| 437 |
+
use crate::move_callbacks::ObjectiveClient;
|
| 438 |
+
fn world() -> DefuseWorld {
|
| 439 |
+
DefuseWorld {
|
| 440 |
+
state: DefuseState {
|
| 441 |
+
flags: 32768,
|
| 442 |
+
busy: 0,
|
| 443 |
+
clock: 0,
|
| 444 |
+
flags104: 0,
|
| 445 |
+
complete: 0,
|
| 446 |
+
animation: 99,
|
| 447 |
+
weapon: 11,
|
| 448 |
+
event: [7, 1],
|
| 449 |
+
},
|
| 450 |
+
clients: vec![
|
| 451 |
+
ObjectiveClient {
|
| 452 |
+
connection: 2,
|
| 453 |
+
health: 1,
|
| 454 |
+
follow: 0,
|
| 455 |
+
team: 1,
|
| 456 |
+
busy: 0
|
| 457 |
+
};
|
| 458 |
+
3
|
| 459 |
+
],
|
| 460 |
+
bombs: vec![
|
| 461 |
+
DefuseBomb {
|
| 462 |
+
in_use: false,
|
| 463 |
+
has_class: true,
|
| 464 |
+
hash: BOMB_CLASS_HASH,
|
| 465 |
+
point: [0; 3],
|
| 466 |
+
},
|
| 467 |
+
DefuseBomb {
|
| 468 |
+
in_use: true,
|
| 469 |
+
has_class: true,
|
| 470 |
+
hash: BOMB_CLASS_HASH,
|
| 471 |
+
point: [75f32.to_bits(), 0, 0],
|
| 472 |
+
},
|
| 473 |
+
],
|
| 474 |
+
actor_point: [0; 3],
|
| 475 |
+
}
|
| 476 |
+
}
|
| 477 |
+
fn input() -> FullDefuseInput {
|
| 478 |
+
FullDefuseInput {
|
| 479 |
+
base: DefuseInput {
|
| 480 |
+
tick: 10000,
|
| 481 |
+
duration: 5,
|
| 482 |
+
index: 3,
|
| 483 |
+
slot: 2,
|
| 484 |
+
},
|
| 485 |
+
entity: true,
|
| 486 |
+
client: true,
|
| 487 |
+
enabled_a: 1,
|
| 488 |
+
enabled_b: 1,
|
| 489 |
+
start: 8000,
|
| 490 |
+
delay: 1,
|
| 491 |
+
selector: 2,
|
| 492 |
+
count: 3,
|
| 493 |
+
}
|
| 494 |
+
}
|
| 495 |
+
#[test]
|
| 496 |
+
fn lookup_skips_decoys_and_radius_equality_does_not_start() {
|
| 497 |
+
let mut w = world();
|
| 498 |
+
let o = process_defuse(&mut w, input()).unwrap();
|
| 499 |
+
assert_eq!(o.lookups, vec![Some(1)]);
|
| 500 |
+
assert!(o.effects.is_empty());
|
| 501 |
+
assert_eq!(w.state.busy, 0);
|
| 502 |
+
w.bombs[1].point[0] = 74f32.to_bits();
|
| 503 |
+
let o = process_defuse(&mut w, input()).unwrap();
|
| 504 |
+
assert_eq!(w.state.busy, 1);
|
| 505 |
+
assert_eq!(o.effects.len(), 2);
|
| 506 |
+
}
|
| 507 |
+
#[test]
|
| 508 |
+
fn missing_bomb_and_bad_distance_preserve_state() {
|
| 509 |
+
let mut w = world();
|
| 510 |
+
w.bombs[1].has_class = false;
|
| 511 |
+
let old = w.clone();
|
| 512 |
+
assert!(process_defuse(&mut w, input()).unwrap().missing);
|
| 513 |
+
assert_eq!(w, old);
|
| 514 |
+
w.bombs[1].has_class = true;
|
| 515 |
+
w.bombs[1].point[0] = f32::MAX.to_bits();
|
| 516 |
+
let old = w.clone();
|
| 517 |
+
assert!(process_defuse(&mut w, input()).is_err());
|
| 518 |
+
assert_eq!(w, old);
|
| 519 |
+
}
|
| 520 |
+
}
|
docs/FINDINGS.md
CHANGED
|
@@ -1979,3 +1979,5 @@ RE-1018: Thirty-two source cases recover active completion when signed wrapped e
|
|
| 1979 |
RE-1019: Rust matches32 active source completion cases with signed wrapped elapsed/duration arithmetic and equality completion. The original log/event84/teleport/award order is preserved as plans. Inactive selected state reuses start policy; waiting avoids unused index evaluation, while ready invalid client indices reject before mutation. Unit cases cover exact and wrapped completion, high-bit preservation and waiting/error atomicity. [Evidence](research/RUST_DEFUSE_FINISH.md).
|
| 1980 |
|
| 1981 |
RE-1020: Ninety-six source scenarios connect original admission/teammate scan to actual276433 bomb hash lookup and65655 distance, then start/wait/cancel/complete transitions. Three selectors, radius75 equality, zero/64 teammate counts, inactive/classless/hash-mismatched decoys, wrapped time, completed latch and missing bomb are covered. Missing lookup stops before original2319 fatal handler. Native sqrt is modeled; event/native effect feedback is captured or supplied, not executed. [Evidence](research/QVM_DEFUSE_COMPLETE.md).
|
|
|
|
|
|
|
|
|
| 1979 |
RE-1019: Rust matches32 active source completion cases with signed wrapped elapsed/duration arithmetic and equality completion. The original log/event84/teleport/award order is preserved as plans. Inactive selected state reuses start policy; waiting avoids unused index evaluation, while ready invalid client indices reject before mutation. Unit cases cover exact and wrapped completion, high-bit preservation and waiting/error atomicity. [Evidence](research/RUST_DEFUSE_FINISH.md).
|
| 1980 |
|
| 1981 |
RE-1020: Ninety-six source scenarios connect original admission/teammate scan to actual276433 bomb hash lookup and65655 distance, then start/wait/cancel/complete transitions. Three selectors, radius75 equality, zero/64 teammate counts, inactive/classless/hash-mismatched decoys, wrapped time, completed latch and missing bomb are covered. Missing lookup stops before original2319 fatal handler. Native sqrt is modeled; event/native effect feedback is captured or supplied, not executed. [Evidence](research/QVM_DEFUSE_COMPLETE.md).
|
| 1982 |
+
|
| 1983 |
+
RE-1021: Rust composes verified eligibility, first eligible bomb hash lookup, finite f32 distance and start/cancel/finish across96 source cases. Missing bomb returns the source fatal boundary diagnostic without inventing subsequent behavior. Radius equality does not start defusing; decoy identity and selector semantics match source. State and effect plans stage before commit, with missing/invalid geometry unit tests preserving prior state. Native effects remain captured plans. [Evidence](research/RUST_DEFUSE_COMPLETE.md).
|
docs/PROVISIONAL_TODO.md
CHANGED
|
@@ -2038,3 +2038,5 @@ RE-974–983 completed routing, command rewrite, inactivity, movement-parameter
|
|
| 2038 |
- [x] RE-1019 Port timed bomb-defuse completion and captured effects ([evidence](research/RUST_DEFUSE_FINISH.md)).
|
| 2039 |
|
| 2040 |
- [x] RE-1020 Recover complete mode8 bomb-defuse callback ([evidence](research/QVM_DEFUSE_COMPLETE.md)).
|
|
|
|
|
|
|
|
|
| 2038 |
- [x] RE-1019 Port timed bomb-defuse completion and captured effects ([evidence](research/RUST_DEFUSE_FINISH.md)).
|
| 2039 |
|
| 2040 |
- [x] RE-1020 Recover complete mode8 bomb-defuse callback ([evidence](research/QVM_DEFUSE_COMPLETE.md)).
|
| 2041 |
+
|
| 2042 |
+
- [x] RE-1021 Port complete bomb-defuse lookup and state policy ([evidence](research/RUST_DEFUSE_COMPLETE.md)).
|
docs/RESEARCH_LEDGER.md
CHANGED
|
@@ -2755,3 +2755,7 @@ Rust matches32 active source completion cases with signed wrapped elapsed/durati
|
|
| 2755 |
## 2026-10-29 — RE-1020 Recover complete mode8 bomb-defuse callback
|
| 2756 |
|
| 2757 |
Ninety-six source scenarios connect original admission/teammate scan to actual276433 bomb hash lookup and65655 distance, then start/wait/cancel/complete transitions. Three selectors, radius75 equality, zero/64 teammate counts, inactive/classless/hash-mismatched decoys, wrapped time, completed latch and missing bomb are covered. Missing lookup stops before original2319 fatal handler. Native sqrt is modeled; event/native effect feedback is captured or supplied, not executed. Focused `cargo xtask test-group qvm-defuse-complete` passes with deterministic report regeneration and selected regressions. Full suite deferred to task ten. Original assets unchanged; Python uses the flake-declared Nix runtime.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2755 |
## 2026-10-29 — RE-1020 Recover complete mode8 bomb-defuse callback
|
| 2756 |
|
| 2757 |
Ninety-six source scenarios connect original admission/teammate scan to actual276433 bomb hash lookup and65655 distance, then start/wait/cancel/complete transitions. Three selectors, radius75 equality, zero/64 teammate counts, inactive/classless/hash-mismatched decoys, wrapped time, completed latch and missing bomb are covered. Missing lookup stops before original2319 fatal handler. Native sqrt is modeled; event/native effect feedback is captured or supplied, not executed. Focused `cargo xtask test-group qvm-defuse-complete` passes with deterministic report regeneration and selected regressions. Full suite deferred to task ten. Original assets unchanged; Python uses the flake-declared Nix runtime.
|
| 2758 |
+
|
| 2759 |
+
## 2026-10-29 — RE-1021 Port complete bomb-defuse lookup and state policy
|
| 2760 |
+
|
| 2761 |
+
Rust composes verified eligibility, first eligible bomb hash lookup, finite f32 distance and start/cancel/finish across96 source cases. Missing bomb returns the source fatal boundary diagnostic without inventing subsequent behavior. Radius equality does not start defusing; decoy identity and selector semantics match source. State and effect plans stage before commit, with missing/invalid geometry unit tests preserving prior state. Native effects remain captured plans. Focused `cargo xtask test-group rust-defuse-complete` passes with deterministic report regeneration and selected regressions. Full suite deferred to task ten. Original assets unchanged; Python uses the flake-declared Nix runtime.
|
docs/REVERSE_ENGINEERING_TODO.md
CHANGED
|
@@ -2075,3 +2075,5 @@ RE-974–983 recover routing, command rewriting, inactivity and composed setup t
|
|
| 2075 |
- [x] RE-1019 Port timed bomb-defuse completion and captured effects ([evidence](research/RUST_DEFUSE_FINISH.md)).
|
| 2076 |
|
| 2077 |
- [x] RE-1020 Recover complete mode8 bomb-defuse callback ([evidence](research/QVM_DEFUSE_COMPLETE.md)).
|
|
|
|
|
|
|
|
|
| 2075 |
- [x] RE-1019 Port timed bomb-defuse completion and captured effects ([evidence](research/RUST_DEFUSE_FINISH.md)).
|
| 2076 |
|
| 2077 |
- [x] RE-1020 Recover complete mode8 bomb-defuse callback ([evidence](research/QVM_DEFUSE_COMPLETE.md)).
|
| 2078 |
+
|
| 2079 |
+
- [x] RE-1021 Port complete bomb-defuse lookup and state policy ([evidence](research/RUST_DEFUSE_COMPLETE.md)).
|
docs/research/README.md
CHANGED
|
@@ -1607,3 +1607,5 @@ The extension collector adds five focused tests for native tail calls, command-r
|
|
| 1607 |
- [RE-1019: Port timed bomb-defuse completion and captured effects](RUST_DEFUSE_FINISH.md).
|
| 1608 |
|
| 1609 |
- [RE-1020: Recover complete mode8 bomb-defuse callback](QVM_DEFUSE_COMPLETE.md).
|
|
|
|
|
|
|
|
|
| 1607 |
- [RE-1019: Port timed bomb-defuse completion and captured effects](RUST_DEFUSE_FINISH.md).
|
| 1608 |
|
| 1609 |
- [RE-1020: Recover complete mode8 bomb-defuse callback](QVM_DEFUSE_COMPLETE.md).
|
| 1610 |
+
|
| 1611 |
+
- [RE-1021: Port complete bomb-defuse lookup and state policy](RUST_DEFUSE_COMPLETE.md).
|
docs/research/RUST_DEFUSE_COMPLETE.md
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Port complete bomb-defuse lookup and state policy
|
| 2 |
+
|
| 3 |
+
RE-1021. Focused validation: `cargo xtask test-group rust-defuse-complete`. [Machine report](rust-defuse-complete.json).
|
| 4 |
+
|
| 5 |
+
Rust composes verified eligibility, first eligible bomb hash lookup, finite f32 distance and start/cancel/finish across96 source cases. Missing bomb returns the source fatal boundary diagnostic without inventing subsequent behavior. Radius equality does not start defusing; decoy identity and selector semantics match source. State and effect plans stage before commit, with missing/invalid geometry unit tests preserving prior state. Native effects remain captured plans.
|
docs/research/rust-defuse-complete.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
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": 277,
|
| 2989 |
"source_report": "docs/research/qvm-health-impulse.json",
|
| 2990 |
"source_report_sha256": "625fd8064004ea6dbdf398d51d0af844d9c635ff04c0a0790886dddb6ab81e71"
|
| 2991 |
}
|
scripts/analyze_rust_defuse_complete.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Compare recovered Rust policy with original QVM fixtures."""
|
| 2 |
+
import hashlib,json,os,subprocess
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
from analyze_qvm import write_json
|
| 5 |
+
from rust_lifecycle_support import result
|
| 6 |
+
|
| 7 |
+
def main():
|
| 8 |
+
subprocess.run(['cargo','build','--offline','--package','urt-gameplay','--bin','urt-defuse'],check=True,capture_output=True)
|
| 9 |
+
binary=Path(os.environ.get('CARGO_TARGET_DIR','target'))/'debug'/'urt-defuse'
|
| 10 |
+
path=Path('docs/research/qvm-defuse-complete.json');data=path.read_bytes();probes=[]
|
| 11 |
+
for p in json.loads(data)['probes']:
|
| 12 |
+
probes.append(dict(source={k:p[k] for k in ['flags', 'flags104', 'busy', 'clock', 'complete', 'animation', 'weapon', 'event', 'guards', 'events', 'lookups', 'distances', 'missing']},rust=result(binary,'full',encode(p['input']))))
|
| 13 |
+
write_json(Path('docs/research/rust-defuse-complete.json'),dict(source_report=str(path),source_report_sha256=hashlib.sha256(data).hexdigest(),probes=probes,limits='Finite source/Rust contract parity; native time/file/network replies remain supplied or captured.'))
|
| 14 |
+
|
| 15 |
+
from analyze_rust_defuse_begin import encode as base_encode
|
| 16 |
+
def encode(a):
|
| 17 |
+
from analyze_qvm_acceleration import word
|
| 18 |
+
return base_encode(a)+[a['entity'],a['client'],a['enabled_a'],a['enabled_b'],a['start'],a['delay'],a['connection'],a['health'],a['follow'],a['team'],a['other_busy'],a['other_connection'],a['other_health'],a['other_follow'],a['other_team'],a['count'],a['busy_slot'],a['selector'],word(a['distance']),a['bomb_first'],a['bomb_missing']]
|
| 19 |
+
if __name__=='__main__':main()
|
scripts/test_rust_defuse_complete.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import hashlib,json,unittest
|
| 2 |
+
from pathlib import Path
|
| 3 |
+
class PortTests(unittest.TestCase):
|
| 4 |
+
def test_original_parity(self):
|
| 5 |
+
r=json.loads(Path('docs/research/rust-defuse-complete.json').read_text());self.assertGreaterEqual(len(r['probes']),12)
|
| 6 |
+
for p in r['probes']:self.assertEqual(p['source'],p['rust'])
|
| 7 |
+
def test_identity(self):
|
| 8 |
+
r=json.loads(Path('docs/research/rust-defuse-complete.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
|
@@ -4246,3 +4246,35 @@ report scripts/analyze_qvm_defuse_complete.py docs/research/qvm-defuse-complete.
|
|
| 4246 |
edge docs/research/qvm-defuse-complete.json scripts/test_qvm_defuse_complete.py
|
| 4247 |
group qvm-defuse-complete scripts/analyze_qvm_defuse_complete.py
|
| 4248 |
edge scripts/analyze_qvm_defuse_complete.py scripts/analyze_qvm_resumer_audit.py
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4246 |
edge docs/research/qvm-defuse-complete.json scripts/test_qvm_defuse_complete.py
|
| 4247 |
group qvm-defuse-complete scripts/analyze_qvm_defuse_complete.py
|
| 4248 |
edge scripts/analyze_qvm_defuse_complete.py scripts/analyze_qvm_resumer_audit.py
|
| 4249 |
+
report scripts/analyze_rust_defuse_complete.py docs/research/rust-defuse-complete.json
|
| 4250 |
+
edge docs/research/rust-defuse-complete.json scripts/test_rust_defuse_complete.py
|
| 4251 |
+
group rust-defuse-complete scripts/analyze_rust_defuse_complete.py
|
| 4252 |
+
edge scripts/analyze_rust_defuse_complete.py scripts/analyze_qvm_resumer_audit.py
|
| 4253 |
+
edge Cargo.toml scripts/analyze_rust_defuse_complete.py
|
| 4254 |
+
edge Cargo.lock scripts/analyze_rust_defuse_complete.py
|
| 4255 |
+
edge crates/urt-gameplay/Cargo.toml scripts/analyze_rust_defuse_complete.py
|
| 4256 |
+
edge crates/urt-gameplay/src/lib.rs scripts/analyze_rust_defuse_complete.py
|
| 4257 |
+
edge crates/urt-gameplay/src/filter_loading.rs scripts/analyze_rust_defuse_complete.py
|
| 4258 |
+
edge crates/urt-gameplay/src/filter_lifecycle.rs scripts/analyze_rust_defuse_complete.py
|
| 4259 |
+
edge crates/urt-gameplay/src/native_policy.rs scripts/analyze_rust_defuse_complete.py
|
| 4260 |
+
edge crates/urt-gameplay/src/bin/urt-filter-loading.rs scripts/analyze_rust_defuse_complete.py
|
| 4261 |
+
edge crates/urt-gameplay/src/qvm_format.rs scripts/analyze_rust_defuse_complete.py
|
| 4262 |
+
edge docs/research/qvm-defuse-complete.json scripts/analyze_rust_defuse_complete.py
|
| 4263 |
+
group rust-defuse-complete scripts/analyze_rust_health_impulse.py
|
| 4264 |
+
group gameplay scripts/analyze_rust_defuse_complete.py
|
| 4265 |
+
edge crates/urt-gameplay/src/filter_commands.rs scripts/analyze_rust_defuse_complete.py
|
| 4266 |
+
edge crates/urt-gameplay/src/bin/urt-filter-commands.rs scripts/analyze_rust_defuse_complete.py
|
| 4267 |
+
edge crates/urt-gameplay/src/client_timer.rs scripts/analyze_rust_defuse_complete.py
|
| 4268 |
+
edge crates/urt-gameplay/src/bin/urt-client-timer.rs scripts/analyze_rust_defuse_complete.py
|
| 4269 |
+
edge crates/urt-gameplay/src/client_processing.rs scripts/analyze_rust_defuse_complete.py
|
| 4270 |
+
edge crates/urt-gameplay/src/bin/urt-client-processing.rs scripts/analyze_rust_defuse_complete.py
|
| 4271 |
+
edge crates/urt-gameplay/src/client_move_context.rs scripts/analyze_rust_defuse_complete.py
|
| 4272 |
+
edge crates/urt-gameplay/src/bin/urt-client-move-context.rs scripts/analyze_rust_defuse_complete.py
|
| 4273 |
+
edge crates/urt-gameplay/src/move_callbacks.rs scripts/analyze_rust_defuse_complete.py
|
| 4274 |
+
edge crates/urt-gameplay/src/bin/urt-move-callbacks.rs scripts/analyze_rust_defuse_complete.py
|
| 4275 |
+
edge crates/urt-gameplay/src/assistance.rs scripts/analyze_rust_defuse_complete.py
|
| 4276 |
+
edge crates/urt-gameplay/src/bin/urt-assistance.rs scripts/analyze_rust_defuse_complete.py
|
| 4277 |
+
edge crates/urt-gameplay/src/move_callbacks.rs scripts/analyze_rust_defuse_complete.py
|
| 4278 |
+
edge crates/urt-gameplay/src/defuse.rs scripts/analyze_rust_defuse_complete.py
|
| 4279 |
+
edge crates/urt-gameplay/src/bin/urt-defuse.rs scripts/analyze_rust_defuse_complete.py
|
| 4280 |
+
edge crates/urt-gameplay/src/move_callbacks.rs scripts/analyze_rust_defuse_complete.py
|
xtask/src/lib.rs
CHANGED
|
@@ -1158,6 +1158,8 @@ mod integration_tests {
|
|
| 1158 |
("move_callbacks", "defuse_cancel"),
|
| 1159 |
("defuse", "defuse_finish"),
|
| 1160 |
("move_callbacks", "defuse_finish"),
|
|
|
|
|
|
|
| 1161 |
("client_move_context", "client_move_scratch"),
|
| 1162 |
("client_processing", "client_routing"),
|
| 1163 |
("client_timer", "client_routing"),
|
|
@@ -1389,6 +1391,7 @@ mod integration_tests {
|
|
| 1389 |
("qvm-defuse-begin", "defuse_begin"),
|
| 1390 |
("qvm-defuse-cancel", "defuse_cancel"),
|
| 1391 |
("qvm-defuse-finish", "defuse_finish"),
|
|
|
|
| 1392 |
("qvm-client-move-scratch", "client_move_scratch"),
|
| 1393 |
("qvm-client-routing", "client_routing"),
|
| 1394 |
("qvm-client-command-rewrite", "client_command_rewrite"),
|
|
|
|
| 1158 |
("move_callbacks", "defuse_cancel"),
|
| 1159 |
("defuse", "defuse_finish"),
|
| 1160 |
("move_callbacks", "defuse_finish"),
|
| 1161 |
+
("defuse", "defuse_complete"),
|
| 1162 |
+
("move_callbacks", "defuse_complete"),
|
| 1163 |
("client_move_context", "client_move_scratch"),
|
| 1164 |
("client_processing", "client_routing"),
|
| 1165 |
("client_timer", "client_routing"),
|
|
|
|
| 1391 |
("qvm-defuse-begin", "defuse_begin"),
|
| 1392 |
("qvm-defuse-cancel", "defuse_cancel"),
|
| 1393 |
("qvm-defuse-finish", "defuse_finish"),
|
| 1394 |
+
("qvm-defuse-complete", "defuse_complete"),
|
| 1395 |
("qvm-client-move-scratch", "client_move_scratch"),
|
| 1396 |
("qvm-client-routing", "client_routing"),
|
| 1397 |
("qvm-client-command-rewrite", "client_command_rewrite"),
|