paavohtl Claude Opus 4.6 (1M context) commited on
Commit
9db9cd5
·
1 Parent(s): 7e1788d

debug: add OPENWA_WATCH_FRAME for runtime hardware watchpoints

Browse files

Arms DR0 on DDGame+0x50 at the specified frame via the existing VEH
watchpoint infrastructure. Set OPENWA_WATCH_FRAME=0 to arm from the
first frame.

Key finding: DDGame+0x50 is a sprite pointer at frame 1350, NOT a
small integer. The earlier raw-dump comparison was flawed (cross-
process pointer values compared without ASLR normalization). The real
desync is in sub-object content, not in DDGame field values.

Also adds shotgun replay (PASSES with both constructors, confirming
the desync is longbow-specific, not arrow-handler-specific).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

crates/openwa-wormkit/src/debug_watchpoint.rs CHANGED
@@ -32,12 +32,8 @@ use windows_sys::Win32::System::Diagnostics::Debug::{
32
 
33
  /// DDGame offsets to watch. Hardware limit: 4 watchpoints (DR0–DR3).
34
  /// Change these to investigate different fields.
35
- const WATCH_OFFSETS: [(u32, &str); 4] = [
36
- (0x041C, "sprite_cache[38]"),
37
- (0x0420, "sprite_cache[39]"),
38
- (0x7820, "render_idx[0]"),
39
- (0x7824, "render_idx[1]"),
40
- // Swap in (0x7828, "render_idx[2]") if needed — likely same writer as [0]/[1]
41
  ];
42
 
43
  /// DDGame base address (set when allocation is reported).
@@ -87,10 +83,12 @@ unsafe extern "system" fn veh_handler(info: *mut EXCEPTION_POINTERS) -> i32 {
87
  SAVED_DR = [ctx.Dr0, ctx.Dr1, ctx.Dr2, ctx.Dr3, ctx.Dr6, ctx.Dr7];
88
 
89
  let base = DDGAME_BASE.load(Ordering::Relaxed);
90
- ctx.Dr0 = base + WATCH_OFFSETS[0].0;
91
- ctx.Dr1 = base + WATCH_OFFSETS[1].0;
92
- ctx.Dr2 = base + WATCH_OFFSETS[2].0;
93
- ctx.Dr3 = base + WATCH_OFFSETS[3].0;
 
 
94
  ctx.Dr6 = 0;
95
  ctx.Dr7 = dr7_for_count(WATCH_OFFSETS.len());
96
 
 
32
 
33
  /// DDGame offsets to watch. Hardware limit: 4 watchpoints (DR0–DR3).
34
  /// Change these to investigate different fields.
35
+ const WATCH_OFFSETS: [(u32, &str); 1] = [
36
+ (0x0050, "ddgame+0x50"),
 
 
 
 
37
  ];
38
 
39
  /// DDGame base address (set when allocation is reported).
 
83
  SAVED_DR = [ctx.Dr0, ctx.Dr1, ctx.Dr2, ctx.Dr3, ctx.Dr6, ctx.Dr7];
84
 
85
  let base = DDGAME_BASE.load(Ordering::Relaxed);
86
+ let dr_regs = [&mut ctx.Dr0, &mut ctx.Dr1, &mut ctx.Dr2, &mut ctx.Dr3];
87
+ for (i, dr) in dr_regs.into_iter().enumerate() {
88
+ if i < WATCH_OFFSETS.len() {
89
+ *dr = base + WATCH_OFFSETS[i].0;
90
+ }
91
+ }
92
  ctx.Dr6 = 0;
93
  ctx.Dr7 = dr7_for_count(WATCH_OFFSETS.len());
94
 
crates/openwa-wormkit/src/replacements/input.rs CHANGED
@@ -186,6 +186,19 @@ unsafe extern "stdcall" fn hook_turn_manager(turngame: u32) {
186
  if !ddgame.is_null() {
187
  let game_frame = (*ddgame).frame_counter;
188
  crate::debug_sync::on_frame_start(game_frame);
 
 
 
 
 
 
 
 
 
 
 
 
 
189
  }
190
 
191
  // Call original
 
186
  if !ddgame.is_null() {
187
  let game_frame = (*ddgame).frame_counter;
188
  crate::debug_sync::on_frame_start(game_frame);
189
+
190
+ // Hardware watchpoint: arm once at the watch frame
191
+ static WATCH_ARMED: core::sync::atomic::AtomicBool = core::sync::atomic::AtomicBool::new(false);
192
+ if !WATCH_ARMED.load(Ordering::Relaxed) {
193
+ if let Ok(val) = std::env::var("OPENWA_WATCH_FRAME") {
194
+ let target: i32 = val.parse().unwrap_or(0);
195
+ if game_frame >= target {
196
+ WATCH_ARMED.store(true, Ordering::Relaxed);
197
+ crate::debug_watchpoint::prepare();
198
+ crate::debug_watchpoint::on_ddgame_alloc(ddgame as *mut u8);
199
+ }
200
+ }
201
+ }
202
  }
203
 
204
  // Call original
testdata/replays/shotgun.WAgame ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e1e1bdccaf88674cae5c859ebf5b9fc1e5ace782e1d157aae4bb14f593be3ea9
3
+ size 12318
testdata/replays/shotgun_expected.log ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1cc0c12cdf6c012f1ce153bdc7f5ed03199a0602a9e486c64c7cdfad8cecd12e
3
+ size 1186