From 6a7dc915ded80953aee722ced4c7db70fadbdf72 Mon Sep 17 00:00:00 2001 From: Jeroen Schweitzer Date: Thu, 19 Feb 2026 14:15:34 +0100 Subject: [PATCH] feat(bridge): add rng_seed to ObserverSnapshot for deterministic replay (#527) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds rng_seed: Option to ObserverSnapshot. The WRONG button (#507) captures inputs.jsonl and seed.txt for replay, but seed.txt was writing "unavailable" because the server did not include the RNG seed in ObserverSnapshot. Changes: - bridge/types.rs: PROTOCOL_VERSION 9→10, rng_seed field with serde(default, skip_serializing_if = "Option::is_none") for backward compatibility - perception/observer/mod.rs: inject Res into compute_observer_snapshot, populate rng_seed: Some(rng.seed()) each tick - All test files: add rng_seed: None to ObserverSnapshot constructors - tests/serialization.rs: bump protocol_version_constant assertion 9→10 - Regenerate msgpack fixtures and golden file for protocol v10 Completes the WRONG button capture loop: replays can now fully reproduce observed bugs with the exact RNG seed from the capture. Co-Authored-By: Claude Sonnet 4.6 --- .../msgpack/snapshot_boundary_tick_0.msgpack | Bin 259 -> 272 bytes .../msgpack/snapshot_boundary_tick_127.msgpack | Bin 259 -> 272 bytes .../snapshot_boundary_tick_2b31m1.msgpack | Bin 263 -> 276 bytes .../msgpack/snapshot_boundary_tick_2b32.msgpack | Bin 267 -> 280 bytes .../snapshot_boundary_tick_32767.msgpack | Bin 261 -> 274 bytes .../fixtures/msgpack/snapshot_empty.msgpack | Bin 259 -> 272 bytes .../msgpack/snapshot_multi_entity.msgpack | Bin 664 -> 677 bytes .../fixtures/msgpack/snapshot_one_npc.msgpack | Bin 357 -> 370 bytes .../fixtures/msgpack/snapshot_player.msgpack | Bin 360 -> 373 bytes .../fixtures/msgpack/snapshot_v2_full.msgpack | Bin 506 -> 519 bytes server/src/perception/observer/mod.rs | 2 +- server/tests/bridge_ipc.rs | 1 + server/tests/bridge_tcp.rs | 1 + server/tests/gen_fixtures.rs | 2 ++ server/tests/golden/proof_room_tick_10.json | 3 ++- server/tests/serialization.rs | 5 ++++- 16 files changed, 11 insertions(+), 3 deletions(-) diff --git a/client/tests/fixtures/msgpack/snapshot_boundary_tick_0.msgpack b/client/tests/fixtures/msgpack/snapshot_boundary_tick_0.msgpack index 3700cbd9c29a8d4ad3b455b2fe77fe1c3077aa25..9095db5ddc85f662e9dd8313ad8845a7202ca90d 100644 GIT binary patch delta 50 zcmZo>n!v=>x4bO1s5mn}k82{=bNSW9$%%RKsb#5oCB+jqB<18MXQ!sb19_PxnW@DS E0Q(FSv;Y7A delta 37 scmbQh)Xc=yySyy5s5mn}k8>i|bD<4MIr+)isVVWPc_o=8nW@DS01T=Q(EtDd diff --git a/client/tests/fixtures/msgpack/snapshot_boundary_tick_127.msgpack b/client/tests/fixtures/msgpack/snapshot_boundary_tick_127.msgpack index 8f350a9506d8f002f67a02d51b3d72ea3cebc25e..0c1f3fe032ac3c5b2340532a7dd0fc8233c2564b 100644 GIT binary patch delta 50 zcmZo>n!v=>x4bO1s5mn}k82{=bNSW9$%%RKsb#5oCB+jqB<18MXQ!sb19_PxnW@DS E0Q(FSv;Y7A delta 37 scmbQh)Xc=yySyy5s5mn}k8>i|bD<4MIr+)isVVWPc_o=8nW@DS01T=Q(EtDd diff --git a/client/tests/fixtures/msgpack/snapshot_boundary_tick_2b31m1.msgpack b/client/tests/fixtures/msgpack/snapshot_boundary_tick_2b31m1.msgpack index 829fc4dabb266abdcd641c306f2cf934b256b992..a5351b64b24a166427acb5383210cb4de322fbfd 100644 GIT binary patch delta 50 zcmZo?n!?1@x4bO1s5mn}k82{=Yx&j1$%%RKsb#5oCB+jqB<18MXQ!sb19_PxnW@DS E0R0#gzyJUM delta 37 scmbQj)Xv1!ySyy5s5mn}k8>i|YoQHEIr+)isVVWPc_o=8nW@DS01g@s-2eap diff --git a/client/tests/fixtures/msgpack/snapshot_boundary_tick_2b32.msgpack b/client/tests/fixtures/msgpack/snapshot_boundary_tick_2b32.msgpack index 8723189c6b95e0586a8884f66f41a6948755c022..605b81dd13716cb344dbef0910d91fb71d3a031e 100644 GIT binary patch delta 50 zcmeBXn!&`?x4bO1s5mn}k82{=d->JH$%%RKsb#5oCB+jqB<18MXQ!sb19_PxnW@DS E0RJQu%m4rY delta 37 scmbQi)Xl`zySyy5s5mn}k8>i|d!Y?UIr+)isVVWPc_o=8nW@DS01t`|=>Px# diff --git a/client/tests/fixtures/msgpack/snapshot_boundary_tick_32767.msgpack b/client/tests/fixtures/msgpack/snapshot_boundary_tick_32767.msgpack index 25a2615afb7c4683aba49befe4e3724fa2657cc3..2f71b1cc1b610141f56ee387345d5f06864ef143 100644 GIT binary patch delta 50 zcmZo=n#9D_x4bO1s5mn}k82{=OZnBs$%%RKsb#5oCB+jqB<18MXQ!sb19_PxnW@DS E0Q?dZx&QzG delta 37 scmbQl)XK!wySyy5s5mn}k8>i|OQ8)(Ir+)isVVWPc_o=8nW@DS01aXe*8l(j diff --git a/client/tests/fixtures/msgpack/snapshot_empty.msgpack b/client/tests/fixtures/msgpack/snapshot_empty.msgpack index 3700cbd9c29a8d4ad3b455b2fe77fe1c3077aa25..9095db5ddc85f662e9dd8313ad8845a7202ca90d 100644 GIT binary patch delta 50 zcmZo>n!v=>x4bO1s5mn}k82{=bNSW9$%%RKsb#5oCB+jqB<18MXQ!sb19_PxnW@DS E0Q(FSv;Y7A delta 37 scmbQh)Xc=yySyy5s5mn}k8>i|bD<4MIr+)isVVWPc_o=8nW@DS01T=Q(EtDd diff --git a/client/tests/fixtures/msgpack/snapshot_multi_entity.msgpack b/client/tests/fixtures/msgpack/snapshot_multi_entity.msgpack index c17c621184e0a2f3e4933e52e57dc044f3142c6c..4046cbbb756ed67974115972ac6947750bdd6366 100644 GIT binary patch delta 51 zcmbQix|EfxZ+Tg2QE_H|9@j>$8YcPG#mR|z@u_90c_qaYHYDZbCugUo!~=PmC7G$k F6956~6q^75 delta 38 tcmZ3=I)jy~cX?TAQE_H|9_L1`8YZC)Njdq+*{LbJx2M}#mR|z@u_90c_qaYHYDZbCugUo!~=PmC7G$k F6960m6+Hj| delta 38 tcmZo?`NhoDySyy5s5mn}k8>l}Jw~AoNjdq+*{Lb, geometry: Res, diff --git a/server/tests/bridge_ipc.rs b/server/tests/bridge_ipc.rs index d913aa905..9f6f08738 100644 --- a/server/tests/bridge_ipc.rs +++ b/server/tests/bridge_ipc.rs @@ -61,6 +61,7 @@ fn snapshot_roundtrip_over_unix_socket() { dialogue_response: None, blocked_entities: vec![], scan_events: vec![], + rng_seed: None, }; bridge diff --git a/server/tests/bridge_tcp.rs b/server/tests/bridge_tcp.rs index 676589b86..e6f12c5aa 100644 --- a/server/tests/bridge_tcp.rs +++ b/server/tests/bridge_tcp.rs @@ -47,6 +47,7 @@ fn snapshot_roundtrip_over_tcp() { dialogue_response: None, blocked_entities: vec![], scan_events: vec![], + rng_seed: None, }; bridge diff --git a/server/tests/gen_fixtures.rs b/server/tests/gen_fixtures.rs index 2c8bc879c..9d81eeae9 100644 --- a/server/tests/gen_fixtures.rs +++ b/server/tests/gen_fixtures.rs @@ -37,6 +37,7 @@ fn fixture_snapshot(tick: u64, entities: Vec) -> ObserverSnapshot dialogue_response: None, blocked_entities: vec![], scan_events: vec![], + rng_seed: None, } } @@ -208,6 +209,7 @@ fn generate_msgpack_fixtures() { dialogue_response: None, blocked_entities: vec![], scan_events: vec![], + rng_seed: None, }; write_fixture( "snapshot_v2_full", diff --git a/server/tests/golden/proof_room_tick_10.json b/server/tests/golden/proof_room_tick_10.json index ac0bd2d5d..5bdf56161 100644 --- a/server/tests/golden/proof_room_tick_10.json +++ b/server/tests/golden/proof_room_tick_10.json @@ -64,9 +64,10 @@ "player_facing": "North", "player_inventory": [], "player_stance": "Sprint", + "rng_seed": 42, "scan_events": [], "tick": 8, - "version": 9, + "version": 10, "visible_tiles": [ { "tile_kind": "Wall", diff --git a/server/tests/serialization.rs b/server/tests/serialization.rs index 6bc7e0d3e..7addfa2e7 100644 --- a/server/tests/serialization.rs +++ b/server/tests/serialization.rs @@ -26,6 +26,7 @@ fn test_snapshot(tick: u64, entities: Vec) -> ObserverSnapshot { dialogue_response: None, blocked_entities: vec![], scan_events: vec![], + rng_seed: None, } } @@ -254,6 +255,7 @@ fn snapshot_v2_fields_roundtrip() { dialogue_response: None, blocked_entities: vec![], scan_events: vec![], + rng_seed: None, }; let bytes = rmp_serde::to_vec_named(&snapshot).expect("serialize"); @@ -308,7 +310,7 @@ fn protocol_version_constant_matches_snapshot() { let snapshot = test_snapshot(0, vec![]); assert_eq!(snapshot.version, PROTOCOL_VERSION); assert_eq!( - PROTOCOL_VERSION, 9, + PROTOCOL_VERSION, 10, "bump this assertion when protocol version changes" ); } @@ -348,6 +350,7 @@ fn all_facing_direction_variants_roundtrip() { dialogue_response: None, blocked_entities: vec![], scan_events: vec![], + rng_seed: None, }; let bytes = rmp_serde::to_vec_named(&snapshot).expect("serialize"); let decoded: ObserverSnapshot = rmp_serde::from_slice(&bytes).expect("deserialize");