paavohtl Claude Opus 4.6 (1M context) commited on
Commit
0f1b207
·
1 Parent(s): 5933e6b

feat: replace hardcoded debug UI field labels with registry lookups

Browse files

Remove CGAMETASK_FIELD_LABELS and all per-entity extra_labels arrays
from the debug UI inspector. Field names are now resolved via
registry::field_at_inherited() which walks the CTask inheritance chain
(entity → CGameTask → CTask).

Add #[derive(FieldRegistry)] to CGameTask and CTaskMissile. Add
field_at_inherited() helper for inheritance-aware offset → name lookup.

Entity types without Rust struct definitions (CTaskMine, CTaskOilDrum,
CTaskCrate) will show base class fields only until their structs are
defined — but any new struct fields are automatically picked up.

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

crates/openwa-core/src/registry.rs CHANGED
@@ -277,6 +277,37 @@ pub fn all_struct_fields() -> impl Iterator<Item = &'static StructFields> {
277
  struct_map().values().copied()
278
  }
279
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
280
  // =========================================================================
281
  // Live object tracker
282
  // =========================================================================
 
277
  struct_map().values().copied()
278
  }
279
 
280
+ /// Look up a field at `offset` using inheritance-aware search.
281
+ ///
282
+ /// For CTask-derived entities, the inheritance chain is:
283
+ /// entity-specific fields → CGameTask → CTask.
284
+ /// This function tries each registry in order, returning the first match.
285
+ ///
286
+ /// `struct_name` is the leaf class name (e.g., "CTaskWorm").
287
+ pub fn field_at_inherited(struct_name: &str, offset: u32) -> Option<&'static FieldEntry> {
288
+ // Inheritance chain for CTask hierarchy. Could be generalized later
289
+ // but this covers all current game entity types.
290
+ const CTASK_CHAIN: &[&str] = &["CGameTask", "CTask"];
291
+
292
+ // Try the leaf struct first
293
+ if let Some(fields) = struct_fields_for(struct_name) {
294
+ if let Some(entry) = fields.field_at(offset) {
295
+ return Some(entry);
296
+ }
297
+ }
298
+
299
+ // Walk the inheritance chain
300
+ for &parent in CTASK_CHAIN {
301
+ if let Some(fields) = struct_fields_for(parent) {
302
+ if let Some(entry) = fields.field_at(offset) {
303
+ return Some(entry);
304
+ }
305
+ }
306
+ }
307
+
308
+ None
309
+ }
310
+
311
  // =========================================================================
312
  // Live object tracker
313
  // =========================================================================
crates/openwa-core/src/task/game_task.rs CHANGED
@@ -1,5 +1,6 @@
1
  use super::base::CTask;
2
  use crate::fixed::Fixed;
 
3
 
4
  /// Game task - extends CTask with physics and gameplay data.
5
  ///
@@ -9,6 +10,7 @@ use crate::fixed::Fixed;
9
  /// Source: wkJellyWorm CGameTask.h
10
  ///
11
  /// Additional vtable (12 methods at offsets 0x1C-0x48 in vtable)
 
12
  #[repr(C)]
13
  pub struct CGameTask {
14
  /// 0x00-0x2F: Base CTask fields
 
1
  use super::base::CTask;
2
  use crate::fixed::Fixed;
3
+ use crate::FieldRegistry;
4
 
5
  /// Game task - extends CTask with physics and gameplay data.
6
  ///
 
10
  /// Source: wkJellyWorm CGameTask.h
11
  ///
12
  /// Additional vtable (12 methods at offsets 0x1C-0x48 in vtable)
13
+ #[derive(FieldRegistry)]
14
  #[repr(C)]
15
  pub struct CGameTask {
16
  /// 0x00-0x2F: Base CTask fields
crates/openwa-core/src/task/missile.rs CHANGED
@@ -1,5 +1,6 @@
1
  use super::game_task::CGameTask;
2
  use crate::fixed::Fixed;
 
3
 
4
  /// Projectile / missile entity task.
5
  ///
@@ -18,6 +19,7 @@ use crate::fixed::Fixed;
18
  ///
19
  /// Source: Ghidra decompilation of 0x507D10 (constructor) and
20
  /// wkJellyWorm CTaskMissile.h (field layout reference).
 
21
  #[repr(C)]
22
  pub struct CTaskMissile {
23
  /// 0x00–0xFB: CGameTask base (pos at 0x84/0x88, speed at 0x90/0x94).
 
1
  use super::game_task::CGameTask;
2
  use crate::fixed::Fixed;
3
+ use crate::FieldRegistry;
4
 
5
  /// Projectile / missile entity task.
6
  ///
 
19
  ///
20
  /// Source: Ghidra decompilation of 0x507D10 (constructor) and
21
  /// wkJellyWorm CTaskMissile.h (field layout reference).
22
+ #[derive(FieldRegistry)]
23
  #[repr(C)]
24
  pub struct CTaskMissile {
25
  /// 0x00–0xFB: CGameTask base (pos at 0x84/0x88, speed at 0x90/0x94).
crates/openwa-debugui/src/app.rs CHANGED
@@ -2,6 +2,7 @@ use eframe::egui;
2
  use openwa_core::address::va;
3
  use openwa_core::engine::{DDGame, DDGameWrapper};
4
  use openwa_core::rebase::rb;
 
5
  use openwa_core::task::{
6
  CTask, CTaskCloud, CTaskFire, CTaskTeam, CTaskTurnGame, CTaskWorm, TurnGameCtx,
7
  };
@@ -336,58 +337,18 @@ impl DebugApp {
336
  // ---------------------------------------------------------------------------
337
 
338
  /// CGameTask field labels for the base class region (0x00..0xFC).
339
- const CGAMETASK_FIELD_LABELS: &[(usize, &str)] = &[
340
- // CTask base (0x00..0x30)
341
- (0x00, "vtable"),
342
- (0x04, "parent"),
343
- (0x08, "children_data"),
344
- (0x0C, "children_watermark"),
345
- (0x10, "children_capacity"),
346
- (0x14, "class_type"),
347
- (0x18, "bit_grid"),
348
- (0x1C, "shared_data"),
349
- (0x20, "owns_shared_data"),
350
- (0x24, "msg_handler"),
351
- (0x28, "creation_flags"),
352
- (0x2C, "ddgame"),
353
- // CGameTask fields (0x30..0xFC)
354
- (0x84, "pos_x"),
355
- (0x88, "pos_y"),
356
- (0x8C, "angle"),
357
- (0x90, "speed_x"),
358
- (0x94, "speed_y"),
359
- // SoundEmitter (0xE8..0xFC)
360
- (0xE8, "emitter_vtable"),
361
- (0xEC, "emitter_unk04"),
362
- (0xF0, "emitter_unk08"),
363
- (0xF4, "local_snd_count"),
364
- (0xF8, "emitter_owner"),
365
- ];
366
-
367
  /// Display all DWORDs of a CGameTask-derived entity with labelled fields.
368
- /// `extra_labels` provides field names for offsets beyond the CGameTask base (0xFC+).
 
 
369
  unsafe fn show_game_task_raw_fields(
370
  ui: &mut egui::Ui,
371
  addr: u32,
372
  type_name: &str,
373
  total_size: usize,
374
- extra_labels: &[(usize, &str)],
375
  ) {
376
  let base = addr as *const u32;
377
 
378
- let label_for = |off: usize| -> Option<&str> {
379
- CGAMETASK_FIELD_LABELS
380
- .iter()
381
- .find(|&&(o, _)| o == off)
382
- .map(|&(_, n)| n)
383
- .or_else(|| {
384
- extra_labels
385
- .iter()
386
- .find(|&&(o, _)| o == off)
387
- .map(|&(_, n)| n)
388
- })
389
- };
390
-
391
  // Sections: CTask base, CGameTask unknowns, pos/speed, more unknowns, emitter,
392
  // then type-specific in 0x80-byte chunks to keep each section manageable.
393
  let mut sections: Vec<(usize, usize, String)> = vec![
@@ -434,7 +395,9 @@ unsafe fn show_game_task_raw_fields(
434
  for i in 0..dwords {
435
  let off = start + i * 4;
436
  let val = *base.add(off / 4);
437
- let field_name = label_for(off).unwrap_or("");
 
 
438
 
439
  ui.label(format!("+0x{:03X}", off));
440
  // Show as hex + signed decimal
@@ -592,64 +555,21 @@ impl DebugApp {
592
  // --- CTaskMine-specific fields ---
593
  if name == "CTaskMine" {
594
  show_game_task_raw_fields(
595
- ui,
596
- addr,
597
- "CTaskMine",
598
- 0x128,
599
- &[
600
- (0x0FC, "unknown_fc[0]"),
601
- (0x100, "unknown_fc[4]"),
602
- (0x104, "unknown_fc[8]"),
603
- (0x108, "unknown_fc[C]"),
604
- (0x10C, "unknown_fc[10]"),
605
- (0x110, "slot_id"),
606
- (0x114, "unknown_114"),
607
- (0x118, "fuse_timer"),
608
- (0x11C, "unknown_11c"),
609
- (0x120, "unknown_120"),
610
- (0x124, "owner_team"),
611
- ],
612
  );
613
  }
614
 
615
  // --- CTaskOilDrum-specific fields ---
616
  if name == "CTaskOilDrum" {
617
  show_game_task_raw_fields(
618
- ui,
619
- addr,
620
- "CTaskOilDrum",
621
- 0x110,
622
- &[
623
- (0x0FC, "triggered"),
624
- (0x100, "slot_id"),
625
- (0x104, "unknown_104"),
626
- (0x108, "health"),
627
- (0x10C, "roll_counter"),
628
- ],
629
  );
630
  }
631
 
632
  // --- CTaskCrate-specific fields ---
633
  if name == "CTaskCrate" {
634
  show_game_task_raw_fields(
635
- ui,
636
- addr,
637
- "CTaskCrate",
638
- 0x4B0,
639
- &[
640
- (0x0FC, "unknown_fc"),
641
- (0x100, "slot_id"),
642
- (0x104, "unknown_104"),
643
- (0x108, "unknown_108"),
644
- (0x10C, "timer"),
645
- // scheme_data starts at 0x110 (0xE5 DWORDs)
646
- (0x124, "crate_type"),
647
- (0x24C, "heal_amount"),
648
- (0x264, "extra_init_flag"),
649
- (0x4A4, "unknown_4a4"),
650
- (0x4A8, "sequence_ref"),
651
- (0x4AC, "unknown_4ac"),
652
- ],
653
  );
654
  }
655
 
@@ -869,44 +789,7 @@ impl DebugApp {
869
  ui.separator();
870
 
871
  show_game_task_raw_fields(
872
- ui,
873
- addr,
874
- "CTaskWorm",
875
- 0x3FC,
876
- &[
877
- (0x0FC, "team_index"),
878
- (0x100, "worm_index"),
879
- (0x104, "unknown_104"),
880
- (0x110, "spawn_params[0]"),
881
- (0x114, "spawn_params[1]"),
882
- (0x118, "spawn_params[2]"),
883
- (0x11C, "spawn_params[3]"),
884
- (0x120, "spawn_params[4]"),
885
- (0x124, "spawn_params[5]"),
886
- (0x128, "spawn_params[6]"),
887
- (0x12C, "spawn_params[7]"),
888
- (0x130, "spawn_params[8]"),
889
- (0x134, "spawn_params[9]"),
890
- (0x158, "slot_id"),
891
- (0x164, "stationary_frames"),
892
- (0x170, "selected_weapon"),
893
- (0x1A8, "facing_dir_2"),
894
- (0x1AC, "facing_dir_inv"),
895
- (0x1EC, "movement_streak"),
896
- (0x24C, "aim_angle"),
897
- (0x268, "show_cursor"),
898
- (0x2F0, "worm_name[0..4]"),
899
- (0x2F4, "worm_name[4..8]"),
900
- (0x2F8, "worm_name[8..12]"),
901
- (0x2FC, "worm_name[12..16]"),
902
- (0x334, "facing_dir_3"),
903
- (0x368, "animator"),
904
- (0x3DC, "facing_direction"),
905
- (0x3E4, "input_aim_up"),
906
- (0x3E8, "input_aim_down"),
907
- (0x3EC, "input_move_left"),
908
- (0x3F0, "input_move_right"),
909
- ],
910
  );
911
  }
912
 
@@ -924,28 +807,7 @@ impl DebugApp {
924
  ui.separator();
925
 
926
  show_game_task_raw_fields(
927
- ui,
928
- addr,
929
- "CTaskMissile",
930
- 0x41C,
931
- &[
932
- (0x128, "launch_seed"),
933
- (0x12C, "slot_id"),
934
- (0x130, "spawn: owner_id"),
935
- (0x134, "spawn: [1]"),
936
- (0x138, "spawn: spawn_x"),
937
- (0x13C, "spawn: spawn_y"),
938
- (0x140, "spawn: speed_x"),
939
- (0x144, "spawn: speed_y"),
940
- (0x148, "spawn: cursor_x"),
941
- (0x14C, "spawn: cursor_y"),
942
- (0x150, "spawn: pellet_idx"),
943
- (0x154, "spawn: fb_timer"),
944
- (0x158, "spawn: fb_param"),
945
- (0x3A0, "launch_speed"),
946
- (0x3A8, "homing_enabled"),
947
- (0x3C8, "direction"),
948
- ],
949
  );
950
  }
951
  }
 
2
  use openwa_core::address::va;
3
  use openwa_core::engine::{DDGame, DDGameWrapper};
4
  use openwa_core::rebase::rb;
5
+ use openwa_core::registry;
6
  use openwa_core::task::{
7
  CTask, CTaskCloud, CTaskFire, CTaskTeam, CTaskTurnGame, CTaskWorm, TurnGameCtx,
8
  };
 
337
  // ---------------------------------------------------------------------------
338
 
339
  /// CGameTask field labels for the base class region (0x00..0xFC).
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
340
  /// Display all DWORDs of a CGameTask-derived entity with labelled fields.
341
+ ///
342
+ /// Field names are resolved from the global registry via inheritance-aware
343
+ /// lookup (entity → CGameTask → CTask). No hardcoded label tables needed.
344
  unsafe fn show_game_task_raw_fields(
345
  ui: &mut egui::Ui,
346
  addr: u32,
347
  type_name: &str,
348
  total_size: usize,
 
349
  ) {
350
  let base = addr as *const u32;
351
 
 
 
 
 
 
 
 
 
 
 
 
 
 
352
  // Sections: CTask base, CGameTask unknowns, pos/speed, more unknowns, emitter,
353
  // then type-specific in 0x80-byte chunks to keep each section manageable.
354
  let mut sections: Vec<(usize, usize, String)> = vec![
 
395
  for i in 0..dwords {
396
  let off = start + i * 4;
397
  let val = *base.add(off / 4);
398
+ let field_name = registry::field_at_inherited(type_name, off as u32)
399
+ .map(|f| f.name)
400
+ .unwrap_or("");
401
 
402
  ui.label(format!("+0x{:03X}", off));
403
  // Show as hex + signed decimal
 
555
  // --- CTaskMine-specific fields ---
556
  if name == "CTaskMine" {
557
  show_game_task_raw_fields(
558
+ ui, addr, "CTaskMine", 0x128,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
559
  );
560
  }
561
 
562
  // --- CTaskOilDrum-specific fields ---
563
  if name == "CTaskOilDrum" {
564
  show_game_task_raw_fields(
565
+ ui, addr, "CTaskOilDrum", 0x110,
 
 
 
 
 
 
 
 
 
 
566
  );
567
  }
568
 
569
  // --- CTaskCrate-specific fields ---
570
  if name == "CTaskCrate" {
571
  show_game_task_raw_fields(
572
+ ui, addr, "CTaskCrate", 0x4B0,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
573
  );
574
  }
575
 
 
789
  ui.separator();
790
 
791
  show_game_task_raw_fields(
792
+ ui, addr, "CTaskWorm", 0x3FC,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
793
  );
794
  }
795
 
 
807
  ui.separator();
808
 
809
  show_game_task_raw_fields(
810
+ ui, addr, "CTaskMissile", 0x41C,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
811
  );
812
  }
813
  }