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

refactor: rename team arena types for clarity

Browse files

- FullTeamBlock → TeamBlock (nothing partial about it)
- TeamBlockSlot0 → TeamSlot0 (shorter, follows from TeamBlock)
- TeamBlockHeader → TeamHeader (shorter)
- _teams_region → team_blocks_region (public, used field)
- _worm_overlap → worm_overlap (used field)
- _metadata → trailer (it's a 0x3C trailing region)

Runtime hex dump confirmed the existing struct layout is correct:
header(0x9C union) + worms[7](0x9C each) + trailer(0x3C) = 0x51C.

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

crates/openwa-core/src/engine/ddgame.rs CHANGED
@@ -1428,17 +1428,17 @@ pub mod offsets {
1428
  /// GetAmmo/AddAmmo/SubtractAmmo.
1429
  pub const TEAM_ARENA_STATE: usize = 0x4628;
1430
 
1431
- // === Team block array (7 × FullTeamBlock, stride 0x51C) ===
1432
  /// Start of team block array within DDGame (7 blocks, stride 0x51C).
1433
  /// Derived: entry_ptr(team=0) - 0x598 = 0x4628 - 0x598 = 0x4090.
1434
  /// Runtime-confirmed: block[0] is zeroed preamble, blocks[1-6] hold team data.
1435
  pub const TEAM_BLOCKS: usize = 0x4090;
1436
 
1437
- /// Byte offset from TeamArenaState base back to FullTeamBlock array start.
1438
- /// `blocks_ptr = (tws_base as *const c_char).sub(ARENA_TO_BLOCKS) as *const FullTeamBlock`
1439
  ///
1440
  /// entry_ptr(0) = DDGame+0x4628 = TEAM_BLOCKS + 0x598.
1441
- /// 0x598 = sizeof(FullTeamBlock) + 0x7C = one block + offset into TeamBlockHeader.
1442
  pub const ARENA_TO_BLOCKS: usize = 0x598;
1443
 
1444
  // === FUN_00526120 init offsets (stride 0x194, 10 entries) ===
@@ -1636,7 +1636,7 @@ const _: () = assert!(core::mem::size_of::<SoundQueueEntry>() == 0x24);
1636
  ///
1637
  /// WA supports up to 8 playable worms per team. The original code accesses
1638
  /// worms via raw pointer arithmetic from the team entry pointer, using
1639
- /// stride 0x9C. This means the 8th worm crosses the FullTeamBlock boundary
1640
  /// into the next block's header slot — see `TeamArenaRef::team_worm()`.
1641
  ///
1642
  /// Field offsets confirmed by runtime memory dump (validator DLL):
@@ -1675,7 +1675,7 @@ pub struct WormEntry {
1675
 
1676
  const _: () = assert!(core::mem::size_of::<WormEntry>() == 0x9C);
1677
 
1678
- /// Team-level metadata stored at slot 0 of each FullTeamBlock (0x9C bytes).
1679
  ///
1680
  /// This struct overlays the same memory as a WormEntry but interprets the
1681
  /// high offsets (0x6C+) as team metadata rather than worm data. The low
@@ -1685,9 +1685,9 @@ const _: () = assert!(core::mem::size_of::<WormEntry>() == 0x9C);
1685
  /// Accessed via `TeamArenaRef::team_header()` and `team_header_b()`.
1686
  #[repr(C)]
1687
  #[derive(Clone, Copy)]
1688
- pub struct TeamBlockHeader {
1689
  /// 0x00-0x5F: Opaque — may hold 8th worm data from previous team.
1690
- pub _worm_overlap: [u8; 0x60],
1691
  /// 0x60-0x6B: Unknown padding.
1692
  pub _unknown_60: [u8; 0x0C],
1693
  /// 0x6C: Team eliminated flag (nonzero = eliminated).
@@ -1710,27 +1710,27 @@ pub struct TeamBlockHeader {
1710
  pub _unknown_98: [u8; 4],
1711
  }
1712
 
1713
- const _: () = assert!(core::mem::size_of::<TeamBlockHeader>() == 0x9C);
1714
 
1715
- /// Union for slot 0 of a FullTeamBlock.
1716
  ///
1717
  /// This slot is dual-purpose: its high offsets store team metadata
1718
- /// (`TeamBlockHeader`), while its low offsets may contain the 8th worm
1719
  /// of the previous team (`WormEntry`). The two uses don't conflict
1720
  /// because worm data occupies 0x00-0x5F and header data starts at 0x6C.
1721
  #[repr(C)]
1722
  #[derive(Clone, Copy)]
1723
- pub union TeamBlockSlot0 {
1724
  /// View as worm data (used when the previous team has 8 worms).
1725
  pub worm: core::mem::ManuallyDrop<WormEntry>,
1726
  /// View as team metadata (eliminated, alliance, worm_count, etc.).
1727
- pub team: core::mem::ManuallyDrop<TeamBlockHeader>,
1728
  }
1729
 
1730
  /// Full per-team data block (0x51C bytes, 7 blocks in DDGame).
1731
  ///
1732
- /// Each block starts with a `TeamBlockSlot0` union (0x9C bytes) that serves
1733
- /// dual purpose: its high offsets hold team metadata (`TeamBlockHeader`),
1734
  /// while its low offsets may contain the 8th worm of the previous team.
1735
  /// The remaining 7 worm slots follow, then 0x3C bytes of metadata.
1736
  ///
@@ -1739,19 +1739,19 @@ pub union TeamBlockSlot0 {
1739
  /// which uses raw pointer arithmetic and naturally crosses block boundaries.
1740
  ///
1741
  /// **Header access**: Use `TeamArenaRef::team_header(idx)` to get the
1742
- /// `TeamBlockHeader` for a team (reads from `blocks[idx+1].header.team`).
1743
  #[repr(C)]
1744
- pub struct FullTeamBlock {
1745
- /// 0x000-0x09B: Header slot (union of TeamBlockHeader and WormEntry).
1746
  /// Team metadata at high offsets; may hold 8th worm data at low offsets.
1747
- pub header: TeamBlockSlot0,
1748
  /// 0x09C-0x4DF: 7 worm entries (slots 1-7, stride 0x9C)
1749
  pub worms: [WormEntry; 7],
1750
  /// 0x4E0-0x51B: Team metadata (0x3C bytes)
1751
- pub _metadata: [u8; 0x3C],
1752
  }
1753
 
1754
- const _: () = assert!(core::mem::size_of::<FullTeamBlock>() == 0x51C);
1755
 
1756
  /// Team arena state area within DDGame (at DDGame + 0x4628).
1757
  ///
@@ -1793,12 +1793,12 @@ pub struct TeamArenaState {
1793
  /// This region contains 7 team entries at stride 0x51C (1-indexed, index 0
1794
  /// is preamble). The original WA.exe accesses team data via raw pointer
1795
  /// arithmetic relative to the arena base. In our Rust code, team data is
1796
- /// accessed through `TeamArenaRef`, `FullTeamBlock`, and `TeamBlockHeader`,
1797
  /// so this region is treated as opaque padding.
1798
  ///
1799
  /// The 7th entry (team 6, 1-indexed) only has 8 bytes before team_count;
1800
  /// the rest overlaps with weapon_slots.
1801
- pub _teams_region: [u8; 0x1EB0],
1802
  /// 0x1EB0: Number of teams in the game (used by team iteration loops)
1803
  pub team_count: i32,
1804
  /// 0x1EB4: Per-alliance weapon ammo and delay data.
@@ -1885,7 +1885,7 @@ impl TeamArenaState {
1885
  ///
1886
  /// Wraps the raw `base: u32` from trampoline register captures and provides
1887
  /// accessor methods that encapsulate the backward pointer arithmetic to reach
1888
- /// FullTeamBlock worm data. The FullTeamBlock array lives 0x598 bytes before
1889
  /// the TeamArenaState in DDGame memory.
1890
  ///
1891
  /// # Safety
@@ -1922,10 +1922,10 @@ impl TeamArenaRef {
1922
  &mut *(self.base as *mut TeamArenaState)
1923
  }
1924
 
1925
- /// Get pointer to the FullTeamBlock array base.
1926
  #[inline]
1927
- pub unsafe fn blocks(&self) -> *const FullTeamBlock {
1928
- self.base.sub(offsets::ARENA_TO_BLOCKS) as *const FullTeamBlock
1929
  }
1930
 
1931
  /// Get the team header (metadata) for a team.
@@ -1933,7 +1933,7 @@ impl TeamArenaRef {
1933
  /// Returns `&block[team_idx+1].header.team`, which holds team metadata:
1934
  /// worm_count, eliminated flag, weapon_alliance, team_name.
1935
  #[inline]
1936
- pub unsafe fn team_header(&self, team_idx: usize) -> &TeamBlockHeader {
1937
  &(*self.blocks().add(team_idx + 1)).header.team
1938
  }
1939
 
@@ -1941,7 +1941,7 @@ impl TeamArenaRef {
1941
  ///
1942
  /// Uses raw pointer arithmetic matching the original WA code:
1943
  /// `base + team_idx * 0x51C + worm_num * 0x9C - 0x598`.
1944
- /// This naturally crosses FullTeamBlock boundaries when worm_num = 8,
1945
  /// since the 8th worm's early fields (state, health) spill into the
1946
  /// next block's header slot. The header metadata lives at high offsets
1947
  /// (0x6C+) that don't conflict with worm data (0x00-0x5F).
@@ -1964,7 +1964,7 @@ impl TeamArenaRef {
1964
  /// **Note**: For accessing worms, prefer `team_worm()` which handles
1965
  /// 8-worm teams correctly via cross-boundary pointer arithmetic.
1966
  #[inline]
1967
- pub unsafe fn team_and_header(&self, team_idx: usize) -> (&FullTeamBlock, &TeamBlockHeader) {
1968
  let blocks = self.blocks();
1969
  let block = &*blocks.add(team_idx);
1970
  let header = &(*blocks.add(team_idx + 1)).header.team;
@@ -1976,14 +1976,14 @@ impl TeamArenaRef {
1976
  /// Pattern B indexes from block[i+2] for 0-indexed team `i`:
1977
  /// `base + 0x510 + i*0x51C` = `blocks[i+2].header.team + 0x70`.
1978
  #[inline]
1979
- pub unsafe fn team_header_b(&self, team_idx: usize) -> &TeamBlockHeader {
1980
  &(*self.blocks().add(team_idx + 2)).header.team
1981
  }
1982
 
1983
  /// Get mutable team header for Pattern B access.
1984
  #[inline]
1985
- pub unsafe fn team_header_b_mut(&self, team_idx: usize) -> &mut TeamBlockHeader {
1986
- &mut (*(self.blocks() as *mut FullTeamBlock).add(team_idx + 2))
1987
  .header
1988
  .team
1989
  }
 
1428
  /// GetAmmo/AddAmmo/SubtractAmmo.
1429
  pub const TEAM_ARENA_STATE: usize = 0x4628;
1430
 
1431
+ // === Team block array (7 × TeamBlock, stride 0x51C) ===
1432
  /// Start of team block array within DDGame (7 blocks, stride 0x51C).
1433
  /// Derived: entry_ptr(team=0) - 0x598 = 0x4628 - 0x598 = 0x4090.
1434
  /// Runtime-confirmed: block[0] is zeroed preamble, blocks[1-6] hold team data.
1435
  pub const TEAM_BLOCKS: usize = 0x4090;
1436
 
1437
+ /// Byte offset from TeamArenaState base back to TeamBlock array start.
1438
+ /// `blocks_ptr = (tws_base as *const c_char).sub(ARENA_TO_BLOCKS) as *const TeamBlock`
1439
  ///
1440
  /// entry_ptr(0) = DDGame+0x4628 = TEAM_BLOCKS + 0x598.
1441
+ /// 0x598 = sizeof(TeamBlock) + 0x7C = one block + offset into TeamHeader.
1442
  pub const ARENA_TO_BLOCKS: usize = 0x598;
1443
 
1444
  // === FUN_00526120 init offsets (stride 0x194, 10 entries) ===
 
1636
  ///
1637
  /// WA supports up to 8 playable worms per team. The original code accesses
1638
  /// worms via raw pointer arithmetic from the team entry pointer, using
1639
+ /// stride 0x9C. This means the 8th worm crosses the TeamBlock boundary
1640
  /// into the next block's header slot — see `TeamArenaRef::team_worm()`.
1641
  ///
1642
  /// Field offsets confirmed by runtime memory dump (validator DLL):
 
1675
 
1676
  const _: () = assert!(core::mem::size_of::<WormEntry>() == 0x9C);
1677
 
1678
+ /// Team-level metadata stored at slot 0 of each TeamBlock (0x9C bytes).
1679
  ///
1680
  /// This struct overlays the same memory as a WormEntry but interprets the
1681
  /// high offsets (0x6C+) as team metadata rather than worm data. The low
 
1685
  /// Accessed via `TeamArenaRef::team_header()` and `team_header_b()`.
1686
  #[repr(C)]
1687
  #[derive(Clone, Copy)]
1688
+ pub struct TeamHeader {
1689
  /// 0x00-0x5F: Opaque — may hold 8th worm data from previous team.
1690
+ pub worm_overlap: [u8; 0x60],
1691
  /// 0x60-0x6B: Unknown padding.
1692
  pub _unknown_60: [u8; 0x0C],
1693
  /// 0x6C: Team eliminated flag (nonzero = eliminated).
 
1710
  pub _unknown_98: [u8; 4],
1711
  }
1712
 
1713
+ const _: () = assert!(core::mem::size_of::<TeamHeader>() == 0x9C);
1714
 
1715
+ /// Union for slot 0 of a TeamBlock.
1716
  ///
1717
  /// This slot is dual-purpose: its high offsets store team metadata
1718
+ /// (`TeamHeader`), while its low offsets may contain the 8th worm
1719
  /// of the previous team (`WormEntry`). The two uses don't conflict
1720
  /// because worm data occupies 0x00-0x5F and header data starts at 0x6C.
1721
  #[repr(C)]
1722
  #[derive(Clone, Copy)]
1723
+ pub union TeamSlot0 {
1724
  /// View as worm data (used when the previous team has 8 worms).
1725
  pub worm: core::mem::ManuallyDrop<WormEntry>,
1726
  /// View as team metadata (eliminated, alliance, worm_count, etc.).
1727
+ pub team: core::mem::ManuallyDrop<TeamHeader>,
1728
  }
1729
 
1730
  /// Full per-team data block (0x51C bytes, 7 blocks in DDGame).
1731
  ///
1732
+ /// Each block starts with a `TeamSlot0` union (0x9C bytes) that serves
1733
+ /// dual purpose: its high offsets hold team metadata (`TeamHeader`),
1734
  /// while its low offsets may contain the 8th worm of the previous team.
1735
  /// The remaining 7 worm slots follow, then 0x3C bytes of metadata.
1736
  ///
 
1739
  /// which uses raw pointer arithmetic and naturally crosses block boundaries.
1740
  ///
1741
  /// **Header access**: Use `TeamArenaRef::team_header(idx)` to get the
1742
+ /// `TeamHeader` for a team (reads from `blocks[idx+1].header.team`).
1743
  #[repr(C)]
1744
+ pub struct TeamBlock {
1745
+ /// 0x000-0x09B: Header slot (union of TeamHeader and WormEntry).
1746
  /// Team metadata at high offsets; may hold 8th worm data at low offsets.
1747
+ pub header: TeamSlot0,
1748
  /// 0x09C-0x4DF: 7 worm entries (slots 1-7, stride 0x9C)
1749
  pub worms: [WormEntry; 7],
1750
  /// 0x4E0-0x51B: Team metadata (0x3C bytes)
1751
+ pub trailer: [u8; 0x3C],
1752
  }
1753
 
1754
+ const _: () = assert!(core::mem::size_of::<TeamBlock>() == 0x51C);
1755
 
1756
  /// Team arena state area within DDGame (at DDGame + 0x4628).
1757
  ///
 
1793
  /// This region contains 7 team entries at stride 0x51C (1-indexed, index 0
1794
  /// is preamble). The original WA.exe accesses team data via raw pointer
1795
  /// arithmetic relative to the arena base. In our Rust code, team data is
1796
+ /// accessed through `TeamArenaRef`, `TeamBlock`, and `TeamHeader`,
1797
  /// so this region is treated as opaque padding.
1798
  ///
1799
  /// The 7th entry (team 6, 1-indexed) only has 8 bytes before team_count;
1800
  /// the rest overlaps with weapon_slots.
1801
+ pub team_blocks_region: [u8; 0x1EB0],
1802
  /// 0x1EB0: Number of teams in the game (used by team iteration loops)
1803
  pub team_count: i32,
1804
  /// 0x1EB4: Per-alliance weapon ammo and delay data.
 
1885
  ///
1886
  /// Wraps the raw `base: u32` from trampoline register captures and provides
1887
  /// accessor methods that encapsulate the backward pointer arithmetic to reach
1888
+ /// TeamBlock worm data. The TeamBlock array lives 0x598 bytes before
1889
  /// the TeamArenaState in DDGame memory.
1890
  ///
1891
  /// # Safety
 
1922
  &mut *(self.base as *mut TeamArenaState)
1923
  }
1924
 
1925
+ /// Get pointer to the TeamBlock array base.
1926
  #[inline]
1927
+ pub unsafe fn blocks(&self) -> *const TeamBlock {
1928
+ self.base.sub(offsets::ARENA_TO_BLOCKS) as *const TeamBlock
1929
  }
1930
 
1931
  /// Get the team header (metadata) for a team.
 
1933
  /// Returns `&block[team_idx+1].header.team`, which holds team metadata:
1934
  /// worm_count, eliminated flag, weapon_alliance, team_name.
1935
  #[inline]
1936
+ pub unsafe fn team_header(&self, team_idx: usize) -> &TeamHeader {
1937
  &(*self.blocks().add(team_idx + 1)).header.team
1938
  }
1939
 
 
1941
  ///
1942
  /// Uses raw pointer arithmetic matching the original WA code:
1943
  /// `base + team_idx * 0x51C + worm_num * 0x9C - 0x598`.
1944
+ /// This naturally crosses TeamBlock boundaries when worm_num = 8,
1945
  /// since the 8th worm's early fields (state, health) spill into the
1946
  /// next block's header slot. The header metadata lives at high offsets
1947
  /// (0x6C+) that don't conflict with worm data (0x00-0x5F).
 
1964
  /// **Note**: For accessing worms, prefer `team_worm()` which handles
1965
  /// 8-worm teams correctly via cross-boundary pointer arithmetic.
1966
  #[inline]
1967
+ pub unsafe fn team_and_header(&self, team_idx: usize) -> (&TeamBlock, &TeamHeader) {
1968
  let blocks = self.blocks();
1969
  let block = &*blocks.add(team_idx);
1970
  let header = &(*blocks.add(team_idx + 1)).header.team;
 
1976
  /// Pattern B indexes from block[i+2] for 0-indexed team `i`:
1977
  /// `base + 0x510 + i*0x51C` = `blocks[i+2].header.team + 0x70`.
1978
  #[inline]
1979
+ pub unsafe fn team_header_b(&self, team_idx: usize) -> &TeamHeader {
1980
  &(*self.blocks().add(team_idx + 2)).header.team
1981
  }
1982
 
1983
  /// Get mutable team header for Pattern B access.
1984
  #[inline]
1985
+ pub unsafe fn team_header_b_mut(&self, team_idx: usize) -> &mut TeamHeader {
1986
+ &mut (*(self.blocks() as *mut TeamBlock).add(team_idx + 2))
1987
  .header
1988
  .team
1989
  }
crates/openwa-core/src/engine/mod.rs CHANGED
@@ -9,8 +9,8 @@ pub mod net_bridge;
9
  pub mod net_wrapper;
10
 
11
  pub use ddgame::{
12
- DDGame, FullTeamBlock, RenderEntry, SoundQueueEntry, TeamArenaRef, TeamArenaState,
13
- TeamBlockHeader, TeamBlockSlot0, TeamIndexMap, WormEntry, GAME_PHASE_NORMAL_MIN,
14
  GAME_PHASE_SUDDEN_DEATH,
15
  };
16
  pub use ddgame_wrapper::DDGameWrapper;
 
9
  pub mod net_wrapper;
10
 
11
  pub use ddgame::{
12
+ DDGame, RenderEntry, SoundQueueEntry, TeamArenaRef, TeamArenaState, TeamBlock,
13
+ TeamHeader, TeamIndexMap, TeamSlot0, WormEntry, GAME_PHASE_NORMAL_MIN,
14
  GAME_PHASE_SUDDEN_DEATH,
15
  };
16
  pub use ddgame_wrapper::DDGameWrapper;
crates/openwa-wormkit/src/replacements/weapon.rs CHANGED
@@ -409,7 +409,7 @@ unsafe fn fire_skip_go(worm: *const CTaskWorm, entry: *const WeaponEntry) {
409
  let bit = 1u32 << bit_index;
410
 
411
  // First DWORD of each team's region in TeamArenaState (stride 0x51C)
412
- let flags_ptr = ddgame.team_arena._teams_region
413
  [team_index * 0x51C..][..4]
414
  .as_ptr() as *mut u32;
415
  let flags = *flags_ptr;
 
409
  let bit = 1u32 << bit_index;
410
 
411
  // First DWORD of each team's region in TeamArenaState (stride 0x51C)
412
+ let flags_ptr = ddgame.team_arena.team_blocks_region
413
  [team_index * 0x51C..][..4]
414
  .as_ptr() as *mut u32;
415
  let flags = *flags_ptr;