paavohtl Claude Opus 4.6 (1M context) commited on
Commit
afaf5de
·
1 Parent(s): d439705

refactor: propagate types and named fields in game_state_init

Browse files

Split _unknown blocks in DDGame (28 new fields) and GameInfo (17 new
fields) to replace raw pointer arithmetic with named struct access.
Retype game_speed, level bounds, and render_scale to Fixed. Change
game_info_sub parameters from *const u8 to *mut GameInfo across all
init sub-functions, eliminating the untyped pointer pattern.

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

crates/openwa-core/src/engine/ddgame.rs CHANGED
@@ -131,35 +131,67 @@ pub struct DDGame {
131
  /// 0x54C: CTaskLand pointer (set by CTaskLand__InitLandscape at 0x5056F0).
132
  /// The landscape/terrain task. Vtable at 0x664388.
133
  pub task_land: *mut u8,
134
- /// 0x550-0x5CB: Unknown.
135
- ///
136
- /// Runtime observations (not yet linked to code):
137
- /// - 0x5C4: value matches code address 0x5755D0 (fixed-point normalize fn)
138
- /// - 0x5C8-0x5CB: config-like values
139
- pub _unknown_550: [u8; 0x5CC - 0x550],
 
140
 
141
  /// 0x5CC: Game frame counter. Incremented at end of each frame in
142
  /// GameFrameDispatcher. Compared against GameInfo+0xF344 (sound start
143
  /// threshold) by IsSoundSuppressed and DispatchGlobalSound.
144
  pub frame_counter: i32,
145
 
146
- /// 0x5D0-0x25FF: Large unverified region.
147
- ///
148
- /// Runtime observations:
149
- /// - 0x5D0: frame processing flag (set 0/1 by GameFrameEndProcessor)
150
- /// - 0x5D8-0x5FF: small config-like values (2048, 150, 3000, 696, 896, 100, 300)
151
- /// - 0x600-0x25FF: identity permutation [0,1,2,...,~2048] — purpose unknown
152
- pub _unknown_5d0: [u8; 0x2600 - 0x5D0],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
153
 
154
  /// 0x2600-0x2DFF: Block of 0xFFFFFFFF values at runtime (512 i32 entries).
155
  /// May be unused slots in a parallel table to the 0x600 permutation.
156
  pub _unknown_2600: [u8; 0x2E00 - 0x2600],
157
 
158
- /// 0x2E00-0x45EB: Unknown (mostly zero at runtime)
159
  ///
160
  /// Contains FUN_00526120 zeroed offsets at stride 0x194:
161
  /// 0x379C, 0x3930, 0x3AC4, 0x3C58, 0x3DEC, 0x3F80, 0x4114, 0x42A8, 0x443C, 0x45D0
162
- pub _unknown_2e00: [u8; 0x45EC - 0x2E00],
 
 
 
 
 
 
 
 
 
 
 
 
163
 
164
  /// 0x45EC: Game RNG state. Advanced each frame and by weapon spread:
165
  /// `rng = (frame_counter + rng) * 0x19660D + 0x3C6EF35F`.
@@ -193,10 +225,10 @@ pub struct DDGame {
193
  /// 0x72A8-0x72D7: Unknown
194
  pub _unknown_72a8: [u8; 0x72D8 - 0x72A8],
195
 
196
- /// 0x72D8: Game speed multiplier (Fixed-point, 0x10000 = 1.0x).
197
- pub game_speed: i32,
198
- /// 0x72DC: Game speed target (Fixed-point, 0x10000 = 1.0x).
199
- pub game_speed_target: i32,
200
  /// 0x72E0-0x72E3: Unknown
201
  pub _unknown_72e0: u32,
202
  /// 0x72E4: Active render/weapon slot count. Reset to 14 (0x0E) by WeaponRelease
@@ -237,8 +269,11 @@ pub struct DDGame {
237
  pub _unknown_7344: [u8; 8],
238
  /// 0x734C: Landscape dimension param (passed to PCLandscape vtable slot 6).
239
  pub _field_734c: u32,
240
- /// 0x7350-0x7373: Unknown
241
- pub _unknown_7350: [u8; 36],
 
 
 
242
  /// 0x7374: Unknown (zeroed by InitTurnState).
243
  pub _field_7374: u32,
244
  /// 0x7378: Unknown (zeroed by InitTurnState).
@@ -254,8 +289,12 @@ pub struct DDGame {
254
  pub viewport_width_2: i32,
255
  /// 0x738C: Viewport height max/duplicate (Fixed-point).
256
  pub viewport_height_2: i32,
257
- /// 0x7390-0x739B: Unknown
258
- pub _unknown_7390: [u8; 0x739C - 0x7390],
 
 
 
 
259
  /// 0x739C: Render state flag (zeroed by InitRenderIndices).
260
  pub render_state_flag: u32,
261
 
@@ -271,8 +310,14 @@ pub struct DDGame {
271
  /// 0x73B0: Render entry table (14 entries × 0x14 bytes).
272
  /// First u32 of each entry zeroed by InitRenderIndices.
273
  pub render_entries: [RenderEntry; 14],
274
- /// 0x74C8-0x764B: Unknown
275
- pub _unknown_74c8: [u8; 0x764C - 0x74C8],
 
 
 
 
 
 
276
  /// 0x764C: Rendering phase. Checked by CTaskCloud::HandleMessage:
277
  /// clouds only render when this == 5 (in-game rendering active).
278
  pub render_phase: i32,
@@ -289,28 +334,31 @@ pub struct DDGame {
289
  pub _field_7784: u32,
290
  /// 0x7788: Unknown (set from GameInfo+0xF362 during InitTurnState).
291
  pub _field_7788: u32,
292
- /// 0x778C: Unknown (set to Fixed 1.0 = 0x10000 by InitTurnState).
293
- pub _field_778c: u32,
294
  /// 0x7790: Unknown (zeroed by InitTurnState).
295
  pub _field_7790: u32,
296
- /// 0x7794-0x779B: Unknown
297
- pub _unknown_7794: [u8; 8],
 
 
298
 
299
  /// 0x779C: Level bound min X (Fixed16.16, negative = off-screen left).
300
  pub level_bound_min_x: Fixed,
301
  /// 0x77A0: Level bound max X (Fixed16.16).
302
  pub level_bound_max_x: Fixed,
303
- /// 0x77A4: Level bound min Y (Fixed-point, same as min_x typically).
304
- pub level_bound_min_y: i32,
305
- /// 0x77A8: Level bound max Y (Fixed-point).
306
- pub level_bound_max_y: i32,
307
  /// 0x77AC-0x77B7: Unknown
308
  pub _unknown_77ac: [u8; 0x77B8 - 0x77AC],
309
  /// 0x77B8: Level width for 3D sound distance computation (pixels, not fixed-point).
310
  /// Read by ComputeDistanceParams, shifted left 16 before passing to Distance3D_Attenuation.
311
  pub level_width_sound: i32,
312
- /// 0x77BC-0x77BF: Unknown
313
- pub _unknown_77bc: [u8; 0x77C0 - 0x77BC],
 
314
 
315
  /// 0x77C0: Level width in pixels (set by PCLandscape constructor).
316
  pub level_width: u32,
@@ -319,8 +367,10 @@ pub struct DDGame {
319
  /// 0x77C8: Total pixels (width × height).
320
  pub level_total_pixels: u32,
321
 
322
- /// 0x77CC-0x77D3: Unknown
323
- pub _unknown_77cc: [u8; 8],
 
 
324
  /// 0x77D4: Unknown (zeroed by InitTurnState).
325
  pub _field_77d4: u32,
326
  /// 0x77D8: Unknown (zeroed by InitTurnState).
@@ -714,9 +764,9 @@ impl crate::snapshot::Snapshot for DDGame {
714
  write_indent(w, i)?;
715
  writeln!(w, "frame_counter = {}", self.frame_counter)?;
716
  write_indent(w, i)?;
717
- writeln!(w, "game_speed = {}", Fixed(self.game_speed))?;
718
  write_indent(w, i)?;
719
- writeln!(w, "game_speed_target = {}", Fixed(self.game_speed_target))?;
720
  write_indent(w, i)?;
721
  writeln!(
722
  w,
 
131
  /// 0x54C: CTaskLand pointer (set by CTaskLand__InitLandscape at 0x5056F0).
132
  /// The landscape/terrain task. Vtable at 0x664388.
133
  pub task_land: *mut u8,
134
+ /// 0x550-0x5C3: Unknown.
135
+ pub _unknown_550: [u8; 0x5C4 - 0x550],
136
+ /// 0x5C4: Vector normalize function pointer. Selected by game version in InitGameState:
137
+ /// < 0x99 → VECTOR_NORMALIZE_SIMPLE (0x5755D0), >= 0x99 → VECTOR_NORMALIZE_OVERFLOW.
138
+ pub vector_normalize_fn: u32,
139
+ /// 0x5C8: Stack height config. Version-dependent: < -1 → 0x700, >= -1 → 0x800.
140
+ pub stack_height: u32,
141
 
142
  /// 0x5CC: Game frame counter. Incremented at end of each frame in
143
  /// GameFrameDispatcher. Compared against GameInfo+0xF344 (sound start
144
  /// threshold) by IsSoundSuppressed and DispatchGlobalSound.
145
  pub frame_counter: i32,
146
 
147
+ /// 0x5D0: Frame processing flag (set 0/1 by GameFrameEndProcessor, zeroed by InitGameState).
148
+ pub _field_5d0: u32,
149
+ /// 0x5D4: Unknown (zeroed by InitGameState).
150
+ pub _field_5d4: u32,
151
+ /// 0x5D8: Unknown (zeroed by InitGameState).
152
+ pub _field_5d8: u32,
153
+ /// 0x5DC: Unknown (zeroed by InitGameState).
154
+ pub _field_5dc: u32,
155
+ /// 0x5E0: Water level in pixels. Computed as `(100 - water_pct) * level_height / 100`.
156
+ pub water_level: i32,
157
+ /// 0x5E4: Water kill Y boundary (pixels). Derived from level_bound_max_y integer part + 0x28.
158
+ pub water_kill_y: i32,
159
+ /// 0x5E8: Initial water level (copy of water_level at init time).
160
+ pub water_level_initial: i32,
161
+ /// 0x5EC: Unknown (zeroed by InitGameState).
162
+ pub _field_5ec: u32,
163
+ /// 0x5F0: Unknown (set to 1 by InitGameState).
164
+ pub _field_5f0: u32,
165
+ /// 0x5F4: Unknown (set to 100 by InitGameState).
166
+ pub _field_5f4: u32,
167
+ /// 0x5F8: Unknown (zeroed by InitGameState).
168
+ pub _field_5f8: u32,
169
+ /// 0x5FC: Unknown (zeroed by InitGameState).
170
+ pub _field_5fc: u32,
171
+ /// 0x600-0x25FF: Identity permutation [0,1,2,...,~2048] — SpriteGfxTable.
172
+ pub _unknown_600: [u8; 0x2600 - 0x600],
173
 
174
  /// 0x2600-0x2DFF: Block of 0xFFFFFFFF values at runtime (512 i32 entries).
175
  /// May be unused slots in a parallel table to the 0x600 permutation.
176
  pub _unknown_2600: [u8; 0x2E00 - 0x2600],
177
 
178
+ /// 0x2E00-0x45D3: Unknown (mostly zero at runtime).
179
  ///
180
  /// Contains FUN_00526120 zeroed offsets at stride 0x194:
181
  /// 0x379C, 0x3930, 0x3AC4, 0x3C58, 0x3DEC, 0x3F80, 0x4114, 0x42A8, 0x443C, 0x45D0
182
+ pub _unknown_2e00: [u8; 0x45D4 - 0x2E00],
183
+ /// 0x45D4: Terrain drop percentage A (from GameInfo+0xD955).
184
+ pub terrain_pct_a: u32,
185
+ /// 0x45D8: Terrain drop percentage B (from GameInfo+0xD958).
186
+ pub terrain_pct_b: u32,
187
+ /// 0x45DC: Terrain drop percentage C (from GameInfo+0xD957).
188
+ pub terrain_pct_c: u32,
189
+ /// 0x45E0: Unknown (zeroed by InitGameState version config).
190
+ pub _field_45e0: u32,
191
+ /// 0x45E4: Unknown (zeroed by InitGameState version config).
192
+ pub _field_45e4: u32,
193
+ /// 0x45E8: Unknown (zeroed by InitGameState version config).
194
+ pub _field_45e8: u32,
195
 
196
  /// 0x45EC: Game RNG state. Advanced each frame and by weapon spread:
197
  /// `rng = (frame_counter + rng) * 0x19660D + 0x3C6EF35F`.
 
225
  /// 0x72A8-0x72D7: Unknown
226
  pub _unknown_72a8: [u8; 0x72D8 - 0x72A8],
227
 
228
+ /// 0x72D8: Game speed multiplier (Fixed16.16, 1.0 = normal speed).
229
+ pub game_speed: Fixed,
230
+ /// 0x72DC: Game speed target (Fixed16.16, 1.0 = normal speed).
231
+ pub game_speed_target: Fixed,
232
  /// 0x72E0-0x72E3: Unknown
233
  pub _unknown_72e0: u32,
234
  /// 0x72E4: Active render/weapon slot count. Reset to 14 (0x0E) by WeaponRelease
 
269
  pub _unknown_7344: [u8; 8],
270
  /// 0x734C: Landscape dimension param (passed to PCLandscape vtable slot 6).
271
  pub _field_734c: u32,
272
+ /// 0x7350-0x736F: Unknown
273
+ pub _unknown_7350: [u8; 0x7370 - 0x7350],
274
+ /// 0x7370: Current team color index. Set from GameInfo+0xD924;
275
+ /// overridden to -1 (0xFFFFFFFF) if any CPU team is detected.
276
+ pub team_color: u32,
277
  /// 0x7374: Unknown (zeroed by InitTurnState).
278
  pub _field_7374: u32,
279
  /// 0x7378: Unknown (zeroed by InitTurnState).
 
289
  pub viewport_width_2: i32,
290
  /// 0x738C: Viewport height max/duplicate (Fixed-point).
291
  pub viewport_height_2: i32,
292
+ /// 0x7390: Unknown (zeroed by InitGameState).
293
+ pub _field_7390: u32,
294
+ /// 0x7394: Render scale factor (Fixed-point, 0x10000 = 1.0).
295
+ pub render_scale: Fixed,
296
+ /// 0x7398: Unknown (zeroed by InitGameState).
297
+ pub _field_7398: u32,
298
  /// 0x739C: Render state flag (zeroed by InitRenderIndices).
299
  pub render_state_flag: u32,
300
 
 
310
  /// 0x73B0: Render entry table (14 entries × 0x14 bytes).
311
  /// First u32 of each entry zeroed by InitRenderIndices.
312
  pub render_entries: [RenderEntry; 14],
313
+ /// 0x74C8-0x763F: Unknown
314
+ pub _unknown_74c8: [u8; 0x7640 - 0x74C8],
315
+ /// 0x7640: Unknown (zeroed by InitGameState).
316
+ pub _field_7640: u32,
317
+ /// 0x7644: Unknown config (from GameInfo+0xF363).
318
+ pub _field_7644: u32,
319
+ /// 0x7648: Unknown config (from GameInfo+0xF364).
320
+ pub _field_7648: u32,
321
  /// 0x764C: Rendering phase. Checked by CTaskCloud::HandleMessage:
322
  /// clouds only render when this == 5 (in-game rendering active).
323
  pub render_phase: i32,
 
334
  pub _field_7784: u32,
335
  /// 0x7788: Unknown (set from GameInfo+0xF362 during InitTurnState).
336
  pub _field_7788: u32,
337
+ /// 0x778C: Unknown (set to Fixed 1.0 by InitTurnState).
338
+ pub _field_778c: Fixed,
339
  /// 0x7790: Unknown (zeroed by InitTurnState).
340
  pub _field_7790: u32,
341
+ /// 0x7794: Unknown (zeroed by InitGameState level bounds).
342
+ pub _field_7794: u32,
343
+ /// 0x7798: Unknown (zeroed by InitGameState level bounds).
344
+ pub _field_7798: u32,
345
 
346
  /// 0x779C: Level bound min X (Fixed16.16, negative = off-screen left).
347
  pub level_bound_min_x: Fixed,
348
  /// 0x77A0: Level bound max X (Fixed16.16).
349
  pub level_bound_max_x: Fixed,
350
+ /// 0x77A4: Level bound min Y (Fixed16.16).
351
+ pub level_bound_min_y: Fixed,
352
+ /// 0x77A8: Level bound max Y (Fixed16.16).
353
+ pub level_bound_max_y: Fixed,
354
  /// 0x77AC-0x77B7: Unknown
355
  pub _unknown_77ac: [u8; 0x77B8 - 0x77AC],
356
  /// 0x77B8: Level width for 3D sound distance computation (pixels, not fixed-point).
357
  /// Read by ComputeDistanceParams, shifted left 16 before passing to Distance3D_Attenuation.
358
  pub level_width_sound: i32,
359
+ /// 0x77BC: Screen height in pixels (output from display vtable[1]).
360
+ /// Read by InitGameState for resolution-dependent layout.
361
+ pub screen_height_pixels: i32,
362
 
363
  /// 0x77C0: Level width in pixels (set by PCLandscape constructor).
364
  pub level_width: u32,
 
367
  /// 0x77C8: Total pixels (width × height).
368
  pub level_total_pixels: u32,
369
 
370
+ /// 0x77CC: Map boundary width. Default 0x30D4; updated to `level_w + 0x2954` for version > 0x32.
371
+ pub map_boundary_width: u32,
372
+ /// 0x77D0: Map boundary height. Default = level_height; updated to 0x2B8 for version > 0x32.
373
+ pub map_boundary_height: u32,
374
  /// 0x77D4: Unknown (zeroed by InitTurnState).
375
  pub _field_77d4: u32,
376
  /// 0x77D8: Unknown (zeroed by InitTurnState).
 
764
  write_indent(w, i)?;
765
  writeln!(w, "frame_counter = {}", self.frame_counter)?;
766
  write_indent(w, i)?;
767
+ writeln!(w, "game_speed = {}", self.game_speed)?;
768
  write_indent(w, i)?;
769
+ writeln!(w, "game_speed_target = {}", self.game_speed_target)?;
770
  write_indent(w, i)?;
771
  writeln!(
772
  w,
crates/openwa-core/src/engine/game_info.rs CHANGED
@@ -35,13 +35,25 @@ pub struct GameInfo {
35
  pub scoring_param_a: u16,
36
  /// 0xD78A: Scoring parameter B (multiplied by 50 for initial score).
37
  pub scoring_param_b: u16,
38
- /// 0xD78C-0xD931: Unknown
39
- pub _unknown_d78c: [u8; 0xD932 - 0xD78C],
 
 
 
 
40
  /// 0xD932: DoubleTurnTime availability threshold (u16).
41
  /// If game_version > 0xD1 and this > 0x7FFF, DoubleTurnTime is disabled.
42
  pub double_turn_time_threshold: u16,
43
- /// 0xD934-0xD93B: Unknown
44
- pub _unknown_d934: [u8; 0xD93C - 0xD934],
 
 
 
 
 
 
 
 
45
  /// 0xD93C: Super weapon allowed flag. If 0, super weapons are
46
  /// disabled (except when game_version < 0x2A).
47
  pub super_weapon_allowed: u8,
@@ -61,21 +73,41 @@ pub struct GameInfo {
61
  pub landscape_scheme_flag: u8,
62
  /// 0xD94C: Donkey (weapon 0x36) disable flag.
63
  pub donkey_disabled: u8,
64
- /// 0xD94D-0xD955: Unknown
65
- pub _unknown_d94d: [u8; 0xD956 - 0xD94D],
 
 
66
  /// 0xD956: When set, the AquaSheep slot is treated as SuperSheep instead.
67
  pub aquasheep_is_supersheep: u8,
68
- /// 0xD957-0xD958: Unknown
69
- pub _unknown_d957: [u8; 0xD959 - 0xD957],
 
 
70
  /// 0xD959: Version-gated weapon restriction. If nonzero and game_version > 0x29,
71
  /// returns -2 for unavailable weapons.
72
  pub weapon_version_gate: u8,
73
- /// 0xD95A-0xD98A: Unknown
74
- pub _unknown_d95a: [u8; 0xD98B - 0xD95A],
75
- /// 0xD98B: Terrain flag (set from map object during replay loading).
76
- pub terrain_flag: u8,
77
- /// 0xD98C-0xD9A1: Unknown
78
- pub _unknown_d98c: [u8; 0xD9A2 - 0xD98C],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
  /// 0xD9A2: Network weapon exception flag. When net_config_2 != 0,
80
  /// weapons 10/0x37/0x38 are only disabled if this is also 0.
81
  pub net_weapon_exception: u8,
@@ -96,8 +128,10 @@ pub struct GameInfo {
96
  /// If nonzero, streaming audio subsystem is created in InitHardware.
97
  pub speech_enabled: u8,
98
 
99
- /// 0xDAA5-0xDAAB: Unknown
100
- pub _unknown_daa5: [u8; 0xDAAC - 0xDAA5],
 
 
101
 
102
  /// 0xDAAC: Landscape data path (passed to PCLandscape constructor).
103
  /// Points to a path string used for loading level terrain data.
@@ -115,8 +149,10 @@ pub struct GameInfo {
115
  /// 0xDB08: Invisibility (weapon 0x42) mode flag (u32). Controls team-count
116
  /// vs network_ecx check for availability.
117
  pub invisibility_mode: u32,
118
- /// 0xDB0C-0xDB1B: Unknown
119
- pub _unknown_db0c: [u8; 0xDB1C - 0xDB0C],
 
 
120
  /// 0xDB1C: Replay map sub-type (first DWORD of first payload).
121
  /// >= 1: map stored in playback.thm. Negative: inline map data.
122
  pub replay_map_type: i32,
@@ -142,8 +178,10 @@ pub struct GameInfo {
142
  pub _unknown_df60: [u8; 0xEF60 - 0xDF60],
143
  /// 0xEF60: Cleared to 0 during replay loading.
144
  pub replay_field_ef60: u32,
145
- /// 0xEF64-0xF343: Unknown
146
- pub _unknown_ef64: [u8; 0xF344 - 0xEF64],
 
 
147
 
148
  /// 0xF344: Sound start frame threshold (i32). Sound is suppressed when
149
  /// DDGame.frame_counter < this value. Checked by IsSoundSuppressed and
@@ -154,12 +192,32 @@ pub struct GameInfo {
154
  /// Checked by IsSoundSuppressed, DispatchGlobalSound, PlaySoundPooled_Direct.
155
  pub sound_mute: u8,
156
 
157
- /// 0xF349-0xF361: Unknown
158
- pub _unknown_f349: [u8; 0xF362 - 0xF349],
 
 
 
 
 
 
159
  /// 0xF362: Unknown byte copied to DDGame+0x7788 during turn state init.
160
  pub _field_f362: u8,
161
- /// 0xF363-0xF373: Unknown
162
- pub _unknown_f363: [u8; 0xF374 - 0xF363],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
163
 
164
  /// 0xF374: Display flags passed to DisplayGfx::Init.
165
  pub display_flags: u32,
@@ -250,6 +308,18 @@ pub struct GameInfo {
250
 
251
  const _: () = assert!(core::mem::size_of::<GameInfo>() == 0xF91C);
252
 
 
 
 
 
 
 
 
 
 
 
 
 
253
  struct HexU32s<'a>(&'a [u32]);
254
 
255
  impl core::fmt::Debug for HexU32s<'_> {
 
35
  pub scoring_param_a: u16,
36
  /// 0xD78A: Scoring parameter B (multiplied by 50 for initial score).
37
  pub scoring_param_b: u16,
38
+ /// 0xD78C-0xD923: Unknown
39
+ pub _unknown_d78c: [u8; 0xD924 - 0xD78C],
40
+ /// 0xD924: Starting team color index (u8). Copied to DDGame.team_color at init.
41
+ pub team_color_source: u8,
42
+ /// 0xD925-0xD931: Unknown
43
+ pub _unknown_d925: [u8; 0xD932 - 0xD925],
44
  /// 0xD932: DoubleTurnTime availability threshold (u16).
45
  /// If game_version > 0xD1 and this > 0x7FFF, DoubleTurnTime is disabled.
46
  pub double_turn_time_threshold: u16,
47
+ /// 0xD934-0xD937: Unknown
48
+ pub _unknown_d934: [u8; 0xD938 - 0xD934],
49
+ /// 0xD938: Random crate drop percentage — land mines (u8, 0-100).
50
+ pub drop_pct_land: u8,
51
+ /// 0xD939: Random crate drop percentage — mines (u8, 0-100).
52
+ pub drop_pct_mine: u8,
53
+ /// 0xD93A: Random crate drop percentage — oil barrels (u8, 0-100).
54
+ pub drop_pct_barrel: u8,
55
+ /// 0xD93B: Unknown
56
+ pub _unknown_d93b: u8,
57
  /// 0xD93C: Super weapon allowed flag. If 0, super weapons are
58
  /// disabled (except when game_version < 0x2A).
59
  pub super_weapon_allowed: u8,
 
73
  pub landscape_scheme_flag: u8,
74
  /// 0xD94C: Donkey (weapon 0x36) disable flag.
75
  pub donkey_disabled: u8,
76
+ /// 0xD94D-0xD954: Unknown
77
+ pub _unknown_d94d: [u8; 0xD955 - 0xD94D],
78
+ /// 0xD955: Terrain drop config byte A. Copied to DDGame.terrain_pct_a.
79
+ pub terrain_cfg_a: u8,
80
  /// 0xD956: When set, the AquaSheep slot is treated as SuperSheep instead.
81
  pub aquasheep_is_supersheep: u8,
82
+ /// 0xD957: Terrain drop config byte C. Copied to DDGame.terrain_pct_c.
83
+ pub terrain_cfg_c: u8,
84
+ /// 0xD958: Terrain drop config byte B. Copied to DDGame.terrain_pct_b.
85
+ pub terrain_cfg_b: u8,
86
  /// 0xD959: Version-gated weapon restriction. If nonzero and game_version > 0x29,
87
  /// returns -2 for unavailable weapons.
88
  pub weapon_version_gate: u8,
89
+ /// 0xD95A-0xD967: Unknown
90
+ pub _unknown_d95a: [u8; 0xD968 - 0xD95A],
91
+ /// 0xD968: Extended team count (u16). Used for buffer allocation sizing.
92
+ pub num_teams_alloc: u16,
93
+ /// 0xD96A: Extended terrain drop percentage (u8, remainder after land/mine/barrel).
94
+ pub ext_terrain_pct: u8,
95
+ /// 0xD96B: Extended terrain drop type (u8). 0 = auto-fill remainder.
96
+ pub ext_terrain_type: u8,
97
+ /// 0xD96C-0xD987: Unknown
98
+ pub _unknown_d96c: [u8; 0xD988 - 0xD96C],
99
+ /// 0xD988: Game speed config (Fixed16.16). Read as i32 for headful game_speed_target.
100
+ /// Note: high byte (0xD98B) overlaps with terrain_flag — use `terrain_flag` accessor.
101
+ pub game_speed_config: i32,
102
+ /// 0xD98C-0xD98F: Unknown
103
+ pub _unknown_d98c: [u8; 0xD990 - 0xD98C],
104
+ /// 0xD990: Team slot allocation count (u32). Used to size DDGame+0x514 array.
105
+ pub team_slot_count: u32,
106
+ /// 0xD994: Object slot allocation count (u32). Used to size DDGame+0x518 array
107
+ /// and buffer object allocation.
108
+ pub object_slot_count: u32,
109
+ /// 0xD998-0xD9A1: Unknown
110
+ pub _unknown_d998: [u8; 0xD9A2 - 0xD998],
111
  /// 0xD9A2: Network weapon exception flag. When net_config_2 != 0,
112
  /// weapons 10/0x37/0x38 are only disabled if this is also 0.
113
  pub net_weapon_exception: u8,
 
128
  /// If nonzero, streaming audio subsystem is created in InitHardware.
129
  pub speech_enabled: u8,
130
 
131
+ /// 0xDAA5-0xDAA7: Unknown
132
+ pub _unknown_daa5: [u8; 0xDAA8 - 0xDAA5],
133
+ /// 0xDAA8: Turn percentage raw value (i32). Converted to fixed-point: `(val << 16) / 100`.
134
+ pub turn_percentage_raw: i32,
135
 
136
  /// 0xDAAC: Landscape data path (passed to PCLandscape constructor).
137
  /// Points to a path string used for loading level terrain data.
 
149
  /// 0xDB08: Invisibility (weapon 0x42) mode flag (u32). Controls team-count
150
  /// vs network_ecx check for availability.
151
  pub invisibility_mode: u32,
152
+ /// 0xDB0C: Replay/network config flag (u8). Checked by InitGameState.
153
+ pub replay_config_flag: u8,
154
+ /// 0xDB0D-0xDB1B: Unknown
155
+ pub _unknown_db0d: [u8; 0xDB1C - 0xDB0D],
156
  /// 0xDB1C: Replay map sub-type (first DWORD of first payload).
157
  /// >= 1: map stored in playback.thm. Negative: inline map data.
158
  pub replay_map_type: i32,
 
178
  pub _unknown_df60: [u8; 0xEF60 - 0xDF60],
179
  /// 0xEF60: Cleared to 0 during replay loading.
180
  pub replay_field_ef60: u32,
181
+ /// 0xEF64-0xF33F: Unknown
182
+ pub _unknown_ef64: [u8; 0xF340 - 0xEF64],
183
+ /// 0xF340: State flag. InitGameState: `(this != 0) - 1` → wrapper+0xEC.
184
+ pub _field_f340: u32,
185
 
186
  /// 0xF344: Sound start frame threshold (i32). Sound is suppressed when
187
  /// DDGame.frame_counter < this value. Checked by IsSoundSuppressed and
 
192
  /// Checked by IsSoundSuppressed, DispatchGlobalSound, PlaySoundPooled_Direct.
193
  pub sound_mute: u8,
194
 
195
+ /// 0xF349-0xF34B: Unknown
196
+ pub _unknown_f349: [u8; 0xF34C - 0xF349],
197
+ /// 0xF34C: State field (i32). Set to -1 by InitGameState.
198
+ pub _field_f34c: i32,
199
+ /// 0xF350-0xF360: Unknown
200
+ pub _unknown_f350: [u8; 0xF361 - 0xF350],
201
+ /// 0xF361: Render phase config byte. Copied to DDGame.render_phase at init.
202
+ pub render_phase_cfg: u8,
203
  /// 0xF362: Unknown byte copied to DDGame+0x7788 during turn state init.
204
  pub _field_f362: u8,
205
+ /// 0xF363: Config byte. Copied to DDGame._field_7644 at init.
206
+ pub _field_f363: u8,
207
+ /// 0xF364: Config byte. Copied to DDGame._field_7648 at init.
208
+ pub _field_f364: u8,
209
+ /// 0xF365: Terrain config flag (u8). Bool-ified to wrapper+0x414.
210
+ pub _field_f365: u8,
211
+ /// 0xF366-0xF367: Unknown
212
+ pub _unknown_f366: [u8; 0xF368 - 0xF366],
213
+ /// 0xF368: Worm selection count config (i32). 0 = use default.
214
+ pub worm_select_cfg_a: i32,
215
+ /// 0xF36C: Worm selection count alt config (i32). -1 = use default (7).
216
+ pub worm_select_cfg_b: i32,
217
+ /// 0xF370: Display palette flag (u8). Bool-ified to palette+0x10 if headful.
218
+ pub _field_f370: u8,
219
+ /// 0xF371-0xF373: Unknown
220
+ pub _unknown_f371: [u8; 0xF374 - 0xF371],
221
 
222
  /// 0xF374: Display flags passed to DisplayGfx::Init.
223
  pub display_flags: u32,
 
308
 
309
  const _: () = assert!(core::mem::size_of::<GameInfo>() == 0xF91C);
310
 
311
+ impl GameInfo {
312
+ /// Access terrain_flag at offset 0xD98B (high byte of game_speed_config).
313
+ /// Set from map object during replay loading: 0 = cavern terrain.
314
+ pub fn terrain_flag(&self) -> u8 {
315
+ (self.game_speed_config >> 24) as u8
316
+ }
317
+ /// Set terrain_flag (high byte of game_speed_config at 0xD98B).
318
+ pub fn set_terrain_flag(&mut self, val: u8) {
319
+ self.game_speed_config = (self.game_speed_config & 0x00FF_FFFF) | ((val as i32) << 24);
320
+ }
321
+ }
322
+
323
  struct HexU32s<'a>(&'a [u32]);
324
 
325
  impl core::fmt::Debug for HexU32s<'_> {
crates/openwa-core/src/engine/game_state_init.rs CHANGED
@@ -7,6 +7,7 @@
7
  use crate::engine::ddgame::DDGame;
8
  use crate::engine::ddgame_wrapper::DDGameWrapper;
9
  use crate::engine::game_info::GameInfo;
 
10
  use crate::wa_alloc::wa_malloc;
11
 
12
  /// Pure Rust implementation of SpriteGfxTable__Init (0x541620).
@@ -478,7 +479,7 @@ pub unsafe fn init_turn_state(wrapper: *mut u8) {
478
 
479
  // DDGame._field_7788 = game_info._field_f362 (byte → u32)
480
  (*ddgame)._field_7788 = (*game_info)._field_f362 as u32;
481
- (*ddgame)._field_778c = 0x10000; // Fixed-point 1.0
482
  (*ddgame)._field_7790 = 0;
483
 
484
  // Camera center: (level_width << 16) / 2, (level_height << 16) / 2
@@ -603,14 +604,14 @@ pub unsafe fn init_game_state(wrapper: *mut DDGameWrapper) {
603
  let ddgame_raw = ddgame as *mut u8;
604
 
605
  // ===== Copy replay mode flags from GameInfo =====
606
- let game_info_sub = *(ddgame_raw.add(0x24) as *const *const u8);
607
- (*wrapper).replay_flag_a = *game_info_sub.add(0xDB08);
608
- (*wrapper).replay_flag_b = *game_info_sub.add(0xDB09);
609
 
610
  // ===== SpriteGfxTable__Init =====
611
  // ECX = DDGame+0x600, EDX = version-dependent count
612
  {
613
- let game_version = *(game_info_sub.add(0xD778) as *const i32);
614
  let count: u32 = if game_version >= 0x3C { 0x400 } else { 0x100 };
615
  sprite_gfx_table_init(ddgame_raw.add(0x600) as *mut u8, count);
616
  }
@@ -702,7 +703,7 @@ pub unsafe fn init_game_state(wrapper: *mut DDGameWrapper) {
702
 
703
  // ===== Allocate BufferObject (main_buffer at wrapper+0x0C) =====
704
  {
705
- let buf = allocate_buffer_object(ddgame_raw, game_info_sub);
706
  (*wrapper).main_buffer = buf;
707
  }
708
 
@@ -725,7 +726,7 @@ pub unsafe fn init_game_state(wrapper: *mut DDGameWrapper) {
725
  }
726
 
727
  // ===== Allocate state buffer (wrapper+0x48) =====
728
- (*wrapper).state_buffer = allocate_buffer_object(ddgame_raw, game_info_sub);
729
 
730
  // ===== Allocate statistics object (0xB94 bytes, wrapper+0x4C) =====
731
  {
@@ -763,13 +764,12 @@ pub unsafe fn init_game_state(wrapper: *mut DDGameWrapper) {
763
 
764
  // ===== Select vector normalize function based on game version =====
765
  {
766
- let game_version = *(game_info_sub.add(0xD778) as *const i32);
767
- let func_ptr = if game_version < 0x99 {
768
  rb(va::VECTOR_NORMALIZE_SIMPLE)
769
  } else {
770
  rb(va::VECTOR_NORMALIZE_OVERFLOW)
771
  };
772
- *(ddgame_raw.add(0x5C4) as *mut u32) = func_ptr;
773
  }
774
 
775
  // ===== Initialize sentinel fields =====
@@ -789,7 +789,7 @@ pub unsafe fn init_game_state(wrapper: *mut DDGameWrapper) {
789
 
790
  // ===== Resolution-dependent team render indices =====
791
  {
792
- let screen_height = *(ddgame_raw.add(0x77BC) as *const i32);
793
  if screen_height < 0x2D0 {
794
  // < 720px: smaller layout
795
  *(w.add(0x2B0) as *mut i32) = 0xC;
@@ -831,8 +831,8 @@ pub unsafe fn init_game_state(wrapper: *mut DDGameWrapper) {
831
 
832
  // ===== Team-to-slot mapping (conditional on network/replay mode) =====
833
  if *(ddgame_raw.add(0x1C) as *const u32) != 0 || (*wrapper).replay_flag_a != 0 {
834
- let team_count = *game_info_sub as i32; // first byte of game_info_sub = team count
835
- let exclude_team = *(game_info_sub.add(0xD9DC) as *const i8) as i32;
836
  let mut slot_idx = 0i32;
837
  let pivar8_base = w.add(0x38C) as *mut i32;
838
  let pivar7_base = w.add(0x3BC) as *mut i32;
@@ -866,11 +866,11 @@ pub unsafe fn init_game_state(wrapper: *mut DDGameWrapper) {
866
  *(w.add(0x418) as *mut u32) = 0;
867
 
868
  // Turn percentage: (game_info.turn_percentage << 16) / 100
869
- let turn_pct_raw = *(game_info_sub.add(0xDAA8) as *const i32);
870
  (*wrapper).turn_percentage = (turn_pct_raw << 16) / 100;
871
 
872
  // ===== Display setup (headful only) =====
873
- let is_headful = *(ddgame_raw.add(0x7EF8) as *const u32) != 0;
874
 
875
  if is_headful {
876
  // The headful display initialization involves complex usercall patterns
@@ -878,17 +878,15 @@ pub unsafe fn init_game_state(wrapper: *mut DDGameWrapper) {
878
  // ECX/EDX register params). For now, bridge to original display layer code
879
  // via the existing ported DisplayGfx constructors.
880
  // TODO: Port the headful display init with proper usercall bridges.
881
- init_game_state_display(wrapper, w, ddgame_raw, game_info_sub);
882
  }
883
 
884
  // ===== Worm selection count and terrain config =====
885
  {
886
- let setup_data = *(ddgame_raw.add(0x24) as *const *const u8);
887
- let worm_select_f368 = *(setup_data.add(0xF368) as *const i32);
888
  let min_teams = *(w.add(0x2A8) as *const i32);
889
  let team_count_cfg = *(w.add(0x2AC) as *const i32);
890
 
891
- let mut val = worm_select_f368;
892
  if val == 0 {
893
  val = team_count_cfg;
894
  } else if val < min_teams {
@@ -898,8 +896,7 @@ pub unsafe fn init_game_state(wrapper: *mut DDGameWrapper) {
898
  }
899
  *(w.add(0x2A0) as *mut i32) = val;
900
 
901
- let worm_select_f36c = *(setup_data.add(0xF36C) as *const i32);
902
- val = worm_select_f36c;
903
  if val == -1 {
904
  val = 7;
905
  } else if val < min_teams {
@@ -909,12 +906,11 @@ pub unsafe fn init_game_state(wrapper: *mut DDGameWrapper) {
909
  }
910
  *(w.add(0x2A4) as *mut i32) = val;
911
 
912
- *(w.add(0x414) as *mut u32) = (*(setup_data.add(0xF365) as *const u8) != 0) as u32;
913
 
914
  if is_headful {
915
  let palette_ptr = *(ddgame_raw as *const *mut u8);
916
- *(palette_ptr.add(0x10) as *mut u32) =
917
- (*(setup_data.add(0xF370) as *const u8) != 0) as u32;
918
  }
919
  }
920
 
@@ -942,19 +938,19 @@ pub unsafe fn init_game_state(wrapper: *mut DDGameWrapper) {
942
  init_landscape_flags(wrapper as *mut u8);
943
 
944
  // ===== Level bounds and camera initialization =====
945
- init_game_state_level_bounds(ddgame_raw, game_info_sub);
946
 
947
  // ===== Display objects for HUD (headful only) =====
948
  if is_headful {
949
- init_game_state_hud_objects(wrapper, ddgame_raw, game_info_sub);
950
  }
951
 
952
  // ===== Team name validation =====
953
- init_game_state_team_names(ddgame_raw, game_info_sub);
954
 
955
  // ===== Weapon and team initialization =====
956
- *(ddgame_raw.add(0x4624) as *mut u32) = 0;
957
- *(ddgame_raw.add(0x7EF4) as *mut u32) = 0;
958
 
959
  // InitWeaponTable
960
  {
@@ -974,22 +970,22 @@ pub unsafe fn init_game_state(wrapper: *mut DDGameWrapper) {
974
  }
975
 
976
  // ===== Version-dependent stack/render config =====
977
- init_game_state_version_config(ddgame_raw, game_info_sub);
978
 
979
  // ===== Water level and wind =====
980
- init_game_state_water_level(ddgame_raw, game_info_sub);
981
 
982
  // ===== Random bag initialization =====
983
- init_game_state_random_bags(ddgame_raw, game_info_sub);
984
 
985
  // ===== Turn state =====
986
  init_turn_state(wrapper as *mut u8);
987
 
988
  // ===== Weapon availability loop =====
989
- init_game_state_weapon_avail(wrapper, ddgame_raw, game_info_sub);
990
 
991
  // ===== Team/object tracking arrays =====
992
- init_game_state_tracking_arrays(ddgame_raw, game_info_sub);
993
 
994
  // ===== Statistics counters =====
995
  for i in 0..10u32 {
@@ -1020,7 +1016,7 @@ pub unsafe fn init_game_state(wrapper: *mut DDGameWrapper) {
1020
 
1021
  // ===== CTaskTurnGame constructor =====
1022
  {
1023
- let game_version = *(game_info_sub.add(0xD778) as *const i32);
1024
  if game_version == -2 {
1025
  // Online game: ECX = *net_bridge (dereferenced!) for the constructor
1026
  let mem = wa_malloc(0x324);
@@ -1069,8 +1065,7 @@ pub unsafe fn init_game_state(wrapper: *mut DDGameWrapper) {
1069
  {
1070
  let replay_a = (*wrapper).replay_flag_a;
1071
  let result = if replay_a == 0
1072
- || (*(game_info_sub.add(0xDB0C) as *const u8) != 0
1073
- && *(w.add(0x49C) as *const i32) > 0xC)
1074
  {
1075
  0u32
1076
  } else {
@@ -1084,10 +1079,9 @@ pub unsafe fn init_game_state(wrapper: *mut DDGameWrapper) {
1084
  *(w.add(0xE0) as *mut u32) = 0;
1085
 
1086
  // 0xEC: (game_info.f340 != 0) - 1 (i.e., 0 if nonzero, 0xFFFFFFFF if zero)
1087
- *(w.add(0xEC) as *mut u32) =
1088
- ((*(game_info_sub.add(0xF340) as *const u32) != 0) as u32).wrapping_sub(1);
1089
 
1090
- *(game_info_sub.add(0xF34C) as *mut i32) = -1;
1091
  (*wrapper).state_initialized = 1;
1092
 
1093
  // ===== Serialize initial game state =====
@@ -1143,7 +1137,7 @@ pub unsafe fn init_game_state(wrapper: *mut DDGameWrapper) {
1143
 
1144
  /// Allocate a BufferObject (0x48 bytes) with size computed from game setup.
1145
  /// Used for main_buffer and state_buffer.
1146
- unsafe fn allocate_buffer_object(ddgame_raw: *mut u8, game_info_sub: *const u8) -> *mut u8 {
1147
  use crate::address::va;
1148
  use crate::rebase::rb;
1149
  use crate::wa_alloc::wa_malloc;
@@ -1157,9 +1151,9 @@ unsafe fn allocate_buffer_object(ddgame_raw: *mut u8, game_info_sub: *const u8)
1157
  return core::ptr::null_mut();
1158
  }
1159
  // Compute size: num_teams * 0x450 + 0x4F178 + num_objects * 0x70
1160
- let num_teams = *(game_info_sub.add(0xD968) as *const u16) as u32;
1161
- let num_objects = *(game_info_sub.add(0xD994) as *const i32);
1162
- let size = num_teams * 0x450 + 0x4F178 + num_objects as u32 * 0x70;
1163
 
1164
  let ctor: unsafe extern "stdcall" fn(*mut u32, u32, u32) -> *mut u8 =
1165
  core::mem::transmute(rb(va::BUFFER_OBJECT_CONSTRUCTOR) as usize);
@@ -1300,7 +1294,7 @@ unsafe fn init_game_state_display(
1300
  wrapper: *mut DDGameWrapper,
1301
  w: *mut u8,
1302
  ddgame_raw: *mut u8,
1303
- _game_info_sub: *const u8,
1304
  ) {
1305
  use crate::address::va;
1306
  use crate::rebase::rb;
@@ -1329,7 +1323,7 @@ unsafe fn init_game_state_display(
1329
  *(w.add(0x388) as *mut i32) = screen_height;
1330
 
1331
  // Count active teams from slot_to_team array
1332
- let team_count = *_game_info_sub as u32;
1333
  let count_active = |w: *mut u8, base: u32| -> u32 {
1334
  let mut n = base;
1335
  if *(w.add(0x3AC) as *const i32) >= 0 {
@@ -1532,34 +1526,34 @@ unsafe fn create_camera_object(
1532
  }
1533
 
1534
  /// Initialize level bounds and camera center positions.
1535
- unsafe fn init_game_state_level_bounds(ddgame_raw: *mut u8, game_info_sub: *const u8) {
1536
- // DDGame+0x7794 / +0x7798 = 0
1537
- *(ddgame_raw.add(0x7794) as *mut u32) = 0;
1538
- *(ddgame_raw.add(0x7798) as *mut u32) = 0;
 
1539
 
1540
- let is_cavern = *(ddgame_raw.add(0x777C) as *const i32);
1541
  if is_cavern == 0 {
1542
  // Open-air level
1543
- *(ddgame_raw.add(0x779C) as *mut u32) = 0xF802_0000u32;
1544
- *(ddgame_raw.add(0x77A4) as *mut u32) = 0xF802_0000u32;
1545
- let level_width = *(ddgame_raw.add(0x77C0) as *const i32);
1546
- *(ddgame_raw.add(0x77A0) as *mut i32) = (level_width + 0x7FE) * 0x10000;
1547
  } else {
1548
  // Cavern level
1549
- let game_version = *(game_info_sub.add(0xD778) as *const i32);
1550
- let bound = if game_version >= -1 { 0x20000u32 } else { 0u32 };
1551
- *(ddgame_raw.add(0x779C) as *mut u32) = bound;
1552
- *(ddgame_raw.add(0x77A4) as *mut u32) = 0x20000;
1553
- let level_width = *(ddgame_raw.add(0x77C0) as *const i32);
1554
- *(ddgame_raw.add(0x77A0) as *mut u32) = (level_width as u32)
1555
- .wrapping_mul(0x10000)
1556
- .wrapping_sub(bound);
1557
  }
1558
 
1559
  // Camera center initialization (4 viewports)
1560
  for viewport_offset in [0x8CBCu32, 0x8CCCu32, 0x8CDCu32, 0x8CECu32] {
1561
- let level_w = *(ddgame_raw.add(0x77C0) as *const i32);
1562
- let level_h = *(ddgame_raw.add(0x77C4) as *const i32);
1563
  let cx = (level_w << 16) / 2;
1564
  let cy = (level_h * 0x10000) / 2;
1565
  *(ddgame_raw.add(viewport_offset as usize + 0x18) as *mut i32) = cx;
@@ -1569,14 +1563,14 @@ unsafe fn init_game_state_level_bounds(ddgame_raw: *mut u8, game_info_sub: *cons
1569
  }
1570
 
1571
  // Map dimension fields
1572
- *(ddgame_raw.add(0x77CC) as *mut u32) = 0x30D4;
1573
- *(ddgame_raw.add(0x77D0) as *mut u32) = *(ddgame_raw.add(0x77C4) as *const u32);
1574
 
1575
- let game_version = *(game_info_sub.add(0xD778) as *const i32);
1576
  if game_version > 0x32 {
1577
- let level_w = *(ddgame_raw.add(0x77C0) as *const i32);
1578
- *(ddgame_raw.add(0x77CC) as *mut i32) = level_w + 0x2954;
1579
- *(ddgame_raw.add(0x77D0) as *mut u32) = 0x2B8;
1580
  }
1581
  }
1582
 
@@ -1584,7 +1578,7 @@ unsafe fn init_game_state_level_bounds(ddgame_raw: *mut u8, game_info_sub: *cons
1584
  unsafe fn init_game_state_hud_objects(
1585
  _wrapper: *mut DDGameWrapper,
1586
  ddgame_raw: *mut u8,
1587
- _game_info_sub: *const u8,
1588
  ) {
1589
  use crate::address::va;
1590
  use crate::rebase::rb;
@@ -1632,18 +1626,21 @@ unsafe fn init_game_state_hud_objects(
1632
  }
1633
 
1634
  /// Validate team names — check for CPU team markers.
1635
- unsafe fn init_game_state_team_names(ddgame_raw: *mut u8, game_info_sub: *const u8) {
 
 
1636
  // Set initial team color from game_info
1637
- *(ddgame_raw.add(0x7370) as *mut u32) = *(game_info_sub.add(0xD924) as *const u8) as u32;
1638
 
1639
  // Check team names for CPU marker (high bit set)
1640
- let team_count = *(game_info_sub.add(0x44C) as *const u8);
 
1641
  if team_count != 0 {
1642
  for i in 0..team_count as usize {
1643
- let name_byte = *(game_info_sub.add(0x450 + i * 3000) as *const u8);
1644
  if name_byte >= 0x80 {
1645
  // CPU team detected — set team color to -1
1646
- *(ddgame_raw.add(0x7370) as *mut u32) = 0xFFFF_FFFF;
1647
  break;
1648
  }
1649
  }
@@ -1651,26 +1648,25 @@ unsafe fn init_game_state_team_names(ddgame_raw: *mut u8, game_info_sub: *const
1651
  }
1652
 
1653
  /// Version-dependent configuration (stack size, rendering constants).
1654
- unsafe fn init_game_state_version_config(ddgame_raw: *mut u8, game_info_sub: *const u8) {
1655
- let game_version = *(game_info_sub.add(0xD778) as *const i32);
 
1656
 
1657
- // Stack height: ((game_version < -1) - 1) & 0x100 + 0x700
1658
- // game_version < -1: result = 0x700
1659
- // game_version >= -1: result = 0x800
1660
- let stack_val: u32 = if game_version < -1 { 0x700 } else { 0x800 };
1661
- *(ddgame_raw.add(0x5C8) as *mut u32) = stack_val;
1662
 
1663
  // Copy RNG seed
1664
- *(ddgame_raw.add(0x45EC) as *mut u32) = *(game_info_sub.add(0xD774) as *const u32);
1665
- *(ddgame_raw.add(0x45F0) as *mut u32) = *(game_info_sub.add(0xD774) as *const u32);
 
1666
 
1667
  // Zero various fields
1668
- *(ddgame_raw.add(0x5CC) as *mut u32) = 0;
1669
- *(ddgame_raw.add(0x5D4) as *mut u32) = 0;
1670
- *(ddgame_raw.add(0x5D8) as *mut u32) = 0;
1671
- *(ddgame_raw.add(0x5DC) as *mut u32) = 0;
1672
- *(ddgame_raw.add(0x7E48) as *mut u32) = 0;
1673
- *(ddgame_raw.add(0x5D0) as *mut u32) = 0;
1674
 
1675
  // Zero counter arrays (6 entries each × 3 blocks)
1676
  for block_base in [0x7EA8u32, 0x7EC0, 0x7ED8] {
@@ -1680,28 +1676,29 @@ unsafe fn init_game_state_version_config(ddgame_raw: *mut u8, game_info_sub: *co
1680
  }
1681
 
1682
  // Zero more fields
1683
- *(ddgame_raw.add(0x45E0) as *mut u32) = 0;
1684
- *(ddgame_raw.add(0x45E4) as *mut u32) = 0;
1685
- *(ddgame_raw.add(0x45E8) as *mut u32) = 0;
1686
  }
1687
 
1688
  /// Water level and boundary calculations.
1689
- unsafe fn init_game_state_water_level(ddgame_raw: *mut u8, game_info_sub: *const u8) {
1690
- // Water level: (100 - water_pct) * level_height / 100
1691
- let water_pct = *(ddgame_raw.add(0x7780) as *const i32);
1692
- let level_height = *(ddgame_raw.add(0x77C4) as *const i32);
1693
- let water_level = ((100 - water_pct) * level_height) / 100;
1694
- *(ddgame_raw.add(0x5E0) as *mut i32) = water_level;
 
1695
 
1696
  // Clamp to 0 for newer versions
1697
- let game_version = *(game_info_sub.add(0xD778) as *const i32);
1698
  if water_level < 0 && game_version > 0x179 {
1699
- *(ddgame_raw.add(0x5E0) as *mut i32) = 0;
1700
  }
1701
 
1702
  // Max Y boundary
1703
- let water_val = *(ddgame_raw.add(0x5E0) as *const i32);
1704
- let min_y = *(ddgame_raw.add(0x77A4) as *const i32);
1705
  let max_y_candidate = (water_val + 0xA0) * 0x10000;
1706
  let min_y_plus = min_y + 0x300_0000;
1707
  let max_y = if max_y_candidate > min_y_plus {
@@ -1709,32 +1706,32 @@ unsafe fn init_game_state_water_level(ddgame_raw: *mut u8, game_info_sub: *const
1709
  } else {
1710
  min_y_plus
1711
  };
1712
- *(ddgame_raw.add(0x77A8) as *mut i32) = max_y;
1713
 
1714
  // Clamp for newer versions
1715
  if game_version > 0x11E && (max_y >> 16) + 0x28 > 0x7FFF {
1716
- *(ddgame_raw.add(0x77A8) as *mut u32) = 0x7FD7_0000;
1717
  }
1718
 
1719
  // Derived water fields
1720
- *(ddgame_raw.add(0x5E4) as *mut i32) = *(ddgame_raw.add(0x77AA) as *const i16) as i32 + 0x28;
1721
- *(ddgame_raw.add(0x5F8) as *mut u32) = 0;
1722
- *(ddgame_raw.add(0x5E8) as *mut u32) = *(ddgame_raw.add(0x5E0) as *const u32);
1723
- *(ddgame_raw.add(0x5EC) as *mut u32) = 0;
1724
 
1725
  // Terrain type percentages
1726
- *(ddgame_raw.add(0x45D4) as *mut u32) = *(game_info_sub.add(0xD955) as *const u8) as u32;
1727
- *(ddgame_raw.add(0x45D8) as *mut u32) = *(game_info_sub.add(0xD958) as *const u8) as u32;
1728
- *(ddgame_raw.add(0x45DC) as *mut u32) = *(game_info_sub.add(0xD957) as *const u8) as u32;
1729
 
1730
  // Game state flags
1731
- *(ddgame_raw.add(0x5F0) as *mut u32) = 1;
1732
- *(ddgame_raw.add(0x5F4) as *mut u32) = 100;
1733
- *(ddgame_raw.add(0x5FC) as *mut u32) = 0;
1734
  }
1735
 
1736
  /// Random bag initialization (land, mine, barrel, weapon).
1737
- unsafe fn init_game_state_random_bags(ddgame_raw: *mut u8, game_info_sub: *const u8) {
1738
  // Random bag at DDGame+0x360C (5 zeroes + 1 one)
1739
  {
1740
  let write_idx_ptr = ddgame_raw.add(0x379C) as *mut i32;
@@ -1754,38 +1751,38 @@ unsafe fn init_game_state_random_bags(ddgame_raw: *mut u8, game_info_sub: *const
1754
 
1755
  // Terrain type percentages validation and clamping
1756
  {
1757
- let pct_land = *(game_info_sub.add(0xD938) as *const u8) as i32;
1758
- let pct_mine = *(game_info_sub.add(0xD939) as *const u8) as i32;
1759
- let pct_barrel = *(game_info_sub.add(0xD93A) as *const u8) as i32;
1760
 
1761
  let mut remaining = 100 - pct_land;
1762
  if remaining < 0 {
1763
  // Land > 100%, clamp all
1764
- *(game_info_sub.add(0xD938) as *mut u8) = 100;
1765
- *(game_info_sub.add(0xD939) as *mut u8) = 0;
1766
- *(game_info_sub.add(0xD93A) as *mut u8) = 0;
1767
  remaining = 0;
1768
  } else {
1769
  remaining -= pct_mine;
1770
  if remaining < 0 {
1771
- *(game_info_sub.add(0xD939) as *mut u8) = (pct_mine + remaining) as u8;
1772
- *(game_info_sub.add(0xD93A) as *mut u8) = 0;
1773
  remaining = 0;
1774
  } else {
1775
  remaining -= pct_barrel;
1776
  if remaining < 0 {
1777
- *(game_info_sub.add(0xD93A) as *mut u8) = (pct_barrel + remaining) as u8;
1778
  }
1779
  }
1780
  }
1781
 
1782
  // Extended terrain type from game_info
1783
- let ext_type = *(game_info_sub.add(0xD96B) as *const u8);
1784
- let ext_pct = *(game_info_sub.add(0xD96A) as *const u8) as i32;
1785
  if ext_type == 0 {
1786
- *(game_info_sub.add(0xD96A) as *mut u8) = remaining as u8;
1787
  } else if remaining < ext_pct {
1788
- *(game_info_sub.add(0xD96A) as *mut u8) = 0;
1789
  }
1790
  }
1791
 
@@ -1794,7 +1791,7 @@ unsafe fn init_game_state_random_bags(ddgame_raw: *mut u8, game_info_sub: *const
1794
  let write_idx_ptr = ddgame_raw.add(0x4114) as *mut i32;
1795
 
1796
  // Land entries (type 1)
1797
- let count = *(game_info_sub.add(0xD938) as *const u8) as u32;
1798
  let mut idx = *write_idx_ptr;
1799
  if (idx as u32 + count) < 0x65 {
1800
  for _ in 0..count {
@@ -1805,7 +1802,7 @@ unsafe fn init_game_state_random_bags(ddgame_raw: *mut u8, game_info_sub: *const
1805
  }
1806
 
1807
  // Mine entries (type 2)
1808
- let count = *(game_info_sub.add(0xD939) as *const u8) as u32;
1809
  idx = *write_idx_ptr;
1810
  if (idx as u32 + count) < 0x65 {
1811
  for _ in 0..count {
@@ -1816,7 +1813,7 @@ unsafe fn init_game_state_random_bags(ddgame_raw: *mut u8, game_info_sub: *const
1816
  }
1817
 
1818
  // Barrel entries (type 4)
1819
- let count = *(game_info_sub.add(0xD93A) as *const u8) as u32;
1820
  idx = *write_idx_ptr;
1821
  if (idx as u32 + count) < 0x65 {
1822
  for _ in 0..count {
@@ -1827,7 +1824,7 @@ unsafe fn init_game_state_random_bags(ddgame_raw: *mut u8, game_info_sub: *const
1827
  }
1828
 
1829
  // Extended entries (type 0)
1830
- let count = *(game_info_sub.add(0xD96A) as *const u8) as u32;
1831
  idx = *write_idx_ptr;
1832
  if (idx as u32 + count) < 0x65 {
1833
  for _ in 0..count {
@@ -1870,9 +1867,9 @@ unsafe fn init_game_state_random_bags(ddgame_raw: *mut u8, game_info_sub: *const
1870
  unsafe fn init_game_state_weapon_avail(
1871
  _wrapper: *mut DDGameWrapper,
1872
  ddgame_raw: *mut u8,
1873
- game_info_sub: *const u8,
1874
  ) {
1875
- let game_version = *(game_info_sub.add(0xD778) as *const i32);
1876
 
1877
  for weapon_id in 1..0x47i32 {
1878
  let avail = check_weapon_avail(ddgame_raw as *mut DDGame, weapon_id as u32);
@@ -1894,47 +1891,50 @@ unsafe fn init_game_state_weapon_avail(
1894
  }
1895
 
1896
  /// Allocate team/object tracking arrays and initialize the 0x51C object.
1897
- unsafe fn init_game_state_tracking_arrays(ddgame_raw: *mut u8, game_info_sub: *const u8) {
1898
  use crate::wa_alloc::wa_malloc;
1899
 
 
 
1900
  // Game speed
1901
- *(ddgame_raw.add(0x72D8) as *mut u32) = 0x10000;
1902
 
1903
- let is_headful = *(ddgame_raw.add(0x7EF8) as *const u32) != 0;
1904
  if !is_headful {
1905
- *(ddgame_raw.add(0x72DC) as *mut u32) = 0x1000_0000;
1906
  } else {
1907
- *(ddgame_raw.add(0x72DC) as *mut u32) = *(game_info_sub.add(0xD988) as *const u32);
1908
  }
1909
 
1910
  // Network speed callback
1911
- if *(ddgame_raw.add(8) as *const u32) != 0 {
1912
- let snd_ptr = *(ddgame_raw.add(8) as *const *const u8);
 
1913
  let vtable = *(snd_ptr as *const *const u32);
1914
  let vmethod: unsafe extern "thiscall" fn(*const u8, u32, u32) =
1915
  core::mem::transmute(*vtable.add(2));
1916
  vmethod(
1917
  snd_ptr,
1918
- *(ddgame_raw.add(0x72D8) as *const u32),
1919
- *(ddgame_raw.add(0x72DC) as *const u32),
1920
  );
1921
  }
1922
 
1923
  // Misc fields
1924
- *(ddgame_raw.add(0x8140) as *mut u32) = 0;
1925
- *(ddgame_raw.add(0x764C) as *mut u32) = *(game_info_sub.add(0xF361) as *const u8) as u32;
1926
- *(ddgame_raw.add(0x7640) as *mut u32) = 0;
1927
- *(ddgame_raw.add(0x7644) as *mut u32) = *(game_info_sub.add(0xF363) as *const u8) as u32;
1928
- *(ddgame_raw.add(0x7648) as *mut u32) = *(game_info_sub.add(0xF364) as *const u8) as u32;
1929
 
1930
  // Render state
1931
- *(ddgame_raw.add(0x7390) as *mut u32) = 0;
1932
- *(ddgame_raw.add(0x7394) as *mut u32) = 0x10000;
1933
- *(ddgame_raw.add(0x7398) as *mut u32) = 0;
1934
 
1935
  // Allocate team tracking arrays (DDGame+0x514, DDGame+0x518)
1936
  {
1937
- let count_a = *(game_info_sub.add(0xD990) as *const u32);
1938
  let size = count_a.wrapping_mul(4);
1939
  let arr = wa_malloc(size);
1940
  *(ddgame_raw.add(0x514) as *mut *mut u8) = arr;
@@ -1943,7 +1943,7 @@ unsafe fn init_game_state_tracking_arrays(ddgame_raw: *mut u8, game_info_sub: *c
1943
  }
1944
  }
1945
  {
1946
- let count_b = *(game_info_sub.add(0xD994) as *const u32);
1947
  let size = count_b.wrapping_mul(4);
1948
  let arr = wa_malloc(size);
1949
  *(ddgame_raw.add(0x518) as *mut *mut u8) = arr;
 
7
  use crate::engine::ddgame::DDGame;
8
  use crate::engine::ddgame_wrapper::DDGameWrapper;
9
  use crate::engine::game_info::GameInfo;
10
+ use crate::fixed::Fixed;
11
  use crate::wa_alloc::wa_malloc;
12
 
13
  /// Pure Rust implementation of SpriteGfxTable__Init (0x541620).
 
479
 
480
  // DDGame._field_7788 = game_info._field_f362 (byte → u32)
481
  (*ddgame)._field_7788 = (*game_info)._field_f362 as u32;
482
+ (*ddgame)._field_778c = Fixed::ONE;
483
  (*ddgame)._field_7790 = 0;
484
 
485
  // Camera center: (level_width << 16) / 2, (level_height << 16) / 2
 
604
  let ddgame_raw = ddgame as *mut u8;
605
 
606
  // ===== Copy replay mode flags from GameInfo =====
607
+ let game_info = (*ddgame).game_info;
608
+ (*wrapper).replay_flag_a = (*game_info).invisibility_mode as u8;
609
+ (*wrapper).replay_flag_b = ((*game_info).invisibility_mode >> 8) as u8;
610
 
611
  // ===== SpriteGfxTable__Init =====
612
  // ECX = DDGame+0x600, EDX = version-dependent count
613
  {
614
+ let game_version = (*game_info).game_version;
615
  let count: u32 = if game_version >= 0x3C { 0x400 } else { 0x100 };
616
  sprite_gfx_table_init(ddgame_raw.add(0x600) as *mut u8, count);
617
  }
 
703
 
704
  // ===== Allocate BufferObject (main_buffer at wrapper+0x0C) =====
705
  {
706
+ let buf = allocate_buffer_object(ddgame_raw, game_info);
707
  (*wrapper).main_buffer = buf;
708
  }
709
 
 
726
  }
727
 
728
  // ===== Allocate state buffer (wrapper+0x48) =====
729
+ (*wrapper).state_buffer = allocate_buffer_object(ddgame_raw, game_info);
730
 
731
  // ===== Allocate statistics object (0xB94 bytes, wrapper+0x4C) =====
732
  {
 
764
 
765
  // ===== Select vector normalize function based on game version =====
766
  {
767
+ let game_version = (*game_info).game_version;
768
+ (*ddgame).vector_normalize_fn = if game_version < 0x99 {
769
  rb(va::VECTOR_NORMALIZE_SIMPLE)
770
  } else {
771
  rb(va::VECTOR_NORMALIZE_OVERFLOW)
772
  };
 
773
  }
774
 
775
  // ===== Initialize sentinel fields =====
 
789
 
790
  // ===== Resolution-dependent team render indices =====
791
  {
792
+ let screen_height = (*ddgame).screen_height_pixels;
793
  if screen_height < 0x2D0 {
794
  // < 720px: smaller layout
795
  *(w.add(0x2B0) as *mut i32) = 0xC;
 
831
 
832
  // ===== Team-to-slot mapping (conditional on network/replay mode) =====
833
  if *(ddgame_raw.add(0x1C) as *const u32) != 0 || (*wrapper).replay_flag_a != 0 {
834
+ let team_count = (*game_info).num_teams as i32;
835
+ let exclude_team = (*game_info).starting_team_index as i32;
836
  let mut slot_idx = 0i32;
837
  let pivar8_base = w.add(0x38C) as *mut i32;
838
  let pivar7_base = w.add(0x3BC) as *mut i32;
 
866
  *(w.add(0x418) as *mut u32) = 0;
867
 
868
  // Turn percentage: (game_info.turn_percentage << 16) / 100
869
+ let turn_pct_raw = (*game_info).turn_percentage_raw;
870
  (*wrapper).turn_percentage = (turn_pct_raw << 16) / 100;
871
 
872
  // ===== Display setup (headful only) =====
873
+ let is_headful = (*ddgame).sound_available != 0;
874
 
875
  if is_headful {
876
  // The headful display initialization involves complex usercall patterns
 
878
  // ECX/EDX register params). For now, bridge to original display layer code
879
  // via the existing ported DisplayGfx constructors.
880
  // TODO: Port the headful display init with proper usercall bridges.
881
+ init_game_state_display(wrapper, w, ddgame_raw, game_info);
882
  }
883
 
884
  // ===== Worm selection count and terrain config =====
885
  {
886
+ let mut val = (*game_info).worm_select_cfg_a;
 
887
  let min_teams = *(w.add(0x2A8) as *const i32);
888
  let team_count_cfg = *(w.add(0x2AC) as *const i32);
889
 
 
890
  if val == 0 {
891
  val = team_count_cfg;
892
  } else if val < min_teams {
 
896
  }
897
  *(w.add(0x2A0) as *mut i32) = val;
898
 
899
+ val = (*game_info).worm_select_cfg_b;
 
900
  if val == -1 {
901
  val = 7;
902
  } else if val < min_teams {
 
906
  }
907
  *(w.add(0x2A4) as *mut i32) = val;
908
 
909
+ *(w.add(0x414) as *mut u32) = ((*game_info)._field_f365 != 0) as u32;
910
 
911
  if is_headful {
912
  let palette_ptr = *(ddgame_raw as *const *mut u8);
913
+ *(palette_ptr.add(0x10) as *mut u32) = ((*game_info)._field_f370 != 0) as u32;
 
914
  }
915
  }
916
 
 
938
  init_landscape_flags(wrapper as *mut u8);
939
 
940
  // ===== Level bounds and camera initialization =====
941
+ init_game_state_level_bounds(ddgame_raw, game_info);
942
 
943
  // ===== Display objects for HUD (headful only) =====
944
  if is_headful {
945
+ init_game_state_hud_objects(wrapper, ddgame_raw, game_info);
946
  }
947
 
948
  // ===== Team name validation =====
949
+ init_game_state_team_names(ddgame_raw, game_info);
950
 
951
  // ===== Weapon and team initialization =====
952
+ (*ddgame).hud_status_code = 0;
953
+ (*ddgame).hud_status_text = core::ptr::null();
954
 
955
  // InitWeaponTable
956
  {
 
970
  }
971
 
972
  // ===== Version-dependent stack/render config =====
973
+ init_game_state_version_config(ddgame_raw, game_info);
974
 
975
  // ===== Water level and wind =====
976
+ init_game_state_water_level(ddgame, game_info);
977
 
978
  // ===== Random bag initialization =====
979
+ init_game_state_random_bags(ddgame_raw, game_info);
980
 
981
  // ===== Turn state =====
982
  init_turn_state(wrapper as *mut u8);
983
 
984
  // ===== Weapon availability loop =====
985
+ init_game_state_weapon_avail(wrapper, ddgame_raw, game_info);
986
 
987
  // ===== Team/object tracking arrays =====
988
+ init_game_state_tracking_arrays(ddgame_raw, game_info);
989
 
990
  // ===== Statistics counters =====
991
  for i in 0..10u32 {
 
1016
 
1017
  // ===== CTaskTurnGame constructor =====
1018
  {
1019
+ let game_version = (*game_info).game_version;
1020
  if game_version == -2 {
1021
  // Online game: ECX = *net_bridge (dereferenced!) for the constructor
1022
  let mem = wa_malloc(0x324);
 
1065
  {
1066
  let replay_a = (*wrapper).replay_flag_a;
1067
  let result = if replay_a == 0
1068
+ || ((*game_info).replay_config_flag != 0 && *(w.add(0x49C) as *const i32) > 0xC)
 
1069
  {
1070
  0u32
1071
  } else {
 
1079
  *(w.add(0xE0) as *mut u32) = 0;
1080
 
1081
  // 0xEC: (game_info.f340 != 0) - 1 (i.e., 0 if nonzero, 0xFFFFFFFF if zero)
1082
+ *(w.add(0xEC) as *mut u32) = (((*game_info)._field_f340 != 0) as u32).wrapping_sub(1);
 
1083
 
1084
+ (*game_info)._field_f34c = -1;
1085
  (*wrapper).state_initialized = 1;
1086
 
1087
  // ===== Serialize initial game state =====
 
1137
 
1138
  /// Allocate a BufferObject (0x48 bytes) with size computed from game setup.
1139
  /// Used for main_buffer and state_buffer.
1140
+ unsafe fn allocate_buffer_object(ddgame_raw: *mut u8, game_info: *const GameInfo) -> *mut u8 {
1141
  use crate::address::va;
1142
  use crate::rebase::rb;
1143
  use crate::wa_alloc::wa_malloc;
 
1151
  return core::ptr::null_mut();
1152
  }
1153
  // Compute size: num_teams * 0x450 + 0x4F178 + num_objects * 0x70
1154
+ let num_teams = (*game_info).num_teams_alloc as u32;
1155
+ let num_objects = (*game_info).object_slot_count;
1156
+ let size = num_teams * 0x450 + 0x4F178 + num_objects * 0x70;
1157
 
1158
  let ctor: unsafe extern "stdcall" fn(*mut u32, u32, u32) -> *mut u8 =
1159
  core::mem::transmute(rb(va::BUFFER_OBJECT_CONSTRUCTOR) as usize);
 
1294
  wrapper: *mut DDGameWrapper,
1295
  w: *mut u8,
1296
  ddgame_raw: *mut u8,
1297
+ game_info: *const GameInfo,
1298
  ) {
1299
  use crate::address::va;
1300
  use crate::rebase::rb;
 
1323
  *(w.add(0x388) as *mut i32) = screen_height;
1324
 
1325
  // Count active teams from slot_to_team array
1326
+ let team_count = (*game_info).num_teams as u32;
1327
  let count_active = |w: *mut u8, base: u32| -> u32 {
1328
  let mut n = base;
1329
  if *(w.add(0x3AC) as *const i32) >= 0 {
 
1526
  }
1527
 
1528
  /// Initialize level bounds and camera center positions.
1529
+ unsafe fn init_game_state_level_bounds(ddgame_raw: *mut u8, game_info: *const GameInfo) {
1530
+ let ddgame = ddgame_raw as *mut DDGame;
1531
+
1532
+ (*ddgame)._field_7794 = 0;
1533
+ (*ddgame)._field_7798 = 0;
1534
 
1535
+ let is_cavern = (*ddgame).level_width_raw as i32;
1536
  if is_cavern == 0 {
1537
  // Open-air level
1538
+ (*ddgame).level_bound_min_x = Fixed(0xF802_0000u32 as i32);
1539
+ (*ddgame).level_bound_min_y = Fixed(0xF802_0000u32 as i32);
1540
+ let level_width = (*ddgame).level_width as i32;
1541
+ (*ddgame).level_bound_max_x = Fixed((level_width + 0x7FE) * 0x10000);
1542
  } else {
1543
  // Cavern level
1544
+ let game_version = (*game_info).game_version;
1545
+ let bound = if game_version >= -1 { 0x20000i32 } else { 0i32 };
1546
+ (*ddgame).level_bound_min_x = Fixed(bound);
1547
+ (*ddgame).level_bound_min_y = Fixed(0x20000);
1548
+ let level_width = (*ddgame).level_width;
1549
+ (*ddgame).level_bound_max_x =
1550
+ Fixed((level_width.wrapping_mul(0x10000)).wrapping_sub(bound as u32) as i32);
 
1551
  }
1552
 
1553
  // Camera center initialization (4 viewports)
1554
  for viewport_offset in [0x8CBCu32, 0x8CCCu32, 0x8CDCu32, 0x8CECu32] {
1555
+ let level_w = (*ddgame).level_width as i32;
1556
+ let level_h = (*ddgame).level_height as i32;
1557
  let cx = (level_w << 16) / 2;
1558
  let cy = (level_h * 0x10000) / 2;
1559
  *(ddgame_raw.add(viewport_offset as usize + 0x18) as *mut i32) = cx;
 
1563
  }
1564
 
1565
  // Map dimension fields
1566
+ (*ddgame).map_boundary_width = 0x30D4;
1567
+ (*ddgame).map_boundary_height = (*ddgame).level_height;
1568
 
1569
+ let game_version = (*game_info).game_version;
1570
  if game_version > 0x32 {
1571
+ let level_w = (*ddgame).level_width as i32;
1572
+ (*ddgame).map_boundary_width = (level_w + 0x2954) as u32;
1573
+ (*ddgame).map_boundary_height = 0x2B8;
1574
  }
1575
  }
1576
 
 
1578
  unsafe fn init_game_state_hud_objects(
1579
  _wrapper: *mut DDGameWrapper,
1580
  ddgame_raw: *mut u8,
1581
+ _game_info: *const GameInfo,
1582
  ) {
1583
  use crate::address::va;
1584
  use crate::rebase::rb;
 
1626
  }
1627
 
1628
  /// Validate team names — check for CPU team markers.
1629
+ unsafe fn init_game_state_team_names(ddgame_raw: *mut u8, game_info: *mut GameInfo) {
1630
+ let ddgame = ddgame_raw as *mut DDGame;
1631
+
1632
  // Set initial team color from game_info
1633
+ (*ddgame).team_color = (*game_info).team_color_source as u32;
1634
 
1635
  // Check team names for CPU marker (high bit set)
1636
+ let team_count = (*game_info).speech_team_count;
1637
+ let gi_raw = game_info as *const u8;
1638
  if team_count != 0 {
1639
  for i in 0..team_count as usize {
1640
+ let name_byte = *(gi_raw.add(0x450 + i * 3000));
1641
  if name_byte >= 0x80 {
1642
  // CPU team detected — set team color to -1
1643
+ (*ddgame).team_color = 0xFFFF_FFFF;
1644
  break;
1645
  }
1646
  }
 
1648
  }
1649
 
1650
  /// Version-dependent configuration (stack size, rendering constants).
1651
+ unsafe fn init_game_state_version_config(ddgame_raw: *mut u8, game_info: *const GameInfo) {
1652
+ let ddgame = ddgame_raw as *mut DDGame;
1653
+ let game_version = (*game_info).game_version;
1654
 
1655
+ // Stack height: game_version < -1 → 0x700, >= -1 → 0x800
1656
+ (*ddgame).stack_height = if game_version < -1 { 0x700 } else { 0x800 };
 
 
 
1657
 
1658
  // Copy RNG seed
1659
+ let rng_seed = (*game_info).rng_seed;
1660
+ (*ddgame).game_rng = rng_seed;
1661
+ *(ddgame_raw.add(0x45F0) as *mut u32) = rng_seed;
1662
 
1663
  // Zero various fields
1664
+ (*ddgame).frame_counter = 0;
1665
+ (*ddgame)._field_5d4 = 0;
1666
+ (*ddgame)._field_5d8 = 0;
1667
+ (*ddgame)._field_5dc = 0;
1668
+ (*ddgame)._field_7e4c = 0;
1669
+ (*ddgame)._field_5d0 = 0;
1670
 
1671
  // Zero counter arrays (6 entries each × 3 blocks)
1672
  for block_base in [0x7EA8u32, 0x7EC0, 0x7ED8] {
 
1676
  }
1677
 
1678
  // Zero more fields
1679
+ (*ddgame)._field_45e0 = 0;
1680
+ (*ddgame)._field_45e4 = 0;
1681
+ (*ddgame)._field_45e8 = 0;
1682
  }
1683
 
1684
  /// Water level and boundary calculations.
1685
+ unsafe fn init_game_state_water_level(ddgame: *mut DDGame, game_info: *mut GameInfo) {
1686
+ // Water level: (100 - level_height_raw) * level_height / 100
1687
+ // level_height_raw appears to encode a water height percentage here
1688
+ let height_raw = (*ddgame).level_height_raw as i32;
1689
+ let level_height = (*ddgame).level_height as i32;
1690
+ let water_level = ((100 - height_raw) * level_height) / 100;
1691
+ (*ddgame).water_level = water_level;
1692
 
1693
  // Clamp to 0 for newer versions
1694
+ let game_version = (*game_info).game_version;
1695
  if water_level < 0 && game_version > 0x179 {
1696
+ (*ddgame).water_level = 0;
1697
  }
1698
 
1699
  // Max Y boundary
1700
+ let water_val = (*ddgame).water_level;
1701
+ let min_y = (*ddgame).level_bound_min_y.0;
1702
  let max_y_candidate = (water_val + 0xA0) * 0x10000;
1703
  let min_y_plus = min_y + 0x300_0000;
1704
  let max_y = if max_y_candidate > min_y_plus {
 
1706
  } else {
1707
  min_y_plus
1708
  };
1709
+ (*ddgame).level_bound_max_y = Fixed(max_y);
1710
 
1711
  // Clamp for newer versions
1712
  if game_version > 0x11E && (max_y >> 16) + 0x28 > 0x7FFF {
1713
+ (*ddgame).level_bound_max_y = Fixed(0x7FD7_0000u32 as i32);
1714
  }
1715
 
1716
  // Derived water fields
1717
+ (*ddgame).water_kill_y = (*ddgame).level_bound_max_y.to_int() + 0x28;
1718
+ (*ddgame)._field_5f8 = 0;
1719
+ (*ddgame).water_level_initial = (*ddgame).water_level;
1720
+ (*ddgame)._field_5ec = 0;
1721
 
1722
  // Terrain type percentages
1723
+ (*ddgame).terrain_pct_a = (*game_info).terrain_cfg_a as u32;
1724
+ (*ddgame).terrain_pct_b = (*game_info).terrain_cfg_b as u32;
1725
+ (*ddgame).terrain_pct_c = (*game_info).terrain_cfg_c as u32;
1726
 
1727
  // Game state flags
1728
+ (*ddgame)._field_5f0 = 1;
1729
+ (*ddgame)._field_5f4 = 100;
1730
+ (*ddgame)._field_5fc = 0;
1731
  }
1732
 
1733
  /// Random bag initialization (land, mine, barrel, weapon).
1734
+ unsafe fn init_game_state_random_bags(ddgame_raw: *mut u8, game_info: *mut GameInfo) {
1735
  // Random bag at DDGame+0x360C (5 zeroes + 1 one)
1736
  {
1737
  let write_idx_ptr = ddgame_raw.add(0x379C) as *mut i32;
 
1751
 
1752
  // Terrain type percentages validation and clamping
1753
  {
1754
+ let pct_land = (*game_info).drop_pct_land as i32;
1755
+ let pct_mine = (*game_info).drop_pct_mine as i32;
1756
+ let pct_barrel = (*game_info).drop_pct_barrel as i32;
1757
 
1758
  let mut remaining = 100 - pct_land;
1759
  if remaining < 0 {
1760
  // Land > 100%, clamp all
1761
+ (*game_info).drop_pct_land = 100;
1762
+ (*game_info).drop_pct_mine = 0;
1763
+ (*game_info).drop_pct_barrel = 0;
1764
  remaining = 0;
1765
  } else {
1766
  remaining -= pct_mine;
1767
  if remaining < 0 {
1768
+ (*game_info).drop_pct_mine = (pct_mine + remaining) as u8;
1769
+ (*game_info).drop_pct_barrel = 0;
1770
  remaining = 0;
1771
  } else {
1772
  remaining -= pct_barrel;
1773
  if remaining < 0 {
1774
+ (*game_info).drop_pct_barrel = (pct_barrel + remaining) as u8;
1775
  }
1776
  }
1777
  }
1778
 
1779
  // Extended terrain type from game_info
1780
+ let ext_type = (*game_info).ext_terrain_type;
1781
+ let ext_pct = (*game_info).ext_terrain_pct as i32;
1782
  if ext_type == 0 {
1783
+ (*game_info).ext_terrain_pct = remaining as u8;
1784
  } else if remaining < ext_pct {
1785
+ (*game_info).ext_terrain_pct = 0;
1786
  }
1787
  }
1788
 
 
1791
  let write_idx_ptr = ddgame_raw.add(0x4114) as *mut i32;
1792
 
1793
  // Land entries (type 1)
1794
+ let count = (*game_info).drop_pct_land as u32;
1795
  let mut idx = *write_idx_ptr;
1796
  if (idx as u32 + count) < 0x65 {
1797
  for _ in 0..count {
 
1802
  }
1803
 
1804
  // Mine entries (type 2)
1805
+ let count = (*game_info).drop_pct_mine as u32;
1806
  idx = *write_idx_ptr;
1807
  if (idx as u32 + count) < 0x65 {
1808
  for _ in 0..count {
 
1813
  }
1814
 
1815
  // Barrel entries (type 4)
1816
+ let count = (*game_info).drop_pct_barrel as u32;
1817
  idx = *write_idx_ptr;
1818
  if (idx as u32 + count) < 0x65 {
1819
  for _ in 0..count {
 
1824
  }
1825
 
1826
  // Extended entries (type 0)
1827
+ let count = (*game_info).ext_terrain_pct as u32;
1828
  idx = *write_idx_ptr;
1829
  if (idx as u32 + count) < 0x65 {
1830
  for _ in 0..count {
 
1867
  unsafe fn init_game_state_weapon_avail(
1868
  _wrapper: *mut DDGameWrapper,
1869
  ddgame_raw: *mut u8,
1870
+ game_info: *const GameInfo,
1871
  ) {
1872
+ let game_version = (*game_info).game_version;
1873
 
1874
  for weapon_id in 1..0x47i32 {
1875
  let avail = check_weapon_avail(ddgame_raw as *mut DDGame, weapon_id as u32);
 
1891
  }
1892
 
1893
  /// Allocate team/object tracking arrays and initialize the 0x51C object.
1894
+ unsafe fn init_game_state_tracking_arrays(ddgame_raw: *mut u8, game_info: *const GameInfo) {
1895
  use crate::wa_alloc::wa_malloc;
1896
 
1897
+ let ddgame = ddgame_raw as *mut DDGame;
1898
+
1899
  // Game speed
1900
+ (*ddgame).game_speed = Fixed::ONE;
1901
 
1902
+ let is_headful = (*ddgame).sound_available != 0;
1903
  if !is_headful {
1904
+ (*ddgame).game_speed_target = Fixed(0x1000_0000);
1905
  } else {
1906
+ (*ddgame).game_speed_target = Fixed((*game_info).game_speed_config);
1907
  }
1908
 
1909
  // Network speed callback
1910
+ let sound = (*ddgame).sound;
1911
+ if !sound.is_null() {
1912
+ let snd_ptr = sound as *const u8;
1913
  let vtable = *(snd_ptr as *const *const u32);
1914
  let vmethod: unsafe extern "thiscall" fn(*const u8, u32, u32) =
1915
  core::mem::transmute(*vtable.add(2));
1916
  vmethod(
1917
  snd_ptr,
1918
+ (*ddgame).game_speed.0 as u32,
1919
+ (*ddgame).game_speed_target.0 as u32,
1920
  );
1921
  }
1922
 
1923
  // Misc fields
1924
+ (*ddgame).sound_queue_count = 0;
1925
+ (*ddgame).render_phase = (*game_info).render_phase_cfg as i32;
1926
+ (*ddgame)._field_7640 = 0;
1927
+ (*ddgame)._field_7644 = (*game_info)._field_f363 as u32;
1928
+ (*ddgame)._field_7648 = (*game_info)._field_f364 as u32;
1929
 
1930
  // Render state
1931
+ (*ddgame)._field_7390 = 0;
1932
+ (*ddgame).render_scale = Fixed::ONE;
1933
+ (*ddgame)._field_7398 = 0;
1934
 
1935
  // Allocate team tracking arrays (DDGame+0x514, DDGame+0x518)
1936
  {
1937
+ let count_a = (*game_info).team_slot_count;
1938
  let size = count_a.wrapping_mul(4);
1939
  let arr = wa_malloc(size);
1940
  *(ddgame_raw.add(0x514) as *mut *mut u8) = arr;
 
1943
  }
1944
  }
1945
  {
1946
+ let count_b = (*game_info).object_slot_count;
1947
  let size = count_b.wrapping_mul(4);
1948
  let arr = wa_malloc(size);
1949
  *(ddgame_raw.add(0x518) as *mut *mut u8) = arr;
crates/openwa-dll/src/replacements/replay.rs CHANGED
@@ -684,7 +684,7 @@ unsafe fn parse_and_write_v2plus(
684
  call_usercall_esi(map as u32, rb(va::MAP_VIEW_COPY_INFO));
685
 
686
  // Terrain flag: zero = cavern terrain
687
- (*gi).terrain_flag = ((*map).terrain_flag == 0) as u8;
688
 
689
  // Release map object
690
  ((*(*map).vtable).destructor)(map, 1);
 
684
  call_usercall_esi(map as u32, rb(va::MAP_VIEW_COPY_INFO));
685
 
686
  // Terrain flag: zero = cavern terrain
687
+ (*gi).set_terrain_flag(((*map).terrain_flag == 0) as u8);
688
 
689
  // Release map object
690
  ((*(*map).vtable).destructor)(map, 1);