paavohtl Claude Opus 4.6 commited on
Commit
23a00c8
·
1 Parent(s): 20a5838

docs: document replay system architecture and spacebar advance mechanism

Browse files

Fully traced the spacebar-to-advance-replay chain through Ghidra:
keyboard -> TurnGame+0x1F4 -> HurryHandler -> DDGame+0x7E41 -> GameFrameEndProcessor -> local Hurry message (0x17).

Adds 11 replay/turn function addresses and 6 DDGame replay offsets to address.rs.
Creates docs/re-notes/replay-system.md with full findings including
the Option A direct control strategy (set DDGame+0x7E41 from DLL).

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

crates/openwa-types/src/address.rs CHANGED
@@ -114,6 +114,36 @@ pub mod va {
114
  pub const CTASK_TEAM_CTOR: u32 = 0x0055_5BB0;
115
  pub const CTASK_TURNGAME_CTOR: u32 = 0x0055_B280;
116
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
  // === Gameplay functions ===
118
 
119
  pub const CREATE_EXPLOSION: u32 = 0x0054_8080;
@@ -544,5 +574,23 @@ pub mod va {
544
  pub const RENDER_QUEUE: u32 = 0x524;
545
  /// Offset to weapon panel pointer
546
  pub const WEAPON_PANEL: u32 = 0x548;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
547
  }
548
  }
 
114
  pub const CTASK_TEAM_CTOR: u32 = 0x0055_5BB0;
115
  pub const CTASK_TURNGAME_CTOR: u32 = 0x0055_B280;
116
 
117
+ // === Replay / turn management ===
118
+
119
+ /// Loads .WAgame replay file, validates magic 0x4157, stores payload at DDGame+0xDB1C.
120
+ /// stdcall(this, mode) where mode: 1=play, 2=getmap, 3=getscheme, 4=repair. ~12KB function.
121
+ pub const REPLAY_LOADER: u32 = 0x0046_2DF0;
122
+ /// Parses "MM:SS.FF" time string → frame number. Returns -1 on failure.
123
+ pub const PARSE_REPLAY_POSITION: u32 = 0x004E_3490;
124
+ /// Routes game messages through the task handler tree.
125
+ pub const GAME_MESSAGE_ROUTER: u32 = 0x0055_3BD0;
126
+ /// TurnGame message dispatcher. Case 2=FrameFinish, Case 4=ProcessInput, Case 0x28=SkipGo.
127
+ /// thiscall + 4 stack params.
128
+ pub const TURNGAME_HANDLE_MESSAGE: u32 = 0x0055_DC00;
129
+ /// Checks TurnGame+0x1F4 (hurry requested). Normal game: sends packet 0x17.
130
+ /// Replay mode (DB08 && DB0A): sets DDGame+0x7E41 instead. Uses ESI (__usercall).
131
+ pub const TURNGAME_HURRY_HANDLER: u32 = 0x0055_E5F0;
132
+ /// Per-frame turn timer: decrements turn timer by 0x14 (20ms) each frame.
133
+ pub const TURN_MANAGER_PROCESS_FRAME: u32 = 0x0055_FDA0;
134
+ /// Iterates teams during ProcessInput, sends packet 0x2B for valid teams.
135
+ pub const TURNGAME_AUTO_SELECT_TEAMS: u32 = 0x0056_11E0;
136
+ /// Control task HandleMessage (vtable 0x669C28). Translates keyboard input (msg 0xC)
137
+ /// into game messages. Case 9 toggles pause flag.
138
+ pub const CONTROL_TASK_HANDLE_MESSAGE: u32 = 0x0054_51F0;
139
+ /// End-of-frame processing. Reads DDGame+0x7E41 (deferred hurry flag),
140
+ /// converts it to local Hurry message (0x17). Also handles frame counters.
141
+ pub const GAME_FRAME_END_PROCESSOR: u32 = 0x0053_1960;
142
+ /// Main frame loop. Processes message queue, calls GameFrameEndProcessor.
143
+ pub const GAME_FRAME_DISPATCHER: u32 = 0x0053_1D00;
144
+ /// Sends game packet if network buffer capacity allows. Checks DDGame+0x98A4.
145
+ pub const SEND_GAME_PACKET_CONDITIONAL: u32 = 0x0053_1880;
146
+
147
  // === Gameplay functions ===
148
 
149
  pub const CREATE_EXPLOSION: u32 = 0x0054_8080;
 
574
  pub const RENDER_QUEUE: u32 = 0x524;
575
  /// Offset to weapon panel pointer
576
  pub const WEAPON_PANEL: u32 = 0x548;
577
+
578
+ // --- Replay state ---
579
+
580
+ /// Deferred hurry flag. Set to 1 during replay instead of sending network
581
+ /// packet. GameFrameEndProcessor (0x531960) reads this and converts it to
582
+ /// a local Hurry message (TaskMessage 0x17 = 23).
583
+ pub const DEFERRED_HURRY_FLAG: u32 = 0x7E41;
584
+ /// Replay state flag A — checked by TurnGame_HurryHandler and FrameFinish.
585
+ /// Both DB08 and DB0A must be non-zero for replay-mode hurry path.
586
+ pub const REPLAY_STATE_FLAG_A: u32 = 0xDB08;
587
+ /// Replay state flag B — checked together with flag A for replay mode.
588
+ pub const REPLAY_STATE_FLAG_B: u32 = 0xDB0A;
589
+ /// Replay active flag — set to 1 by ReplayLoader when loading a .WAgame file.
590
+ pub const REPLAY_ACTIVE: u32 = 0xDB48;
591
+ /// Input replay file path (string buffer, 0x400 bytes).
592
+ pub const REPLAY_INPUT_PATH: u32 = 0xDB60;
593
+ /// Output replay file path (for recording, 0x400 bytes).
594
+ pub const REPLAY_OUTPUT_PATH: u32 = 0xDF60;
595
  }
596
  }
docs/re-notes/replay-system.md ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Replay System
2
+
3
+ WA's replay system records and plays back game inputs from `.WAgame` files.
4
+
5
+ ## Command-Line Entry Points
6
+
7
+ In `Frontend__MainNavigationLoop` (0x4E6440):
8
+ - `/play <file>` -- play replay from start
9
+ - `/playat <file> [MM:SS.FF]` -- play from specific time offset
10
+
11
+ Globals set by arg parsing:
12
+ - `0x0088AF58` -- replay filename buffer
13
+ - `0x0088C77C` -- playback position (frame number)
14
+ - `0x0088C778` -- position valid flag (0 or 1)
15
+
16
+ ## DDGame Replay Offsets
17
+
18
+ | Offset | Type | Description |
19
+ |----------|-------|-------------|
20
+ | +0x7E41 | byte | Deferred hurry flag. Set to 1 during replay when spacebar triggers a hurry. Consumed by GameFrameEndProcessor which converts it to a local Hurry message (0x17). |
21
+ | +0xDB08 | byte | Replay state flag A. Both A and B must be non-zero for the replay hurry path. |
22
+ | +0xDB0A | byte | Replay state flag B. |
23
+ | +0xDB0B | byte | Replay state flag C (checked by SendGamePacketConditional). |
24
+ | +0xDB1C | ptr | Replay payload pointer (malloc'd from file data). |
25
+ | +0xDB48 | dword | Replay active flag (set to 1 by ReplayLoader). |
26
+ | +0xDB50 | dword | Replay frame pointer/state. |
27
+ | +0xDB54 | dword | Replay frame pointer/state. |
28
+ | +0xDB58 | dword | Replay frame pointer/state. |
29
+ | +0xDB60 | char[0x400] | Input replay file path. |
30
+ | +0xDF60 | char[0x400] | Output replay file path (for recording). |
31
+ | +0xEF60 | dword | Init counter (set to 0 by loader). |
32
+
33
+ ## Spacebar Advance Mechanism (Fully Traced)
34
+
35
+ When spacebar is pressed during replay playback, this chain fires:
36
+
37
+ ```
38
+ Keyboard input
39
+ -> Control task (0x5451F0) translates keyboard action
40
+ -> Sets TurnGame+0x1F4 = 1 ("hurry requested")
41
+ -> TurnGame ProcessInput (msg 4)
42
+ -> TurnGame_HurryHandler (0x55E5F0)
43
+ checks TurnGame+0x1F4
44
+ Normal game: SendGamePacketWrapped(0x17, 0)
45
+ Replay mode: DDGame+0x7E41 = 1 (deferred hurry)
46
+ -> GameFrameEndProcessor (0x531960) end-of-frame
47
+ reads DDGame+0x7E41
48
+ if set: sends msg 0x17 (Hurry) locally + via SendGamePacketConditional
49
+ clears flag
50
+ -> Turn ends, next turn begins from replay data
51
+ ```
52
+
53
+ Packet 0x17 = TaskMessage_Hurry (value 23 from wkJellyWorm enum). In replay mode
54
+ this is processed locally, not over the network. The deferred flag (0x7E41) bridges
55
+ the gap between "input detected during ProcessInput" and "end-of-frame processing."
56
+
57
+ ## Key Functions
58
+
59
+ | Address | Name | Description |
60
+ |----------|------|-------------|
61
+ | 0x462DF0 | ReplayLoader | Loads .WAgame file. Validates magic 0x4157 ("WA"), stores payload at DDGame+0xDB1C. param_2: 1=play, 2=getmap, 3=getscheme, 4=repair. |
62
+ | 0x4E3490 | ParseReplayPosition | Parses "MM:SS.FF" -> frame number. Returns -1 on failure. |
63
+ | 0x531880 | SendGamePacketConditional | Sends packet if network buffer allows. |
64
+ | 0x531960 | GameFrameEndProcessor | End-of-frame. Reads deferred hurry flag, sends Hurry message. |
65
+ | 0x531D00 | GameFrameDispatcher | Main frame loop. Message queue + GameFrameEndProcessor. |
66
+ | 0x5451F0 | ControlTask_HandleMessage | Translates keyboard input (msg 0xC) -> game messages. |
67
+ | 0x553BD0 | GameMessageRouter | Routes messages through task handler tree. |
68
+ | 0x55DC00 | TurnGame_HandleMessage | Message dispatcher. Case 2=FrameFinish, 4=ProcessInput. |
69
+ | 0x55E5F0 | TurnGame_HurryHandler | Hurry logic. Normal: packet 0x17. Replay: deferred flag. __usercall(ESI). |
70
+ | 0x55FDA0 | TurnManager_ProcessFrame | Per-frame turn timer, decrements by 0x14 (20ms). |
71
+ | 0x5611E0 | TurnGame_AutoSelectTeams | Iterates teams during ProcessInput, sends packet 0x2B. |
72
+
73
+ ## Replay File Format
74
+
75
+ - Magic: 4 bytes, value 0x4157 ("WA" in little-endian)
76
+ - Version in upper 16 bits of first dword
77
+ - Size: 4 bytes (payload length)
78
+ - Payload: binary game state + recorded inputs
79
+
80
+ ## Direct Replay Control (Future)
81
+
82
+ To bypass keyboard simulation entirely, set DDGame+0x7E41 = 1 from our DLL thread.
83
+ The game's existing GameFrameEndProcessor converts it to a local Hurry message every
84
+ frame. This is the simplest approach and faithfully replicates what spacebar does.
85
+
86
+ Prerequisites: replay flags DB08 and DB0A must both be non-zero (true during normal
87
+ replay playback).