paavohtl Claude Opus 4.6 (1M context) commited on
Commit
dbc01e5
·
1 Parent(s): 52cf816

refactor: move line drawing snapshot capture to bitgrid module

Browse files

All BitGrid snapshot capture (line drawing + blit) now lives in
replacements/bitgrid.rs for consistency — both operate on BitGrid
surfaces, not the display subsystem.

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

crates/openwa-dll/src/replacements/bitgrid.rs CHANGED
@@ -1,15 +1,240 @@
1
- //! BitGrid blit hook and snapshot capture.
2
  //!
3
  //! Hooks BitGrid__BlitSpriteRect (0x4F6910), the core sprite/bitmap blitting
4
  //! function used for both rendering and collision mask construction.
5
  //! Rust handles 8bpp blend modes 0 (copy) and 1 (color table / transparency),
6
  //! falling through to the original for unsupported modes.
 
 
 
7
 
8
  use crate::log_line;
9
  use core::sync::atomic::{AtomicU32, Ordering};
10
  use openwa_core::address::va;
11
  use openwa_core::rebase::rb;
12
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  // =========================================================================
14
  // Blit snapshot capture
15
  // =========================================================================
 
1
+ //! BitGrid hooks and snapshot capture.
2
  //!
3
  //! Hooks BitGrid__BlitSpriteRect (0x4F6910), the core sprite/bitmap blitting
4
  //! function used for both rendering and collision mask construction.
5
  //! Rust handles 8bpp blend modes 0 (copy) and 1 (color table / transparency),
6
  //! falling through to the original for unsupported modes.
7
+ //!
8
+ //! Also provides snapshot capture for line drawing and polygon fill
9
+ //! (activated by env vars at frame 10).
10
 
11
  use crate::log_line;
12
  use core::sync::atomic::{AtomicU32, Ordering};
13
  use openwa_core::address::va;
14
  use openwa_core::rebase::rb;
15
 
16
+ // =========================================================================
17
+ // Line drawing snapshot capture
18
+ // =========================================================================
19
+
20
+ /// Capture line-drawing snapshots from WA's native BitGrid line functions.
21
+ ///
22
+ /// Creates a test BitGrid, calls WA line functions with known inputs,
23
+ /// saves pixel data to `testdata/snapshots/`. Activated by
24
+ /// `OPENWA_CAPTURE_LINE_SNAPSHOTS=1`.
25
+ pub unsafe fn capture_line_snapshots() {
26
+ use openwa_core::bitgrid::DisplayBitGrid;
27
+ use std::fs;
28
+ use std::io::Write;
29
+
30
+ let grid_w: u32 = 128;
31
+ let grid_h: u32 = 128;
32
+ let grid = DisplayBitGrid::alloc(8, grid_w, grid_h);
33
+ if grid.is_null() {
34
+ let _ = log_line("[BitGrid] LINE SNAPSHOT: Failed to allocate test BitGrid");
35
+ return;
36
+ }
37
+
38
+ let row_stride = (*grid).row_stride;
39
+ let data_ptr = (*grid).data;
40
+ let data_size = (row_stride * grid_h) as usize;
41
+
42
+ // WA line functions (stdcall)
43
+ let draw_clipped: unsafe extern "stdcall" fn(*mut DisplayBitGrid, i32, i32, i32, i32, u32) =
44
+ core::mem::transmute(rb(va::DRAW_LINE_CLIPPED) as usize);
45
+
46
+ let draw_two: unsafe extern "stdcall" fn(*mut DisplayBitGrid, i32, i32, i32, i32, u32, u32) =
47
+ core::mem::transmute(rb(va::DRAW_LINE_TWO_COLOR) as usize);
48
+
49
+ let dir = "testdata/snapshots";
50
+ let _ = fs::create_dir_all(dir);
51
+
52
+ // Macro: clear grid, reset clip, run drawing code, save to file.
53
+ macro_rules! snap {
54
+ ($name:expr, $body:expr) => {{
55
+ core::ptr::write_bytes(data_ptr, 0, data_size);
56
+ (*grid).clip_left = 0;
57
+ (*grid).clip_top = 0;
58
+ (*grid).clip_right = grid_w;
59
+ (*grid).clip_bottom = grid_h;
60
+ $body;
61
+ let path = format!("{}/{}.bin", dir, $name);
62
+ if let Ok(mut file) = fs::File::create(&path) {
63
+ let _ = file.write_all(&grid_w.to_le_bytes());
64
+ let _ = file.write_all(&grid_h.to_le_bytes());
65
+ let _ = file.write_all(&row_stride.to_le_bytes());
66
+ let _ = file.write_all(core::slice::from_raw_parts(data_ptr, data_size));
67
+ }
68
+ }};
69
+ }
70
+
71
+ let f = |x: i32| x << 16; // int to Fixed raw
72
+ let mut count = 0u32;
73
+
74
+ // Single-color line tests
75
+ for &(name, x1, y1, x2, y2, color) in &[
76
+ ("clipped_horizontal", f(10), f(64), f(118), f(64), 1u32),
77
+ ("clipped_vertical", f(64), f(10), f(64), f(118), 2),
78
+ ("clipped_diagonal_45", f(10), f(10), f(118), f(118), 3),
79
+ ("clipped_diagonal_steep", f(60), f(10), f(68), f(118), 4),
80
+ ("clipped_diagonal_shallow", f(10), f(60), f(118), f(68), 5),
81
+ ("clipped_negative_slope", f(118), f(10), f(10), f(118), 6),
82
+ (
83
+ "clipped_subpixel",
84
+ f(10) + 0x8000,
85
+ f(20) + 0x4000,
86
+ f(100) + 0xC000,
87
+ f(80) + 0x2000,
88
+ 7,
89
+ ),
90
+ ("clipped_zero_length", f(64), f(64), f(64), f(64), 8),
91
+ ("clipped_partially_outside", f(-20), f(64), f(148), f(64), 9),
92
+ ("clipped_fully_outside", f(-50), f(-50), f(-10), f(-10), 10),
93
+ ] {
94
+ snap!(name, draw_clipped(grid, x1, y1, x2, y2, color));
95
+ count += 1;
96
+ }
97
+
98
+ // Two-color line tests
99
+ for &(name, x1, y1, x2, y2, c1, c2) in &[
100
+ ("twocol_horizontal", f(10), f(64), f(118), f(64), 1u32, 2u32),
101
+ ("twocol_vertical", f(64), f(10), f(64), f(118), 1, 2),
102
+ ("twocol_diagonal_45", f(10), f(10), f(118), f(118), 1, 2),
103
+ ("twocol_steep", f(60), f(10), f(68), f(118), 3, 4),
104
+ ("twocol_shallow", f(10), f(60), f(118), f(68), 3, 4),
105
+ ("twocol_negative", f(118), f(10), f(10), f(118), 5, 6),
106
+ (
107
+ "twocol_subpixel",
108
+ f(10) + 0x8000,
109
+ f(20) + 0x4000,
110
+ f(100) + 0xC000,
111
+ f(80) + 0x2000,
112
+ 7,
113
+ 8,
114
+ ),
115
+ ] {
116
+ snap!(name, draw_two(grid, x1, y1, x2, y2, c1, c2));
117
+ count += 1;
118
+ }
119
+
120
+ // Restricted clip rect tests
121
+ snap!("clipped_restricted_clip", {
122
+ (*grid).clip_left = 30;
123
+ (*grid).clip_top = 30;
124
+ (*grid).clip_right = 98;
125
+ (*grid).clip_bottom = 98;
126
+ draw_clipped(grid, f(10), f(10), f(118), f(118), 11)
127
+ });
128
+ count += 1;
129
+
130
+ snap!("twocol_restricted_clip", {
131
+ (*grid).clip_left = 30;
132
+ (*grid).clip_top = 30;
133
+ (*grid).clip_right = 98;
134
+ (*grid).clip_bottom = 98;
135
+ draw_two(grid, f(10), f(10), f(118), f(118), 9, 10)
136
+ });
137
+ count += 1;
138
+
139
+ // Polygon fill tests — call WA's polygon pipeline directly
140
+ let clip_x: unsafe extern "thiscall" fn(*mut DisplayBitGrid, *const i32, i32) -> i32 =
141
+ core::mem::transmute(rb(0x004F_7BA0) as usize);
142
+ let clip_y: unsafe extern "thiscall" fn(*mut DisplayBitGrid, i32) -> i32 =
143
+ core::mem::transmute(rb(0x004F_7D00) as usize);
144
+ let rasterize: unsafe extern "stdcall" fn(*mut DisplayBitGrid, i32, u32) =
145
+ core::mem::transmute(rb(0x004F_7E90) as usize);
146
+
147
+ // Helper: write vertices to the global vertex buffer (0x8B1370) and call pipeline
148
+ let vert_buf = rb(0x008B_1370) as *mut i32;
149
+
150
+ macro_rules! polygon_snap {
151
+ ($name:expr, $verts:expr, $color:expr) => {{
152
+ let verts: &[(i32, i32)] = $verts;
153
+ for (i, &(x, y)) in verts.iter().enumerate() {
154
+ *vert_buf.add(i * 2) = x;
155
+ *vert_buf.add(i * 2 + 1) = y;
156
+ }
157
+ snap!($name, {
158
+ let n = clip_x(grid, vert_buf, verts.len() as i32);
159
+ if n > 2 {
160
+ let n = clip_y(grid, n);
161
+ if n > 2 {
162
+ rasterize(grid, n, $color);
163
+ }
164
+ }
165
+ });
166
+ count += 1;
167
+ }};
168
+ }
169
+
170
+ // Triangle
171
+ polygon_snap!(
172
+ "poly_triangle",
173
+ &[(f(64), f(10)), (f(118), f(100)), (f(10), f(100))],
174
+ 1u32
175
+ );
176
+
177
+ // Square
178
+ polygon_snap!(
179
+ "poly_square",
180
+ &[
181
+ (f(20), f(20)),
182
+ (f(100), f(20)),
183
+ (f(100), f(100)),
184
+ (f(20), f(100))
185
+ ],
186
+ 2u32
187
+ );
188
+
189
+ // Diamond
190
+ polygon_snap!(
191
+ "poly_diamond",
192
+ &[
193
+ (f(64), f(10)),
194
+ (f(118), f(64)),
195
+ (f(64), f(118)),
196
+ (f(10), f(64))
197
+ ],
198
+ 3u32
199
+ );
200
+
201
+ // Partially outside (triangle extending beyond grid)
202
+ polygon_snap!(
203
+ "poly_partially_outside",
204
+ &[(f(64), f(-30)), (f(160), f(100)), (f(-30), f(100))],
205
+ 4u32
206
+ );
207
+
208
+ // Restricted clip rect
209
+ snap!("poly_restricted_clip", {
210
+ (*grid).clip_left = 30;
211
+ (*grid).clip_top = 30;
212
+ (*grid).clip_right = 98;
213
+ (*grid).clip_bottom = 98;
214
+ let verts: &[(i32, i32)] = &[(f(64), f(10)), (f(118), f(100)), (f(10), f(100))];
215
+ for (i, &(x, y)) in verts.iter().enumerate() {
216
+ *vert_buf.add(i * 2) = x;
217
+ *vert_buf.add(i * 2 + 1) = y;
218
+ }
219
+ let n = clip_x(grid, vert_buf, verts.len() as i32);
220
+ if n > 2 {
221
+ let n = clip_y(grid, n);
222
+ if n > 2 {
223
+ rasterize(grid, n, 5u32);
224
+ }
225
+ }
226
+ });
227
+ count += 1;
228
+
229
+ let _ = log_line(&format!(
230
+ "[BitGrid] LINE SNAPSHOT: Saved {count} snapshots to {dir}/"
231
+ ));
232
+
233
+ // Free the grid
234
+ let destructor = (*(*grid).vtable).destructor;
235
+ destructor(grid, 1);
236
+ }
237
+
238
  // =========================================================================
239
  // Blit snapshot capture
240
  // =========================================================================
crates/openwa-dll/src/replacements/display.rs CHANGED
@@ -48,224 +48,6 @@ unsafe extern "thiscall" fn headless_destructor(
48
  this
49
  }
50
 
51
- /// Capture line-drawing snapshots from WA's native BitGrid line functions.
52
- ///
53
- /// Creates a test BitGrid, calls WA line functions with known inputs,
54
- /// saves pixel data to `testdata/snapshots/`. Activated by
55
- /// `OPENWA_CAPTURE_LINE_SNAPSHOTS=1`.
56
- pub unsafe fn capture_line_snapshots() {
57
- use openwa_core::bitgrid::DisplayBitGrid;
58
- use std::fs;
59
- use std::io::Write;
60
-
61
- let grid_w: u32 = 128;
62
- let grid_h: u32 = 128;
63
- let grid = DisplayBitGrid::alloc(8, grid_w, grid_h);
64
- if grid.is_null() {
65
- let _ = log_line("[Display] SNAPSHOT: Failed to allocate test BitGrid");
66
- return;
67
- }
68
-
69
- let row_stride = (*grid).row_stride;
70
- let data_ptr = (*grid).data;
71
- let data_size = (row_stride * grid_h) as usize;
72
-
73
- // WA line functions (stdcall)
74
- let draw_clipped: unsafe extern "stdcall" fn(*mut DisplayBitGrid, i32, i32, i32, i32, u32) =
75
- core::mem::transmute(rb(va::DRAW_LINE_CLIPPED) as usize);
76
-
77
- let draw_two: unsafe extern "stdcall" fn(*mut DisplayBitGrid, i32, i32, i32, i32, u32, u32) =
78
- core::mem::transmute(rb(va::DRAW_LINE_TWO_COLOR) as usize);
79
-
80
- let dir = "testdata/snapshots";
81
- let _ = fs::create_dir_all(dir);
82
-
83
- // Macro: clear grid, reset clip, run drawing code, save to file.
84
- macro_rules! snap {
85
- ($name:expr, $body:expr) => {{
86
- core::ptr::write_bytes(data_ptr, 0, data_size);
87
- (*grid).clip_left = 0;
88
- (*grid).clip_top = 0;
89
- (*grid).clip_right = grid_w;
90
- (*grid).clip_bottom = grid_h;
91
- $body;
92
- let path = format!("{}/{}.bin", dir, $name);
93
- if let Ok(mut file) = fs::File::create(&path) {
94
- let _ = file.write_all(&grid_w.to_le_bytes());
95
- let _ = file.write_all(&grid_h.to_le_bytes());
96
- let _ = file.write_all(&row_stride.to_le_bytes());
97
- let _ = file.write_all(core::slice::from_raw_parts(data_ptr, data_size));
98
- }
99
- }};
100
- }
101
-
102
- let f = |x: i32| x << 16; // int to Fixed raw
103
- let mut count = 0u32;
104
-
105
- // Single-color line tests
106
- for &(name, x1, y1, x2, y2, color) in &[
107
- ("clipped_horizontal", f(10), f(64), f(118), f(64), 1u32),
108
- ("clipped_vertical", f(64), f(10), f(64), f(118), 2),
109
- ("clipped_diagonal_45", f(10), f(10), f(118), f(118), 3),
110
- ("clipped_diagonal_steep", f(60), f(10), f(68), f(118), 4),
111
- ("clipped_diagonal_shallow", f(10), f(60), f(118), f(68), 5),
112
- ("clipped_negative_slope", f(118), f(10), f(10), f(118), 6),
113
- (
114
- "clipped_subpixel",
115
- f(10) + 0x8000,
116
- f(20) + 0x4000,
117
- f(100) + 0xC000,
118
- f(80) + 0x2000,
119
- 7,
120
- ),
121
- ("clipped_zero_length", f(64), f(64), f(64), f(64), 8),
122
- ("clipped_partially_outside", f(-20), f(64), f(148), f(64), 9),
123
- ("clipped_fully_outside", f(-50), f(-50), f(-10), f(-10), 10),
124
- ] {
125
- snap!(name, draw_clipped(grid, x1, y1, x2, y2, color));
126
- count += 1;
127
- }
128
-
129
- // Two-color line tests
130
- for &(name, x1, y1, x2, y2, c1, c2) in &[
131
- ("twocol_horizontal", f(10), f(64), f(118), f(64), 1u32, 2u32),
132
- ("twocol_vertical", f(64), f(10), f(64), f(118), 1, 2),
133
- ("twocol_diagonal_45", f(10), f(10), f(118), f(118), 1, 2),
134
- ("twocol_steep", f(60), f(10), f(68), f(118), 3, 4),
135
- ("twocol_shallow", f(10), f(60), f(118), f(68), 3, 4),
136
- ("twocol_negative", f(118), f(10), f(10), f(118), 5, 6),
137
- (
138
- "twocol_subpixel",
139
- f(10) + 0x8000,
140
- f(20) + 0x4000,
141
- f(100) + 0xC000,
142
- f(80) + 0x2000,
143
- 7,
144
- 8,
145
- ),
146
- ] {
147
- snap!(name, draw_two(grid, x1, y1, x2, y2, c1, c2));
148
- count += 1;
149
- }
150
-
151
- // Restricted clip rect tests
152
- snap!("clipped_restricted_clip", {
153
- (*grid).clip_left = 30;
154
- (*grid).clip_top = 30;
155
- (*grid).clip_right = 98;
156
- (*grid).clip_bottom = 98;
157
- draw_clipped(grid, f(10), f(10), f(118), f(118), 11)
158
- });
159
- count += 1;
160
-
161
- snap!("twocol_restricted_clip", {
162
- (*grid).clip_left = 30;
163
- (*grid).clip_top = 30;
164
- (*grid).clip_right = 98;
165
- (*grid).clip_bottom = 98;
166
- draw_two(grid, f(10), f(10), f(118), f(118), 9, 10)
167
- });
168
- count += 1;
169
-
170
- // Polygon fill tests — call WA's polygon pipeline directly
171
- let clip_x: unsafe extern "thiscall" fn(*mut DisplayBitGrid, *const i32, i32) -> i32 =
172
- core::mem::transmute(rb(0x004F_7BA0) as usize);
173
- let clip_y: unsafe extern "thiscall" fn(*mut DisplayBitGrid, i32) -> i32 =
174
- core::mem::transmute(rb(0x004F_7D00) as usize);
175
- let rasterize: unsafe extern "stdcall" fn(*mut DisplayBitGrid, i32, u32) =
176
- core::mem::transmute(rb(0x004F_7E90) as usize);
177
-
178
- // Helper: write vertices to the global vertex buffer (0x8B1370) and call pipeline
179
- let vert_buf = rb(0x008B_1370) as *mut i32;
180
-
181
- macro_rules! polygon_snap {
182
- ($name:expr, $verts:expr, $color:expr) => {{
183
- let verts: &[(i32, i32)] = $verts;
184
- for (i, &(x, y)) in verts.iter().enumerate() {
185
- *vert_buf.add(i * 2) = x;
186
- *vert_buf.add(i * 2 + 1) = y;
187
- }
188
- snap!($name, {
189
- let n = clip_x(grid, vert_buf, verts.len() as i32);
190
- if n > 2 {
191
- let n = clip_y(grid, n);
192
- if n > 2 {
193
- rasterize(grid, n, $color);
194
- }
195
- }
196
- });
197
- count += 1;
198
- }};
199
- }
200
-
201
- // Triangle
202
- polygon_snap!(
203
- "poly_triangle",
204
- &[(f(64), f(10)), (f(118), f(100)), (f(10), f(100))],
205
- 1u32
206
- );
207
-
208
- // Square
209
- polygon_snap!(
210
- "poly_square",
211
- &[
212
- (f(20), f(20)),
213
- (f(100), f(20)),
214
- (f(100), f(100)),
215
- (f(20), f(100))
216
- ],
217
- 2u32
218
- );
219
-
220
- // Diamond
221
- polygon_snap!(
222
- "poly_diamond",
223
- &[
224
- (f(64), f(10)),
225
- (f(118), f(64)),
226
- (f(64), f(118)),
227
- (f(10), f(64))
228
- ],
229
- 3u32
230
- );
231
-
232
- // Partially outside (triangle extending beyond grid)
233
- polygon_snap!(
234
- "poly_partially_outside",
235
- &[(f(64), f(-30)), (f(160), f(100)), (f(-30), f(100))],
236
- 4u32
237
- );
238
-
239
- // Restricted clip rect
240
- snap!("poly_restricted_clip", {
241
- (*grid).clip_left = 30;
242
- (*grid).clip_top = 30;
243
- (*grid).clip_right = 98;
244
- (*grid).clip_bottom = 98;
245
- let verts: &[(i32, i32)] = &[(f(64), f(10)), (f(118), f(100)), (f(10), f(100))];
246
- for (i, &(x, y)) in verts.iter().enumerate() {
247
- *vert_buf.add(i * 2) = x;
248
- *vert_buf.add(i * 2 + 1) = y;
249
- }
250
- let n = clip_x(grid, vert_buf, verts.len() as i32);
251
- if n > 2 {
252
- let n = clip_y(grid, n);
253
- if n > 2 {
254
- rasterize(grid, n, 5u32);
255
- }
256
- }
257
- });
258
- count += 1;
259
-
260
- let _ = log_line(&format!(
261
- "[Display] SNAPSHOT: Saved {count} snapshots to {dir}/"
262
- ));
263
-
264
- // Free the grid
265
- let destructor = (*(*grid).vtable).destructor;
266
- destructor(grid, 1);
267
- }
268
-
269
  pub fn install() -> Result<(), String> {
270
  let _ = log_line("[Display] Patching DisplayBase vtables");
271
 
 
48
  this
49
  }
50
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  pub fn install() -> Result<(), String> {
52
  let _ = log_line("[Display] Patching DisplayBase vtables");
53
 
crates/openwa-dll/src/replacements/frame_hook.rs CHANGED
@@ -66,7 +66,7 @@ unsafe extern "stdcall" fn hook_turn_manager(turngame: u32) {
66
  // Snapshot capture: run once at frame 10 when env vars are set
67
  if frame == 10 {
68
  if std::env::var("OPENWA_CAPTURE_LINE_SNAPSHOTS").is_ok() {
69
- super::display::capture_line_snapshots();
70
  }
71
  if std::env::var("OPENWA_CAPTURE_BLIT_SNAPSHOTS").is_ok() {
72
  super::bitgrid::capture_blit_snapshots();
 
66
  // Snapshot capture: run once at frame 10 when env vars are set
67
  if frame == 10 {
68
  if std::env::var("OPENWA_CAPTURE_LINE_SNAPSHOTS").is_ok() {
69
+ super::bitgrid::capture_line_snapshots();
70
  }
71
  if std::env::var("OPENWA_CAPTURE_BLIT_SNAPSHOTS").is_ok() {
72
  super::bitgrid::capture_blit_snapshots();