paavohtl commited on
Commit
5ec61be
·
1 Parent(s): c359816

chore: Add new DDGame & GameInfo fields based on game state init functions

Browse files
crates/openwa-core/src/engine/ddgame.rs CHANGED
@@ -218,8 +218,16 @@ pub struct DDGame {
218
  pub rng_state_1: u32,
219
  /// 0x72F0: RNG state word 2 (changes every frame).
220
  pub rng_state_2: u32,
221
- /// 0x72F4-0x7307: Unknown
222
- pub _unknown_72f4: [u8; 0x7308 - 0x72F4],
 
 
 
 
 
 
 
 
223
  /// 0x7308: Sprite/gfx dimension data (passed to GFX_DIR_LOAD_SPRITES).
224
  pub gfx_sprite_data: [u8; 0x730C - 0x7308],
225
  /// 0x730C-0x732F: GfxDir color table (9 entries).
@@ -232,8 +240,22 @@ pub struct DDGame {
232
  pub _unknown_7330: [u8; 8],
233
  /// 0x7338: Fill pixel value
234
  pub fill_pixel: u32,
235
- /// 0x733C-0x737F: Unknown
236
- pub _unknown_733c: [u8; 0x7380 - 0x733C],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
237
 
238
  /// 0x7380: Viewport width (Fixed-point, e.g. 960.0 = 0x03C00000).
239
  pub viewport_width: i32,
@@ -274,8 +296,16 @@ pub struct DDGame {
274
  pub level_width_raw: u32,
275
  /// 0x7780: Level height output (written by PCLandscape constructor param 11).
276
  pub level_height_raw: u32,
277
- /// 0x7784-0x779B: Unknown
278
- pub _unknown_7784: [u8; 0x779C - 0x7784],
 
 
 
 
 
 
 
 
279
 
280
  /// 0x779C: Level bound min X (Fixed16.16, negative = off-screen left).
281
  pub level_bound_min_x: Fixed,
@@ -300,15 +330,43 @@ pub struct DDGame {
300
  /// 0x77C8: Total pixels (width × height).
301
  pub level_total_pixels: u32,
302
 
303
- /// 0x77CC-0x77E3: Unknown
304
- pub _unknown_77cc: [u8; 0x77E4 - 0x77CC],
 
 
 
 
 
 
 
 
305
 
306
  /// 0x77E4: Speech slot table. Maps (team, speech_line_id) → DSSound buffer index.
307
  /// Cleared by DSSound_LoadAllSpeechBanks (0x571A70), filled by DDGameWrapper__LoadSpeechWAV (0x571530).
308
  pub speech_slot_table: SpeechSlotTable,
309
 
310
- /// 0x7D84-0x7E24: Unknown
311
- pub _unknown_7d84: [u8; 0x7E25 - 0x7D84],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
312
  /// 0x7E25: SuperSheep/AquaSheep restriction active flag (byte).
313
  /// When nonzero, the SuperSheep/AquaSheep weapon check is applied.
314
  pub supersheep_restricted: u8,
@@ -326,8 +384,20 @@ pub struct DDGame {
326
  /// 0x7E40: Game logic version byte 4 (set by InitVersionFlags).
327
  /// Used in FireWeapon__MailMineMole to gate vtable call behavior by version.
328
  pub version_flag_4: u8,
329
- /// 0x7E41-0x7E9F: Unknown
330
- pub _unknown_7e41: [u8; 0x7EA0 - 0x7E41],
 
 
 
 
 
 
 
 
 
 
 
 
331
 
332
  /// 0x7EA0: Flag/counter (value 4 at runtime — likely team count).
333
  pub field_7ea0: u32,
@@ -355,15 +425,29 @@ pub struct DDGame {
355
  /// 0x8140: Number of entries currently in the sound queue (0–16).
356
  pub sound_queue_count: i32,
357
 
358
- /// 0x8144-0x814F: Unknown
359
- pub _unknown_8144: [u8; 0x8150 - 0x8144],
 
 
 
 
360
  /// 0x8150: Parallax/camera scale factor (Fixed-point multiplier).
361
  /// Used by DrawCrosshairLine (multiplied by 0x140000) and
362
  /// CTaskCloud render (parallax X offset = wind_speed * this >> 16).
363
  pub parallax_scale: i32,
364
 
365
- /// 0x8154-0x818B: Unknown
366
- pub _unknown_8154: [u8; 0x818C - 0x8154],
 
 
 
 
 
 
 
 
 
 
367
 
368
  /// 0x818C: Turn status text buffer (null-terminated ASCII).
369
  /// Shows on screen during gameplay, e.g. "Cheesy harkitseee siirtoaan"
 
218
  pub rng_state_1: u32,
219
  /// 0x72F0: RNG state word 2 (changes every frame).
220
  pub rng_state_2: u32,
221
+ /// 0x72F4: Unknown (zeroed by InitTurnState).
222
+ pub _field_72f4: u32,
223
+ /// 0x72F8: Unknown (zeroed by InitTurnState).
224
+ pub _field_72f8: u32,
225
+ /// 0x72FC: Unknown (zeroed by InitTurnState).
226
+ pub _field_72fc: u32,
227
+ /// 0x7300: Unknown (zeroed by InitTurnState).
228
+ pub _field_7300: u32,
229
+ /// 0x7304: Unknown (zeroed by InitTurnState).
230
+ pub _field_7304: u32,
231
  /// 0x7308: Sprite/gfx dimension data (passed to GFX_DIR_LOAD_SPRITES).
232
  pub gfx_sprite_data: [u8; 0x730C - 0x7308],
233
  /// 0x730C-0x732F: GfxDir color table (9 entries).
 
240
  pub _unknown_7330: [u8; 8],
241
  /// 0x7338: Fill pixel value
242
  pub fill_pixel: u32,
243
+ /// 0x733C-0x733F: Unknown
244
+ pub _unknown_733c: [u8; 4],
245
+ /// 0x7340: Landscape dimension param (passed to PCLandscape vtable slot 6).
246
+ pub _field_7340: u32,
247
+ /// 0x7344-0x734B: Unknown
248
+ pub _unknown_7344: [u8; 8],
249
+ /// 0x734C: Landscape dimension param (passed to PCLandscape vtable slot 6).
250
+ pub _field_734c: u32,
251
+ /// 0x7350-0x7373: Unknown
252
+ pub _unknown_7350: [u8; 36],
253
+ /// 0x7374: Unknown (zeroed by InitTurnState).
254
+ pub _field_7374: u32,
255
+ /// 0x7378: Unknown (zeroed by InitTurnState).
256
+ pub _field_7378: u32,
257
+ /// 0x737C: Unknown (zeroed by InitTurnState).
258
+ pub _field_737c: u32,
259
 
260
  /// 0x7380: Viewport width (Fixed-point, e.g. 960.0 = 0x03C00000).
261
  pub viewport_width: i32,
 
296
  pub level_width_raw: u32,
297
  /// 0x7780: Level height output (written by PCLandscape constructor param 11).
298
  pub level_height_raw: u32,
299
+ /// 0x7784: Unknown (zeroed by InitTurnState).
300
+ pub _field_7784: u32,
301
+ /// 0x7788: Unknown (set from GameInfo+0xF362 during InitTurnState).
302
+ pub _field_7788: u32,
303
+ /// 0x778C: Unknown (set to Fixed 1.0 = 0x10000 by InitTurnState).
304
+ pub _field_778c: u32,
305
+ /// 0x7790: Unknown (zeroed by InitTurnState).
306
+ pub _field_7790: u32,
307
+ /// 0x7794-0x779B: Unknown
308
+ pub _unknown_7794: [u8; 8],
309
 
310
  /// 0x779C: Level bound min X (Fixed16.16, negative = off-screen left).
311
  pub level_bound_min_x: Fixed,
 
330
  /// 0x77C8: Total pixels (width × height).
331
  pub level_total_pixels: u32,
332
 
333
+ /// 0x77CC-0x77D3: Unknown
334
+ pub _unknown_77cc: [u8; 8],
335
+ /// 0x77D4: Unknown (zeroed by InitTurnState).
336
+ pub _field_77d4: u32,
337
+ /// 0x77D8: Unknown (zeroed by InitTurnState).
338
+ pub _field_77d8: u32,
339
+ /// 0x77DC: Unknown (zeroed by InitTurnState).
340
+ pub _field_77dc: u32,
341
+ /// 0x77E0: Unknown (zeroed by InitTurnState).
342
+ pub _field_77e0: u32,
343
 
344
  /// 0x77E4: Speech slot table. Maps (team, speech_line_id) → DSSound buffer index.
345
  /// Cleared by DSSound_LoadAllSpeechBanks (0x571A70), filled by DDGameWrapper__LoadSpeechWAV (0x571530).
346
  pub speech_slot_table: SpeechSlotTable,
347
 
348
+ /// 0x7D84: Unknown (zeroed by InitTurnState).
349
+ pub _field_7d84: u32,
350
+ /// 0x7D88: Per-team u32 flags (13 entries, zeroed per team by InitTurnState).
351
+ pub _field_7d88: [u32; 13],
352
+ /// 0x7DBC: Per-team byte flags array 1 (13 entries, set to 1 by InitTurnState).
353
+ pub _field_7dbc: [u8; 13],
354
+ /// 0x7DC9: Per-team byte flags array 2 (13 entries, set to 1 by InitTurnState).
355
+ pub _field_7dc9: [u8; 13],
356
+ /// 0x7DD6: Per-team byte flags array 3 (13 entries, zeroed by InitTurnState).
357
+ pub _field_7dd6: [u8; 13],
358
+ /// 0x7DE3: Per-team byte flags array 4 (13 entries, zeroed by InitTurnState).
359
+ pub _field_7de3: [u8; 13],
360
+ /// 0x7DF0: Per-team byte flags array 5 (13 entries, zeroed by InitTurnState).
361
+ pub _field_7df0: [u8; 13],
362
+ /// 0x7DFD-0x7E02: Unknown
363
+ pub _unknown_7dfd: [u8; 6],
364
+ /// 0x7E03: Unknown byte (zeroed by InitTurnState).
365
+ pub _field_7e03: u8,
366
+ /// 0x7E04: Unknown byte (zeroed by InitTurnState).
367
+ pub _field_7e04: u8,
368
+ /// 0x7E05-0x7E24: Unknown
369
+ pub _unknown_7e05: [u8; 32],
370
  /// 0x7E25: SuperSheep/AquaSheep restriction active flag (byte).
371
  /// When nonzero, the SuperSheep/AquaSheep weapon check is applied.
372
  pub supersheep_restricted: u8,
 
384
  /// 0x7E40: Game logic version byte 4 (set by InitVersionFlags).
385
  /// Used in FireWeapon__MailMineMole to gate vtable call behavior by version.
386
  pub version_flag_4: u8,
387
+ /// 0x7E41: Unknown byte (zeroed by InitTurnState).
388
+ pub _field_7e41: u8,
389
+ /// 0x7E42-0x7E4B: Unknown
390
+ pub _unknown_7e42: [u8; 10],
391
+ /// 0x7E4C: Unknown (zeroed by InitTurnState).
392
+ pub _field_7e4c: u32,
393
+ /// 0x7E50-0x7E6F: Unknown fields (all zeroed by InitTurnState).
394
+ pub _fields_7e50: [u32; 8],
395
+ /// 0x7E70: Per-team scoring flags (6 entries, written by InitAllianceData).
396
+ pub team_scoring_flags: [u32; 6],
397
+ /// 0x7E88-0x7E9B: Unknown fields (all zeroed by InitTurnState).
398
+ pub _fields_7e88: [u32; 5],
399
+ /// 0x7E9C-0x7E9F: Unknown
400
+ pub _unknown_7e9c: [u8; 4],
401
 
402
  /// 0x7EA0: Flag/counter (value 4 at runtime — likely team count).
403
  pub field_7ea0: u32,
 
425
  /// 0x8140: Number of entries currently in the sound queue (0–16).
426
  pub sound_queue_count: i32,
427
 
428
+ /// 0x8144-0x8147: Unknown
429
+ pub _unknown_8144: [u8; 4],
430
+ /// 0x8148: Unknown (set to 1 by InitTurnState).
431
+ pub _field_8148: u32,
432
+ /// 0x814C-0x814F: Unknown
433
+ pub _unknown_814c: [u8; 4],
434
  /// 0x8150: Parallax/camera scale factor (Fixed-point multiplier).
435
  /// Used by DrawCrosshairLine (multiplied by 0x140000) and
436
  /// CTaskCloud render (parallax X offset = wind_speed * this >> 16).
437
  pub parallax_scale: i32,
438
 
439
+ /// 0x8154-0x8157: Unknown
440
+ pub _unknown_8154: [u8; 4],
441
+ /// 0x8158: Unknown (zeroed by InitTurnState).
442
+ pub _field_8158: u32,
443
+ /// 0x815C: Unknown (zeroed by InitTurnState).
444
+ pub _field_815c: u32,
445
+ /// 0x8160: Unknown (zeroed by InitTurnState).
446
+ pub _field_8160: u32,
447
+ /// 0x8164: Unknown (zeroed by InitTurnState).
448
+ pub _field_8164: u32,
449
+ /// 0x8168-0x818B: Unknown
450
+ pub _unknown_8168: [u8; 36],
451
 
452
  /// 0x818C: Turn status text buffer (null-terminated ASCII).
453
  /// Shows on screen during gameplay, e.g. "Cheesy harkitseee siirtoaan"
crates/openwa-core/src/engine/game_info.rs CHANGED
@@ -19,16 +19,24 @@ pub struct GameInfo {
19
  /// Used by DSSound_LoadAllSpeechBanks to iterate teams.
20
  pub speech_team_count: u8,
21
 
22
- /// 0x044D-0xD777: Unknown
23
- pub _unknown_044d: [u8; 0xD778 - 0x44D],
 
 
24
 
25
  /// 0xD778: Game version/mode. Compared against various thresholds:
26
  /// -2 = network game, -3..0 = different game modes, 8+ = new versions.
27
  /// Used by DDGame constructor for conditional initialization.
28
  pub game_version: i32,
29
 
30
- /// 0xD77C-0xD931: Unknown
31
- pub _unknown_d77c: [u8; 0xD932 - 0xD77C],
 
 
 
 
 
 
32
  /// 0xD932: DoubleTurnTime availability threshold (u16).
33
  /// If game_version > 0xD1 and this > 0x7FFF, DoubleTurnTime is disabled.
34
  pub double_turn_time_threshold: u16,
@@ -47,8 +55,10 @@ pub struct GameInfo {
47
  /// 0xD946: Network config byte 2 (copied to network object+0x29).
48
  pub net_config_2: u8,
49
 
50
- /// 0xD947-0xD94B: Unknown
51
- pub _unknown_d947: [u8; 0xD94C - 0xD947],
 
 
52
  /// 0xD94C: Donkey (weapon 0x36) disable flag.
53
  pub donkey_disabled: u8,
54
  /// 0xD94D-0xD955: Unknown
@@ -69,8 +79,14 @@ pub struct GameInfo {
69
  /// 0xD9A2: Network weapon exception flag. When net_config_2 != 0,
70
  /// weapons 10/0x37/0x38 are only disabled if this is also 0.
71
  pub net_weapon_exception: u8,
72
- /// 0xD9A3-0xD9DF: Unknown
73
- pub _unknown_d9a3: [u8; 0xD9E0 - 0xD9A3],
 
 
 
 
 
 
74
 
75
  /// 0xD9E0: Streaming audio config data (path config passed to streaming audio ctor).
76
  /// Address of this field is passed as a pointer parameter.
@@ -138,8 +154,12 @@ pub struct GameInfo {
138
  /// Checked by IsSoundSuppressed, DispatchGlobalSound, PlaySoundPooled_Direct.
139
  pub sound_mute: u8,
140
 
141
- /// 0xF349-0xF373: Unknown
142
- pub _unknown_f349: [u8; 0xF374 - 0xF349],
 
 
 
 
143
 
144
  /// 0xF374: Display flags passed to DDDisplay::Init.
145
  pub display_flags: u32,
 
19
  /// Used by DSSound_LoadAllSpeechBanks to iterate teams.
20
  pub speech_team_count: u8,
21
 
22
+ /// 0x044D-0xD773: Unknown
23
+ pub _unknown_044d: [u8; 0xD774 - 0x44D],
24
+ /// 0xD774: Initial RNG seed from scheme options.
25
+ pub rng_seed: u32,
26
 
27
  /// 0xD778: Game version/mode. Compared against various thresholds:
28
  /// -2 = network game, -3..0 = different game modes, 8+ = new versions.
29
  /// Used by DDGame constructor for conditional initialization.
30
  pub game_version: i32,
31
 
32
+ /// 0xD77C-0xD787: Unknown
33
+ pub _unknown_d77c: [u8; 0xD788 - 0xD77C],
34
+ /// 0xD788: Scoring parameter A (multiplied by 50 for initial score).
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,
 
55
  /// 0xD946: Network config byte 2 (copied to network object+0x29).
56
  pub net_config_2: u8,
57
 
58
+ /// 0xD947-0xD94A: Unknown
59
+ pub _unknown_d947: [u8; 4],
60
+ /// 0xD94B: Landscape scheme flag (nonzero enables terrain features via PCLandscape vtable slot 6).
61
+ pub landscape_scheme_flag: u8,
62
  /// 0xD94C: Donkey (weapon 0x36) disable flag.
63
  pub donkey_disabled: u8,
64
  /// 0xD94D-0xD955: Unknown
 
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,
82
+ /// 0xD9A3-0xD9DB: Unknown
83
+ pub _unknown_d9a3: [u8; 0xD9DC - 0xD9A3],
84
+ /// 0xD9DC: Index of the starting team for this round.
85
+ pub starting_team_index: i8,
86
+ /// 0xD9DD: Game mode flag. Negative = training/replay mode. Also used as starting-team index for activity flags in normal mode.
87
+ pub game_mode_flag: i8,
88
+ /// 0xD9DE-0xD9DF: Unknown
89
+ pub _unknown_d9de: [u8; 2],
90
 
91
  /// 0xD9E0: Streaming audio config data (path config passed to streaming audio ctor).
92
  /// Address of this field is passed as a pointer parameter.
 
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 DDDisplay::Init.
165
  pub display_flags: u32,
crates/openwa-core/src/engine/game_state_init.rs CHANGED
@@ -4,6 +4,7 @@
4
  //! InitGameState itself is Rust or the original WA code.
5
 
6
  use crate::engine::ddgame::DDGame;
 
7
  use crate::wa_alloc::wa_malloc;
8
 
9
  /// Pure Rust implementation of SpriteGfxTable__Init (0x541620).
@@ -62,15 +63,15 @@ const TEAM_ENTRY_STRIDE: usize = 3000;
62
  ///
63
  /// Chain: wrapper+0x488 → DDGame, DDGame+0x24 → GameInfo.
64
  #[inline]
65
- unsafe fn game_info_from_wrapper(wrapper: *mut u8) -> *mut u8 {
66
- let ddgame = *(wrapper.add(0x488) as *const *mut u8);
67
- *(ddgame.add(0x24) as *const *mut u8)
68
  }
69
 
70
  /// Helper: read the DDGame pointer from a DDGameWrapper pointer.
71
  #[inline]
72
- unsafe fn ddgame_from_wrapper(wrapper: *mut u8) -> *mut u8 {
73
- *(wrapper.add(0x488) as *const *mut u8)
74
  }
75
 
76
  /// Pure Rust implementation of CGameTask_InitTeamScoring_Maybe (0x528510).
@@ -97,10 +98,8 @@ unsafe fn ddgame_from_wrapper(wrapper: *mut u8) -> *mut u8 {
97
  pub unsafe fn init_team_scoring(wrapper: *mut u8) {
98
  let game_info = game_info_from_wrapper(wrapper);
99
 
100
- // GameInfo+0xD788: scoring_param_a (u16)
101
- // GameInfo+0xD78A: scoring_param_b (u16)
102
- let scoring_param_a = *(game_info.add(0xD788) as *const u16) as u32;
103
- let scoring_param_b = *(game_info.add(0xD78A) as *const u16) as u32;
104
  let value_a = scoring_param_a * 50; // uVar1 * 0x32
105
  let value_b = scoring_param_b * 50; // iVar4
106
 
@@ -119,15 +118,14 @@ pub unsafe fn init_team_scoring(wrapper: *mut u8) {
119
  }
120
 
121
  // Mark the starting team in array_1: wrapper+0x128[starting_team] = 1
122
- // GameInfo+0xD9DC: starting team index (i8)
123
- let starting_team = *(game_info.add(0xD9DC) as *const i8) as i32;
124
  *(base.add(starting_team as usize)) = 1;
125
 
126
  // GameInfo+0xD9DD: mode flag (i8, negative = training/replay mode)
127
- let mode_flag = *(game_info.add(0xD9DD) as *const i8);
128
 
129
  // GameInfo+0x0000: first byte = total team count (num_teams, u8)
130
- let num_teams = *game_info as u8 as i32;
131
 
132
  // Team activity flags at wrapper+0x22C (one u32 per team)
133
  let flags_base = wrapper.add(0x22C) as *mut u32;
@@ -143,12 +141,13 @@ pub unsafe fn init_team_scoring(wrapper: *mut u8) {
143
  // For each active team slot in GameInfo, clear the flag to 0
144
  // GameInfo+0x44C: team count for alliance data (u8)
145
  let game_info2 = game_info_from_wrapper(wrapper);
146
- let alliance_team_count = *game_info2.add(0x44C) as u8 as i32;
147
  if alliance_team_count > 0 {
148
  let mut offset: usize = 0;
149
  for _ in 0..alliance_team_count {
150
  // GameInfo+0x450 + team_index*3000: alliance group (i8)
151
- let alliance_group = *(game_info2.add(0x450 + offset) as *const i8) as i32;
 
152
  if alliance_group >= 0 {
153
  *flags_base.add(alliance_group as usize) = 0;
154
  }
@@ -167,14 +166,14 @@ pub unsafe fn init_team_scoring(wrapper: *mut u8) {
167
  }
168
 
169
  // Set starting team's flag to 1
170
- // GameInfo+0xD9DD: starting team index for flags (i8, reuse mode_flag which is the same field)
171
- let starting_flag_team = *(game_info_from_wrapper(wrapper).add(0xD9DD) as *const i8) as i32;
172
  *flags_base.add(starting_flag_team as usize) = 1;
173
  }
174
 
175
  // Zero CTask sub-object fields for each team's task pointer
176
  // wrapper+0x54[i] = CTask pointer, clear offsets +0x08..+0x18 (5 DWORDs)
177
- let num_teams2 = *game_info_from_wrapper(wrapper) as u8 as i32;
178
  if num_teams2 > 0 {
179
  let task_ptrs = wrapper.add(0x54) as *mut *mut u8;
180
  for i in 0..num_teams2 as usize {
@@ -199,16 +198,16 @@ pub unsafe fn init_team_scoring(wrapper: *mut u8) {
199
  ///
200
  /// # Layout:
201
  /// - wrapper+0x350..+0x384: 13 × u32 alliance bitmasks (one per alliance group)
202
- /// - DDGame+0x7E70..+0x7E88: 6 × u32 per-team scoring flags
203
  /// - wrapper+0x3AC..: auxiliary array of non-starting-team alliance IDs (+0x10)
204
  ///
205
  /// # GameInfo fields used:
206
- /// - +0x000: num_teams (u8, first byte)
207
- /// - +0x44C: alliance_team_count (u8)
208
  /// - +0x450 + i*3000: alliance_group (i8) for team i
209
  /// - +0x451 + i*3000: alliance_id (u8) for team i
210
- /// - +0xD778: game_version (i32)
211
- /// - +0xD9DC: starting_team_index (i8)
212
  ///
213
  /// - wrapper+0x490: first byte used as scoring override value
214
  pub unsafe fn init_alliance_data(wrapper: *mut u8) {
@@ -220,19 +219,19 @@ pub unsafe fn init_alliance_data(wrapper: *mut u8) {
220
  // Build alliance bitmasks: for each team, set bit (alliance_id & 0x1F)
221
  // in bitmask[alliance_group]
222
  let game_info = game_info_from_wrapper(wrapper);
223
- let num_teams = *game_info as u8 as i32;
224
 
225
  // Global DAT_007a087c+4 = 0 (static variable, offset 0x7A0880)
226
  // We skip this — it's a global we don't own. The original sets it but
227
  // it appears unused in the init path.
228
 
229
- let alliance_team_count = *game_info.add(0x44C) as u8 as i32;
230
  if num_teams != 0 && alliance_team_count > 0 {
231
  let mut offset: usize = 0;
232
  for _ in 0..alliance_team_count {
233
  let gi = game_info_from_wrapper(wrapper);
234
- let alliance_group = *(gi.add(0x450 + offset) as *const i8) as i32;
235
- let alliance_id = *(gi.add(0x451 + offset) as *const u8);
236
 
237
  if alliance_group >= 0 {
238
  let bitmask = (wrapper.add(0x350) as *mut u32).add(alliance_group as usize);
@@ -243,15 +242,13 @@ pub unsafe fn init_alliance_data(wrapper: *mut u8) {
243
  }
244
  }
245
 
246
- // Zero 6 per-team scoring flags at DDGame+0x7E70..+0x7E88
247
  let ddgame = ddgame_from_wrapper(wrapper);
248
- for i in 0..6u32 {
249
- *(ddgame.add(0x7E70) as *mut u32).add(i as usize) = 0;
250
- }
251
 
252
  // Build scoring flags and auxiliary alliance array
253
  let game_info = game_info_from_wrapper(wrapper);
254
- let alliance_team_count = *game_info.add(0x44C) as u8 as i32;
255
 
256
  // Local tracking: which alliance IDs we've already added to auxiliary array
257
  let mut seen: [u8; 6] = [0; 6]; // local_8[0..6], indexed by alliance_id
@@ -262,11 +259,12 @@ pub unsafe fn init_alliance_data(wrapper: *mut u8) {
262
 
263
  for team_idx in 0..alliance_team_count {
264
  let gi = game_info_from_wrapper(wrapper);
265
- let alliance_group = *(gi.add(0x450 + team_offset) as *const i8) as i32;
266
- let alliance_id = *(gi.add(0x451 + team_offset) as *const u8) as u32;
267
 
268
  let ddgame = ddgame_from_wrapper(wrapper);
269
- let scoring_flag = (ddgame as *mut u32).add((0x7E70 / 4) + team_idx as usize);
 
270
 
271
  if alliance_group < 0 {
272
  // No alliance: flag = value from wrapper+0x490 (i8 sign-extended)
@@ -275,15 +273,15 @@ pub unsafe fn init_alliance_data(wrapper: *mut u8) {
275
  } else {
276
  // Check if this team is allied with the starting team
277
  let gi = game_info_from_wrapper(wrapper);
278
- let num_teams_byte = *gi as u8;
279
  let override_byte = *(wrapper.add(0x490) as *const u8);
280
 
281
  if num_teams_byte == 0 || override_byte != 0 {
282
  // No teams or override set: flag = 1
283
  *scoring_flag = 1;
284
  } else {
285
- let game_version = *(gi.add(0xD778) as *const i32);
286
- let starting_team = *(gi.add(0xD9DC) as *const i8) as i32;
287
 
288
  let team_bitmask: u32 = if game_version < 0x83 {
289
  // Old version: use full alliance bitmask
@@ -305,7 +303,7 @@ pub unsafe fn init_alliance_data(wrapper: *mut u8) {
305
 
306
  // Build auxiliary array for non-starting-team alliances
307
  let gi2 = game_info_from_wrapper(wrapper);
308
- let starting_team2 = *(gi2.add(0xD9DC) as *const i8) as i32;
309
 
310
  if alliance_group != starting_team2 && seen[alliance_id as usize] == 0 {
311
  let starting_bitmask2 =
@@ -434,7 +432,7 @@ unsafe fn wa_init_feature_flags(wrapper: *mut u8) {
434
  /// object.
435
  pub unsafe fn init_turn_state(wrapper: *mut u8) {
436
  let ddgame = ddgame_from_wrapper(wrapper);
437
- let game_info = *(ddgame.add(0x24) as *const *mut u8);
438
 
439
  // wrapper+0x458 = -1, wrapper+0x450 = 0, wrapper+0x454 = 0
440
  *(wrapper.add(0x458) as *mut u32) = 0xFFFF_FFFF;
@@ -442,116 +440,103 @@ pub unsafe fn init_turn_state(wrapper: *mut u8) {
442
  *(wrapper.add(0x454) as *mut u32) = 0;
443
 
444
  // DDGame+0x72E0/E4 = -1, DDGame+0x72E8 = 0
445
- *(ddgame.add(0x72E0) as *mut u32) = 0xFFFF_FFFF;
446
- *(ddgame.add(0x72E4) as *mut u32) = 0xFFFF_FFFF;
447
- *(ddgame.add(0x72E8) as *mut u32) = 0;
448
 
449
- // Zero array at DDGame+0x73B0, stride 0x14, while offset < 0x118
450
  {
 
451
  let mut off = 0u32;
452
  while off < 0x118 {
453
- *(ddgame.add(0x73B0 + off as usize) as *mut u32) = 0;
454
  off += 0x14;
455
  }
456
  }
457
 
458
  // More DDGame field zeroing
459
- *(ddgame.add(0x739C) as *mut u32) = 0;
460
- *(ddgame.add(0x72F4) as *mut u32) = 0;
461
- *(ddgame.add(0x72F8) as *mut u32) = 0;
462
- *(ddgame.add(0x72FC) as *mut u32) = 0;
463
- *(ddgame.add(0x7300) as *mut u32) = 0;
464
- *(ddgame.add(0x7304) as *mut u32) = 0;
465
-
466
- // DDGame+0x72EC = game_info+0xD774 (RNG seed from scheme)
467
- let rng_seed = *(game_info.add(0xD774) as *const u32);
468
- *(ddgame.add(0x72EC) as *mut u32) = rng_seed;
469
- *(ddgame.add(0x72F0) as *mut u32) = rng_seed; // duplicate
470
-
471
- *(ddgame.add(0x7378) as *mut u32) = 0;
472
- *(ddgame.add(0x7374) as *mut u32) = 0;
473
- *(ddgame.add(0x737C) as *mut u32) = 0;
474
- *(ddgame.add(0x77DC) as *mut u32) = 0;
475
- *(ddgame.add(0x77E0) as *mut u32) = 0;
476
- *(ddgame.add(0x7784) as *mut u32) = 0;
477
-
478
- // DDGame+0x7788 = game_info+0xF362 (byte → u32)
479
- *(ddgame.add(0x7788) as *mut u32) = *(game_info.add(0xF362) as *const u8) as u32;
480
- *(ddgame.add(0x778C) as *mut u32) = 0x10000; // Fixed-point 1.0
481
- *(ddgame.add(0x7790) as *mut u32) = 0;
482
 
483
  // Camera center: (level_width << 16) / 2, (level_height << 16) / 2
484
- let level_width = *(ddgame.add(0x77C0) as *const i32);
485
- let level_height = *(ddgame.add(0x77C4) as *const i32);
486
  let cx = (level_width << 16) / 2;
487
  let cy = (level_height << 16) / 2;
488
- *(ddgame.add(0x7380) as *mut i32) = cx;
489
- *(ddgame.add(0x7388) as *mut i32) = cx; // duplicate
490
- *(ddgame.add(0x7384) as *mut i32) = cy;
491
- *(ddgame.add(0x738C) as *mut i32) = cy; // duplicate
492
 
493
- *(ddgame.add(0x7D84) as *mut u32) = 0;
494
- *(ddgame.add(0x7E4C) as *mut u32) = 0;
495
- *(ddgame.add(0x77D4) as *mut u32) = 0;
496
- *(ddgame.add(0x77D8) as *mut u32) = 0;
497
 
498
  // Per-team loop
499
- let num_teams = *game_info as u8 as i32;
500
  if num_teams > 0 {
501
- let mut off = 0x7D88u32;
502
- for i in 0..num_teams {
503
- *(ddgame.add(off as usize) as *mut u32) = 0;
504
- *(ddgame.add(0x7DBC + i as usize) as *mut u8) = 1;
505
- *(ddgame.add(0x7DC9 + i as usize) as *mut u8) = 1;
506
- *(ddgame.add(0x7DD6 + i as usize) as *mut u8) = 0;
507
- *(ddgame.add(0x7DE3 + i as usize) as *mut u8) = 0;
508
- *(ddgame.add(0x7DF0 + i as usize) as *mut u8) = 0;
509
- off += 4;
510
  }
511
  }
512
 
513
- *(ddgame.add(0x7E03) as *mut u8) = 0;
514
- *(ddgame.add(0x7E04) as *mut u8) = 0;
515
 
516
  // Call DDGame__InitFeatureFlags (600-line feature flag init, bridged)
517
  wa_init_feature_flags(wrapper);
518
 
519
  // Post-feature-flag field writes
520
- *(ddgame.add(0x7E41) as *mut u8) = 0;
521
- *(ddgame.add(0x7E50) as *mut u32) = 0;
522
- *(ddgame.add(0x7E54) as *mut u32) = 0;
523
- *(ddgame.add(0x7E58) as *mut u32) = 0;
524
- *(ddgame.add(0x7E5C) as *mut u32) = 0;
525
- *(ddgame.add(0x7E60) as *mut u32) = 0;
526
- *(ddgame.add(0x7E64) as *mut u32) = 0;
527
- *(ddgame.add(0x7E68) as *mut u32) = 0;
528
- *(ddgame.add(0x7E6C) as *mut u32) = 0;
529
- *(ddgame.add(0x7E88) as *mut u32) = 0;
530
- *(ddgame.add(0x7E8C) as *mut u32) = 0;
531
- *(ddgame.add(0x7E90) as *mut u32) = 0;
532
- *(ddgame.add(0x7E94) as *mut u32) = 0;
533
- *(ddgame.add(0x7E98) as *mut u32) = 0;
534
- *(ddgame.add(0x7EA0) as *mut u32) = 0;
535
- *(ddgame.add(0x7EA4) as *mut u32) = 0;
536
-
537
- *(ddgame.add(0x8148) as *mut u32) = 1;
538
 
539
  let ddgame2 = ddgame_from_wrapper(wrapper);
540
- *(ddgame2.add(0x8158) as *mut u32) = 0;
541
- *(ddgame2.add(0x815C) as *mut u32) = 0;
542
  let ddgame3 = ddgame_from_wrapper(wrapper);
543
- *(ddgame3.add(0x8160) as *mut u32) = 0;
544
- *(ddgame3.add(0x8164) as *mut u32) = 0;
545
 
546
- // Vtable dispatch: DDGame+0x20 → landscape object, vtable slot 1,
547
- // param = game_info+0xD94C (byte)
548
  let ddgame4 = ddgame_from_wrapper(wrapper);
549
- let landscape = *(ddgame4.add(0x20) as *const *mut u8);
550
  if !landscape.is_null() {
551
  let vtable = *(landscape as *const *const usize);
552
  let slot1: unsafe extern "thiscall" fn(*mut u8, u32) = core::mem::transmute(*vtable.add(1));
553
- let game_info2 = *(ddgame4.add(0x24) as *const *const u8);
554
- let param = *game_info2.add(0xD94C) as u32;
555
  slot1(landscape, param);
556
  }
557
  }
@@ -560,22 +545,22 @@ pub unsafe fn init_turn_state(wrapper: *mut u8) {
560
  ///
561
  /// Convention: usercall(EAX=wrapper), plain RET.
562
  ///
563
- /// Checks game_info+0xD94B and dispatches landscape vtable slot 6 with
564
- /// appropriate parameters, then updates DDGame+0x777C.
565
  pub unsafe fn init_landscape_flags(wrapper: *mut u8) {
566
  let ddgame = ddgame_from_wrapper(wrapper);
567
- let game_info = *(ddgame.add(0x24) as *const *mut u8);
568
 
569
- let scheme_flag = *(game_info.add(0xD94B) as *const u8);
570
 
571
  // Read the 4 DDGame fields used as params
572
- let field_7318 = *(ddgame.add(0x7318) as *const u32);
573
- let field_730c = *(ddgame.add(0x730C) as *const u32);
574
- let field_734c = *(ddgame.add(0x734C) as *const u32);
575
- let field_7340 = *(ddgame.add(0x7340) as *const u32);
576
 
577
- // DDGame+0x20 = landscape object
578
- let landscape = *(ddgame.add(0x20) as *const *mut u8);
579
  let vtable = *(landscape as *const *const usize);
580
  // Vtable slot 6 = offset +0x18
581
  let slot6: unsafe extern "thiscall" fn(*mut u8, u32, u32, u32, u32, u32, u32, u32, u32) =
@@ -586,8 +571,8 @@ pub unsafe fn init_landscape_flags(wrapper: *mut u8) {
586
  slot6(
587
  landscape, 1, 1, 1, 1, field_7318, field_730c, field_734c, field_7340,
588
  );
589
- *(ddgame.add(0x777C) as *mut u32) = 1;
590
- } else if *(ddgame.add(0x777C) as *const u32) != 0 {
591
  // Call with (0, 0, 1, 0, field_7318, field_730c, field_734c, field_7340)
592
  slot6(
593
  landscape, 0, 0, 1, 0, field_7318, field_730c, field_734c, field_7340,
 
4
  //! InitGameState itself is Rust or the original WA code.
5
 
6
  use crate::engine::ddgame::DDGame;
7
+ use crate::engine::game_info::GameInfo;
8
  use crate::wa_alloc::wa_malloc;
9
 
10
  /// Pure Rust implementation of SpriteGfxTable__Init (0x541620).
 
63
  ///
64
  /// Chain: wrapper+0x488 → DDGame, DDGame+0x24 → GameInfo.
65
  #[inline]
66
+ unsafe fn game_info_from_wrapper(wrapper: *mut u8) -> *mut GameInfo {
67
+ let ddgame = *(wrapper.add(0x488) as *const *mut DDGame);
68
+ (*ddgame).game_info
69
  }
70
 
71
  /// Helper: read the DDGame pointer from a DDGameWrapper pointer.
72
  #[inline]
73
+ unsafe fn ddgame_from_wrapper(wrapper: *mut u8) -> *mut DDGame {
74
+ *(wrapper.add(0x488) as *const *mut DDGame)
75
  }
76
 
77
  /// Pure Rust implementation of CGameTask_InitTeamScoring_Maybe (0x528510).
 
98
  pub unsafe fn init_team_scoring(wrapper: *mut u8) {
99
  let game_info = game_info_from_wrapper(wrapper);
100
 
101
+ let scoring_param_a = (*game_info).scoring_param_a as u32;
102
+ let scoring_param_b = (*game_info).scoring_param_b as u32;
 
 
103
  let value_a = scoring_param_a * 50; // uVar1 * 0x32
104
  let value_b = scoring_param_b * 50; // iVar4
105
 
 
118
  }
119
 
120
  // Mark the starting team in array_1: wrapper+0x128[starting_team] = 1
121
+ let starting_team = (*game_info).starting_team_index as i32;
 
122
  *(base.add(starting_team as usize)) = 1;
123
 
124
  // GameInfo+0xD9DD: mode flag (i8, negative = training/replay mode)
125
+ let mode_flag = (*game_info).game_mode_flag;
126
 
127
  // GameInfo+0x0000: first byte = total team count (num_teams, u8)
128
+ let num_teams = (*game_info).num_teams as i32;
129
 
130
  // Team activity flags at wrapper+0x22C (one u32 per team)
131
  let flags_base = wrapper.add(0x22C) as *mut u32;
 
141
  // For each active team slot in GameInfo, clear the flag to 0
142
  // GameInfo+0x44C: team count for alliance data (u8)
143
  let game_info2 = game_info_from_wrapper(wrapper);
144
+ let alliance_team_count = (*game_info2).speech_team_count as i32;
145
  if alliance_team_count > 0 {
146
  let mut offset: usize = 0;
147
  for _ in 0..alliance_team_count {
148
  // GameInfo+0x450 + team_index*3000: alliance group (i8)
149
+ let alliance_group =
150
+ *((game_info2 as *const u8).add(0x450 + offset) as *const i8) as i32;
151
  if alliance_group >= 0 {
152
  *flags_base.add(alliance_group as usize) = 0;
153
  }
 
166
  }
167
 
168
  // Set starting team's flag to 1
169
+ // GameInfo+0xD9DD: starting team index for flags (i8, reuse game_mode_flag which is the same field)
170
+ let starting_flag_team = (*game_info_from_wrapper(wrapper)).game_mode_flag as i32;
171
  *flags_base.add(starting_flag_team as usize) = 1;
172
  }
173
 
174
  // Zero CTask sub-object fields for each team's task pointer
175
  // wrapper+0x54[i] = CTask pointer, clear offsets +0x08..+0x18 (5 DWORDs)
176
+ let num_teams2 = (*game_info_from_wrapper(wrapper)).num_teams as i32;
177
  if num_teams2 > 0 {
178
  let task_ptrs = wrapper.add(0x54) as *mut *mut u8;
179
  for i in 0..num_teams2 as usize {
 
198
  ///
199
  /// # Layout:
200
  /// - wrapper+0x350..+0x384: 13 × u32 alliance bitmasks (one per alliance group)
201
+ /// - DDGame.team_scoring_flags: 6 × u32 per-team scoring flags
202
  /// - wrapper+0x3AC..: auxiliary array of non-starting-team alliance IDs (+0x10)
203
  ///
204
  /// # GameInfo fields used:
205
+ /// - num_teams
206
+ /// - speech_team_count (alliance_team_count)
207
  /// - +0x450 + i*3000: alliance_group (i8) for team i
208
  /// - +0x451 + i*3000: alliance_id (u8) for team i
209
+ /// - game_version
210
+ /// - starting_team_index
211
  ///
212
  /// - wrapper+0x490: first byte used as scoring override value
213
  pub unsafe fn init_alliance_data(wrapper: *mut u8) {
 
219
  // Build alliance bitmasks: for each team, set bit (alliance_id & 0x1F)
220
  // in bitmask[alliance_group]
221
  let game_info = game_info_from_wrapper(wrapper);
222
+ let num_teams = (*game_info).num_teams as i32;
223
 
224
  // Global DAT_007a087c+4 = 0 (static variable, offset 0x7A0880)
225
  // We skip this — it's a global we don't own. The original sets it but
226
  // it appears unused in the init path.
227
 
228
+ let alliance_team_count = (*game_info).speech_team_count as i32;
229
  if num_teams != 0 && alliance_team_count > 0 {
230
  let mut offset: usize = 0;
231
  for _ in 0..alliance_team_count {
232
  let gi = game_info_from_wrapper(wrapper);
233
+ let alliance_group = *((gi as *const u8).add(0x450 + offset) as *const i8) as i32;
234
+ let alliance_id = *((gi as *const u8).add(0x451 + offset));
235
 
236
  if alliance_group >= 0 {
237
  let bitmask = (wrapper.add(0x350) as *mut u32).add(alliance_group as usize);
 
242
  }
243
  }
244
 
245
+ // Zero 6 per-team scoring flags (DDGame.team_scoring_flags)
246
  let ddgame = ddgame_from_wrapper(wrapper);
247
+ (*ddgame).team_scoring_flags = [0u32; 6];
 
 
248
 
249
  // Build scoring flags and auxiliary alliance array
250
  let game_info = game_info_from_wrapper(wrapper);
251
+ let alliance_team_count = (*game_info).speech_team_count as i32;
252
 
253
  // Local tracking: which alliance IDs we've already added to auxiliary array
254
  let mut seen: [u8; 6] = [0; 6]; // local_8[0..6], indexed by alliance_id
 
259
 
260
  for team_idx in 0..alliance_team_count {
261
  let gi = game_info_from_wrapper(wrapper);
262
+ let alliance_group = *((gi as *const u8).add(0x450 + team_offset) as *const i8) as i32;
263
+ let alliance_id = *((gi as *const u8).add(0x451 + team_offset)) as u32;
264
 
265
  let ddgame = ddgame_from_wrapper(wrapper);
266
+ let scoring_flag =
267
+ core::ptr::addr_of_mut!((*ddgame).team_scoring_flags[team_idx as usize]);
268
 
269
  if alliance_group < 0 {
270
  // No alliance: flag = value from wrapper+0x490 (i8 sign-extended)
 
273
  } else {
274
  // Check if this team is allied with the starting team
275
  let gi = game_info_from_wrapper(wrapper);
276
+ let num_teams_byte = (*gi).num_teams;
277
  let override_byte = *(wrapper.add(0x490) as *const u8);
278
 
279
  if num_teams_byte == 0 || override_byte != 0 {
280
  // No teams or override set: flag = 1
281
  *scoring_flag = 1;
282
  } else {
283
+ let game_version = (*gi).game_version;
284
+ let starting_team = (*gi).starting_team_index as i32;
285
 
286
  let team_bitmask: u32 = if game_version < 0x83 {
287
  // Old version: use full alliance bitmask
 
303
 
304
  // Build auxiliary array for non-starting-team alliances
305
  let gi2 = game_info_from_wrapper(wrapper);
306
+ let starting_team2 = (*gi2).starting_team_index as i32;
307
 
308
  if alliance_group != starting_team2 && seen[alliance_id as usize] == 0 {
309
  let starting_bitmask2 =
 
432
  /// object.
433
  pub unsafe fn init_turn_state(wrapper: *mut u8) {
434
  let ddgame = ddgame_from_wrapper(wrapper);
435
+ let game_info = (*ddgame).game_info;
436
 
437
  // wrapper+0x458 = -1, wrapper+0x450 = 0, wrapper+0x454 = 0
438
  *(wrapper.add(0x458) as *mut u32) = 0xFFFF_FFFF;
 
440
  *(wrapper.add(0x454) as *mut u32) = 0;
441
 
442
  // DDGame+0x72E0/E4 = -1, DDGame+0x72E8 = 0
443
+ (*ddgame)._unknown_72e0 = 0xFFFF_FFFF;
444
+ (*ddgame).render_slot_count = 0xFFFF_FFFF;
445
+ (*ddgame)._unknown_72e8 = 0;
446
 
447
+ // Zero render entry table first u32 at DDGame+0x73B0, stride 0x14, while offset < 0x118
448
  {
449
+ let base = core::ptr::addr_of_mut!((*ddgame).render_entries) as *mut u8;
450
  let mut off = 0u32;
451
  while off < 0x118 {
452
+ *(base.add(off as usize) as *mut u32) = 0;
453
  off += 0x14;
454
  }
455
  }
456
 
457
  // More DDGame field zeroing
458
+ (*ddgame).render_state_flag = 0;
459
+ (*ddgame)._field_72f4 = 0;
460
+ (*ddgame)._field_72f8 = 0;
461
+ (*ddgame)._field_72fc = 0;
462
+ (*ddgame)._field_7300 = 0;
463
+ (*ddgame)._field_7304 = 0;
464
+
465
+ // DDGame.rng_state_1/2 = game_info.rng_seed (RNG seed from scheme)
466
+ let rng_seed = (*game_info).rng_seed;
467
+ (*ddgame).rng_state_1 = rng_seed;
468
+ (*ddgame).rng_state_2 = rng_seed; // duplicate
469
+
470
+ (*ddgame)._field_7378 = 0;
471
+ (*ddgame)._field_7374 = 0;
472
+ (*ddgame)._field_737c = 0;
473
+ (*ddgame)._field_77dc = 0;
474
+ (*ddgame)._field_77e0 = 0;
475
+ (*ddgame)._field_7784 = 0;
476
+
477
+ // DDGame._field_7788 = game_info._field_f362 (byte → u32)
478
+ (*ddgame)._field_7788 = (*game_info)._field_f362 as u32;
479
+ (*ddgame)._field_778c = 0x10000; // Fixed-point 1.0
480
+ (*ddgame)._field_7790 = 0;
481
 
482
  // Camera center: (level_width << 16) / 2, (level_height << 16) / 2
483
+ let level_width = (*ddgame).level_width as i32;
484
+ let level_height = (*ddgame).level_height as i32;
485
  let cx = (level_width << 16) / 2;
486
  let cy = (level_height << 16) / 2;
487
+ (*ddgame).viewport_width = cx;
488
+ (*ddgame).viewport_width_2 = cx; // duplicate
489
+ (*ddgame).viewport_height = cy;
490
+ (*ddgame).viewport_height_2 = cy; // duplicate
491
 
492
+ (*ddgame)._field_7d84 = 0;
493
+ (*ddgame)._field_7e4c = 0;
494
+ (*ddgame)._field_77d4 = 0;
495
+ (*ddgame)._field_77d8 = 0;
496
 
497
  // Per-team loop
498
+ let num_teams = (*game_info).num_teams as i32;
499
  if num_teams > 0 {
500
+ for i in 0..num_teams as usize {
501
+ (*ddgame)._field_7d88[i] = 0;
502
+ (*ddgame)._field_7dbc[i] = 1;
503
+ (*ddgame)._field_7dc9[i] = 1;
504
+ (*ddgame)._field_7dd6[i] = 0;
505
+ (*ddgame)._field_7de3[i] = 0;
506
+ (*ddgame)._field_7df0[i] = 0;
 
 
507
  }
508
  }
509
 
510
+ (*ddgame)._field_7e03 = 0;
511
+ (*ddgame)._field_7e04 = 0;
512
 
513
  // Call DDGame__InitFeatureFlags (600-line feature flag init, bridged)
514
  wa_init_feature_flags(wrapper);
515
 
516
  // Post-feature-flag field writes
517
+ (*ddgame)._field_7e41 = 0;
518
+ (*ddgame)._fields_7e50 = [0u32; 8];
519
+ (*ddgame)._fields_7e88 = [0u32; 5];
520
+ (*ddgame).field_7ea0 = 0;
521
+ (*ddgame).field_7ea4 = 0;
522
+
523
+ (*ddgame)._field_8148 = 1;
 
 
 
 
 
 
 
 
 
 
 
524
 
525
  let ddgame2 = ddgame_from_wrapper(wrapper);
526
+ (*ddgame2)._field_8158 = 0;
527
+ (*ddgame2)._field_815c = 0;
528
  let ddgame3 = ddgame_from_wrapper(wrapper);
529
+ (*ddgame3)._field_8160 = 0;
530
+ (*ddgame3)._field_8164 = 0;
531
 
532
+ // Vtable dispatch: DDGame.landscape → vtable slot 1,
533
+ // param = game_info.donkey_disabled (byte at +0xD94C)
534
  let ddgame4 = ddgame_from_wrapper(wrapper);
535
+ let landscape = (*ddgame4).landscape as *mut u8;
536
  if !landscape.is_null() {
537
  let vtable = *(landscape as *const *const usize);
538
  let slot1: unsafe extern "thiscall" fn(*mut u8, u32) = core::mem::transmute(*vtable.add(1));
539
+ let param = (*(*ddgame4).game_info).donkey_disabled as u32;
 
540
  slot1(landscape, param);
541
  }
542
  }
 
545
  ///
546
  /// Convention: usercall(EAX=wrapper), plain RET.
547
  ///
548
+ /// Checks game_info.landscape_scheme_flag and dispatches landscape vtable slot 6 with
549
+ /// appropriate parameters, then updates DDGame.level_width_raw.
550
  pub unsafe fn init_landscape_flags(wrapper: *mut u8) {
551
  let ddgame = ddgame_from_wrapper(wrapper);
552
+ let game_info = (*ddgame).game_info;
553
 
554
+ let scheme_flag = (*game_info).landscape_scheme_flag;
555
 
556
  // Read the 4 DDGame fields used as params
557
+ let field_7318 = (*ddgame).gfx_color_table[3];
558
+ let field_730c = (*ddgame).gfx_color_table[0];
559
+ let field_734c = (*ddgame)._field_734c;
560
+ let field_7340 = (*ddgame)._field_7340;
561
 
562
+ // DDGame.landscape object
563
+ let landscape = (*ddgame).landscape as *mut u8;
564
  let vtable = *(landscape as *const *const usize);
565
  // Vtable slot 6 = offset +0x18
566
  let slot6: unsafe extern "thiscall" fn(*mut u8, u32, u32, u32, u32, u32, u32, u32, u32) =
 
571
  slot6(
572
  landscape, 1, 1, 1, 1, field_7318, field_730c, field_734c, field_7340,
573
  );
574
+ (*ddgame).level_width_raw = 1;
575
+ } else if (*ddgame).level_width_raw != 0 {
576
  // Call with (0, 0, 1, 0, field_7318, field_730c, field_734c, field_7340)
577
  slot6(
578
  landscape, 0, 0, 1, 0, field_7318, field_730c, field_734c, field_7340,