paavohtl Claude Sonnet 4.6 commited on
Commit
df7c678
·
1 Parent(s): d467819

feat: add CTaskTeam struct (per-team entity, 0x460 bytes)

Browse files

- CTaskTeam (0x460 bytes, vtable 0x669EE4, class_type=10): per-team entity
with team_index (+0x38), _item_slots[100] (+0x88), worm_count (+0x218)
- Size confirmed from malloc(0x460) in FUN_005528c0 (the factory function)
- Secondary interface vtable at +0x30 (Ghidra 0x669F00)
- Registered in SharedData with type code 0x15 (team_index as key)
- Rename CTaskTurnGame._ctaskteam → _game_ctx with detailed comment
explaining it's the intermediate game-context base (class_type=5, vtable
0x669C44), NOT the per-team CTaskTeam — different class, different size
- Add CTaskTeam offset_of! checks in validation (team_index, worm_count)
- Add CTaskTeam inspector section in debug UI (team_index, worm_count)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

crates/openwa-core/src/task.rs CHANGED
@@ -566,6 +566,43 @@ impl CTaskOilDrum {
566
  }
567
  }
568
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
569
  /// Fire/flame entity task.
570
  ///
571
  /// Extends CTask (not CGameTask) — no physics body.
@@ -634,9 +671,22 @@ const _: () = assert!(core::mem::size_of::<CTaskFire>() == 0xD8);
634
  pub struct CTaskTurnGame {
635
  /// 0x00-0x2F: CTask base (vtable, parent, children, shared_data, ddgame, …)
636
  pub base: CTask,
637
- /// 0x30-0xDB: CTaskTeam base region (opaque — includes secondary interface pointer
638
- /// at +0x30, team position/physics fields, and per-team bookkeeping).
639
- pub _ctaskteam: [u8; 0xAC],
 
 
 
 
 
 
 
 
 
 
 
 
 
640
 
641
  // ---- CTaskTurnGame-specific fields (0xDC onwards) ----
642
 
 
566
  }
567
  }
568
 
569
+ /// Per-team state-tracker task — one instance per team, child of CTaskTurnGame.
570
+ ///
571
+ /// Tracks per-team data: which team number it represents, how many worms were
572
+ /// spawned, and a weapon/item slot table. Registered in the SharedData hash
573
+ /// table with type code 0x15 (21).
574
+ ///
575
+ /// Inheritance: extends CTask directly (CTask base at +0x00).
576
+ /// class_type = 10. Allocation: 0x460 bytes via `operator new` (0x5C0AB8).
577
+ /// Constructor: `CTaskTeam__Constructor` (0x555BB0).
578
+ /// Primary vtable: `CTaskTeam__vtable2` (0x00669EE4).
579
+ /// Secondary interface vtable: 0x00669F00 (at object +0x30).
580
+ ///
581
+ /// Key constructor logic (0x555BB0):
582
+ /// - `team_index` at +0x38: 1-based team number (passed as param_3)
583
+ /// - `_item_slots[0..99]` at +0x88: up to 100 weapon/item IDs loaded from scheme
584
+ /// - `worm_count` at +0x218: number of CTaskWorm children constructed (1-indexed)
585
+ /// - SharedData node (0x30 bytes) registered with key = (team_index, type=0x15)
586
+ #[repr(C)]
587
+ pub struct CTaskTeam {
588
+ /// 0x00–0x2F: CTask base (vtable, parent, children, shared_data, ddgame, …)
589
+ pub base: CTask,
590
+ /// 0x30: Secondary interface vtable pointer (Ghidra 0x00669F00)
591
+ pub _secondary_vtable: u32,
592
+ pub _unknown_34: u32,
593
+ /// 0x38: Team number, **1-based**. Matches TeamArenaState slot index.
594
+ pub team_index: u32,
595
+ pub _unknown_3c: [u8; 0x4C],
596
+ /// 0x88–0x217: Up to 100 weapon/item slot entries written from scheme data when
597
+ /// specific scheme flags are set. `worm_count` tracks how many entries are valid.
598
+ pub _item_slots: [u32; 100],
599
+ /// 0x218: Number of CTaskWorm children constructed for this team.
600
+ pub worm_count: u32,
601
+ pub _unknown_21c: [u8; 0x244],
602
+ }
603
+
604
+ const _: () = assert!(core::mem::size_of::<CTaskTeam>() == 0x460);
605
+
606
  /// Fire/flame entity task.
607
  ///
608
  /// Extends CTask (not CGameTask) — no physics body.
 
671
  pub struct CTaskTurnGame {
672
  /// 0x00-0x2F: CTask base (vtable, parent, children, shared_data, ddgame, …)
673
  pub base: CTask,
674
+ /// 0x30-0xDB: Intermediate game-context base region (opaque).
675
+ ///
676
+ /// Initialised by `CTaskTeam__Constructor_Maybe` (0x550EB0) which creates a
677
+ /// class_type-5 object with primary vtable 0x669E34 and secondary interface vtable
678
+ /// 0x669C44 at +0x30. CTaskTurnGame then overrides the primary vtable and
679
+ /// class_type.
680
+ ///
681
+ /// Known non-zero fields inside this region (all offsets relative to object base):
682
+ /// +0x30: secondary interface vtable ptr (0x669C44 Ghidra)
683
+ /// +0x40 / +0x44: landscape height as Fixed16.16 (e.g. Fixed(696.0) for 696px map)
684
+ /// +0x48 / +0x58 / +0x68: Fixed(-1) — boundary sentinels
685
+ /// +0x70 / +0x74: Fixed(1.0) — x/y scale factors
686
+ /// +0x7C: team count (copy of DDGame+0x44C)
687
+ /// +0xD0: small integer (≈ total worm count)
688
+ /// +0xD4 / +0xD8: heap pointers (role unknown)
689
+ pub _game_ctx: [u8; 0xAC],
690
 
691
  // ---- CTaskTurnGame-specific fields (0xDC onwards) ----
692
 
crates/openwa-debugui/src/app.rs CHANGED
@@ -3,7 +3,7 @@ use openwa_core::address::va;
3
  use openwa_core::ddgame::DDGame;
4
  use openwa_core::ddgame_wrapper::DDGameWrapper;
5
  use openwa_core::rebase::rb;
6
- use openwa_core::task::{CTask, CTaskCloud, CTaskFire, CTaskMine, CTaskOilDrum, CTaskTurnGame, CTaskWorm};
7
 
8
  use crate::log;
9
 
@@ -484,6 +484,19 @@ impl DebugApp {
484
  });
485
  }
486
 
 
 
 
 
 
 
 
 
 
 
 
 
 
487
  // --- CTaskFire-specific fields ---
488
  if name == "CTaskFire" {
489
  let fire = addr as *const CTaskFire;
 
3
  use openwa_core::ddgame::DDGame;
4
  use openwa_core::ddgame_wrapper::DDGameWrapper;
5
  use openwa_core::rebase::rb;
6
+ use openwa_core::task::{CTask, CTaskCloud, CTaskFire, CTaskMine, CTaskOilDrum, CTaskTeam, CTaskTurnGame, CTaskWorm};
7
 
8
  use crate::log;
9
 
 
484
  });
485
  }
486
 
487
+ // --- CTaskTeam-specific fields ---
488
+ if name == "CTaskTeam" {
489
+ let team = addr as *const CTaskTeam;
490
+ egui::CollapsingHeader::new("CTaskTeam")
491
+ .default_open(true)
492
+ .show(ui, |ui| {
493
+ egui::Grid::new("team_grid").striped(true).show(ui, |ui| {
494
+ ui.label("team_index"); ui.label(format!("{}", (*team).team_index)); ui.end_row();
495
+ ui.label("worm_count"); ui.label(format!("{}", (*team).worm_count)); ui.end_row();
496
+ });
497
+ });
498
+ }
499
+
500
  // --- CTaskFire-specific fields ---
501
  if name == "CTaskFire" {
502
  let fire = addr as *const CTaskFire;
crates/openwa-wormkit/src/validation/mod.rs CHANGED
@@ -11,7 +11,7 @@ use std::sync::atomic::{AtomicU32, Ordering};
11
 
12
  use openwa_core::rebase::rb;
13
  use openwa_core::address::va;
14
- use openwa_core::task::{CTask, CGameTask, CTaskTurnGame, SharedDataTable};
15
  use openwa_core::ddgame::DDGame;
16
  use openwa_core::ddgame_wrapper::DDGameWrapper;
17
 
@@ -297,6 +297,11 @@ fn validate_struct_offsets(result: &mut ValidationResult) {
297
  check_offset!(result, DDGameWrapper, landscape, 0x4CC);
298
  check_offset!(result, DDGameWrapper, display, 0x4D0);
299
 
 
 
 
 
 
300
  let _ = log_validation("");
301
  let _ = log_validation(" CTaskTurnGame:");
302
  check_offset!(result, CTaskTurnGame, worm_active, 0x108);
 
11
 
12
  use openwa_core::rebase::rb;
13
  use openwa_core::address::va;
14
+ use openwa_core::task::{CTask, CGameTask, CTaskTeam, CTaskTurnGame, SharedDataTable};
15
  use openwa_core::ddgame::DDGame;
16
  use openwa_core::ddgame_wrapper::DDGameWrapper;
17
 
 
297
  check_offset!(result, DDGameWrapper, landscape, 0x4CC);
298
  check_offset!(result, DDGameWrapper, display, 0x4D0);
299
 
300
+ let _ = log_validation("");
301
+ let _ = log_validation(" CTaskTeam:");
302
+ check_offset!(result, CTaskTeam, team_index, 0x38);
303
+ check_offset!(result, CTaskTeam, worm_count, 0x218);
304
+
305
  let _ = log_validation("");
306
  let _ = log_validation(" CTaskTurnGame:");
307
  check_offset!(result, CTaskTurnGame, worm_active, 0x108);