events: recover the player event-posting API (lane E)
Closes B3's oracle gap and B2's known gap. Both milestones write into the
owner's event list; nothing modelled it, so their clean compares bounded the
economy fields only.
Container: EventStorage embedded at ServerPlayer+0x29c (0x1c bytes), verified by
ServerPlayer::GetEventStorage 0x0080db00 whose whole body is 'lea eax,[ecx+0x29c];
ret'. EvNxID at +0x14 == player+0x2b0, exactly the byte run the harness guard
reports. The list is a vector<TurnEvents{int EvTurn; vector<PlayerEvent>}> --
bucketed by turn, not flat, which the save-editor struct note had wrong.
Entry point: int __thiscall EventStorage::PostEvent(this, std::string BY VALUE,
std::string BY VALUE, void* obj, Vector3* pos, int turn, const char* img,
int act), 0x008862b0, RET 0x4c. 161 call sites in 110 functions: this is the
whole simulation's event API, not a research helper. Dedup is per turn bucket on
message/image/location/position/action but NOT summary; act 0 with no subject and
no position is stored as 2; EvNxID starts at 0 and is promoted to 1 on first post;
PruneOldTurns drops buckets older than turn-50 with an off-by-one that always
leaves one stale bucket behind.
Record: 0x74 bytes, EvEID(+4) EvDsc(+8) EvMsg(+0x24) EvLoc(+0x40) EvPos(+0x44)
EvImg(+0x50) EvAct(+0x6c) EvCID(+0x70) -- confirmed field by field against
turn3-state.sav, which contains the EVENT_RESEARCH_OVERBUDGET record itself.
B3's defect is fully explained: 0x00587b97, reached only from the
completion-roll-FAILED branch, under !wasDone && nowDone && owner.
Three note corrections: EvPos defaults to FLT_MAX, not infinity (writing +inf
changes the save bytes); the save array is turn-bucketed, not flat;
EVENT_TECHS_UNLOCKED has no 'parent researched' clause.
56 entries added to ghidra/addresses.json (header regenerated); 11 prototypes,
13 labels, 12 comments and 2 structs written back to the Ghidra project.
This commit is contained in:
parent
84d5609af8
commit
0dff4941bd
7 changed files with 999 additions and 8 deletions
|
|
@ -69,4 +69,4 @@ Status flow: `backlog → in-progress → mapped → verified` (or `blocked`).
|
|||
| golden-trace recapture (post-guards) | verify | in-progress | — | 0% | 2026-09-08 | region sets changed, so goldens must be recaptured: ProcessResearch (expect NEW divergences - the event defect becoming visible), ComputeBudget (verdict should stay 0), and first captures for OnTechResearched / ServerSystem::ProcessTurn / MoveFleet |
|
||||
| B1 replace double-run | verify | backlog | — | 0% | 2026-09-08 | ComputeBudget replace mode runs the original a second time to harvest budget slots; ComputeOutput repairs ships in orbit as a side effect, so this is a real per-turn double effect on objects no region covers. Needs a design fix (harvest without re-running, or declare+revert) |
|
||||
| ReVa MCP link drop (workaround) | meta | verified | high | 100% | 2026-09-08 | The ReVa MCP client link dropped mid-session while the CT111 server stayed healthy (systemd active, :8080 listening, valid key -> 200). `tools/reva_call.py <tool> '<json>'` calls the same server over plain HTTP (initialize -> notifications/initialized -> tools/call; replies are SSE with a leading `id:` line, initialize is plain JSON). Key is NEVER stored in the repo: $REVA_KEY, else ~/.claude.json, else ssh to the CT properties file. Use this whenever mcp__plugin_ReVa_ReVa__* is unavailable |
|
||||
| event posting API | subsystem | in-progress | — | 0% | 2026-09-08 | Closes B3's oracle gap (unposted EVENT_RESEARCH_OVERBUDGET, the ONE item wrong across 40,300) and B2's known gap (ours posts no events, so both clean compares bound economy fields only). Needs: the player event list container, the post/append entry point, the event record layout |
|
||||
| event posting API | subsystem | mapped | high | 90% | 2026-09-08 | RECOVERED (lane E, `findings/subsystems/events.md`). Container: `EventStorage` embedded at `ServerPlayer+0x29c` (0x1c), `EvNxID` at +0x14 = player+0x2b0 — exactly the guard's byte run. Nested `vector<TurnEvents{int EvTurn; vector<PlayerEvent>}>`, record 0x74 B, tags `EvEID EvDsc EvMsg EvImg EvLoc EvPos EvAct EvCID`; layout confirmed field-by-field against turn3-state.sav, which CONTAINS the overbudget record. Entry point `int __thiscall EventStorage::PostEvent(this, string BYVAL, string BYVAL, obj*, Vector3*, turn, const char* img, int act)` 0x008862b0 RET 0x4c — **161 call sites in 110 functions, the whole sim's event API**. B3 defect fully explained: 0x00587b97, in the completion-roll-FAILED branch under `!wasDone && nowDone && owner`. 3 note corrections (EvPos is FLT_MAX not inf; the save array is turn-bucketed not flat; TECHS_UNLOCKED has no parent clause). 56 entries in addresses.json; 11 prototypes + 13 labels + 12 comments + 2 structs written back to Ghidra. Engine: `sots-engine` branch `wip/events` a7348be, `src/game/events` + 112 checks, ctest 32/32. NOT YET WIRED INTO A HOOK — see `docs/E-events.md` for the proposed region/Coverage change |
|
||||
|
|
|
|||
|
|
@ -268,7 +268,7 @@ Int32 cstR, cstE, cstT, maint, shrm, status, elim
|
|||
Boolean npc, rebAi, reqCL
|
||||
SimPlayerTeamSaveStruct teamStruct (Int32 alid, al, na, cf)
|
||||
Int32 hasVac, hasImm, npTrak, hasDisc, hasDiscSp, hasDiscCl, hasEnc, hasEng
|
||||
SimPlayerEventsSaveStruct events (Int32 evNxId; ComplexArray<SimPlayerEvent>)
|
||||
SimPlayerEventsSaveStruct events (Int32 evNxId; ComplexArray<SimPlayerEvent>) <-- SEE NOTE E1
|
||||
NestedInt32 fngNum
|
||||
Int32 pvSav, pvMA, aibN
|
||||
Boolean cnTrd, cnRad, hgs, hadvs, harcc, cnVItl
|
||||
|
|
@ -315,6 +315,25 @@ Int32 ndeflay, rdtc, tnc
|
|||
`...Spi` (complex): `Int32 sPid, sts; Float cst; Int32 mxC; String name; Int32 trns`.
|
||||
tail `AsMon`: `Int32 rDn, sys, rMn, rMx; Float rMd`; tail `Tech`: `Int32 rDn; Float aOdd, aInc; String tch; Int32 rCst`.
|
||||
|
||||
**NOTE E1 — `events` is nested, not flat (corrected 2026-09-08, lane E).** The R1 model
|
||||
`SimPlayerEventsSaveStruct { Int32 evNxId; ComplexArray<SimPlayerEvent> }` reads the array as
|
||||
a flat list of events. Its elements are **turn groups**. The real shape, read off the three
|
||||
serializers (`EventStorage` 0x00825cc0, `TurnEvents` 0x00825bb0/0x00825c40, `PlayerEvent`
|
||||
0x00825970) and confirmed against `verify/results/saves/turn3-state.sav`:
|
||||
|
||||
```
|
||||
Events (EventStorage, in memory ServerPlayer+0x29c, 0x1c bytes)
|
||||
EvNxID : int -- starts at 0, not 1
|
||||
Events : n x TurnEvents (0x18 bytes each)
|
||||
EvTurn : int
|
||||
Events : m x PlayerEvent (0x74 bytes each)
|
||||
EvEID(+0x04) EvDsc(+0x08) EvMsg(+0x24) EvImg(+0x50)
|
||||
EvLoc(+0x40) EvPos{x,y,z}(+0x44) EvAct(+0x6c) EvCID(+0x70)
|
||||
```
|
||||
|
||||
`save_reader.py` already parses this correctly (it falls into the generic tree), so no reader
|
||||
change is needed. Full API in `findings/subsystems/events.md`.
|
||||
|
||||
---
|
||||
|
||||
## 7. Tech tree & ship designs
|
||||
|
|
|
|||
401
findings/subsystems/events.md
Normal file
401
findings/subsystems/events.md
Normal file
|
|
@ -0,0 +1,401 @@
|
|||
# Player event posting API (lane E, 2026-09-08)
|
||||
|
||||
Target: recover the API the game uses to post player-visible turn events, so the
|
||||
reimplementation can post them and so the compare harness can *see* them.
|
||||
|
||||
Motivation: `TechTree::ProcessResearch` (P2-B3) failed its replace-mode oracle by exactly
|
||||
one item across 40,300 — an unposted `EVENT_RESEARCH_OVERBUDGET`. `ServerPlayer::
|
||||
OnTechResearched` (P2-B2) has the same gap. Until events are modelled, both "0 divergence"
|
||||
verdicts bound the economy fields only.
|
||||
|
||||
**Everything below with an address was read out of the instruction stream** (bytes fetched
|
||||
with `read-memory` over the ReVa HTTP shim, disassembled locally with capstone) unless the
|
||||
line says *inferred*. Layout claims are additionally confirmed against three real saves via
|
||||
`verify/save-reader/save_reader.py`.
|
||||
|
||||
---
|
||||
|
||||
## 1. The container
|
||||
|
||||
`ServerPlayer::Events` is an **`EventStorage` embedded at `ServerPlayer+0x29c`**, size `0x1c`.
|
||||
|
||||
Verified by `ServerPlayer::GetEventStorage` at **0x0080db00**, which is the whole function:
|
||||
|
||||
```
|
||||
0080db00 8d819c020000 lea eax, [ecx + 0x29c]
|
||||
0080db06 c3 ret
|
||||
```
|
||||
|
||||
`__thiscall EventStorage* ServerPlayer::GetEventStorage(ServerPlayer* this)` — no stack args,
|
||||
plain `ret`. Every research-path post site either calls it (`ProcessResearch`) or inlines the
|
||||
`lea ecx,[esi+0x29c]` (`OnTechResearched`, 0x008919ab / 0x0089241d).
|
||||
|
||||
### `EventStorage` (0x1c bytes) — already in the Ghidra project, now confirmed by code
|
||||
|
||||
| off | type | name | evidence |
|
||||
|---|---|---|---|
|
||||
| 0x00 | `void*` | vptr | not touched by the post path |
|
||||
| 0x04 | `TurnEvents*` | `Events._Myfirst` | 0x008853aa `mov edx,[esi+8]; sub edx,[esi+4]` |
|
||||
| 0x08 | `TurnEvents*` | `Events._Mylast` | same |
|
||||
| 0x0c | `TurnEvents*` | `Events._Myend` | vector growth in 0x00885427+ |
|
||||
| 0x10 | `allocator` | `Events._Alval` | MSVC `_Vector_val` tail, 4 B |
|
||||
| 0x14 | `int` | **`EvNxID`** | 0x008863e3–0x008863f6 |
|
||||
| 0x18 | — | padding | struct size 0x1c |
|
||||
|
||||
`EvNxID` lands at **`ServerPlayer+0x2b0`** — exactly the byte run the harness guard reported
|
||||
as `player+0x2b0:4`. That is now explained, not merely observed.
|
||||
|
||||
The vector element stride is **0x18**: `0x008853b3 mov eax,0x2aaaaaab; imul edx; sar edx,2`
|
||||
= signed divide by 24.
|
||||
|
||||
### `TurnEvents` (0x18 bytes) — new
|
||||
|
||||
| off | type | name | evidence |
|
||||
|---|---|---|---|
|
||||
| 0x00 | `void*` | vptr | virtual dtor called at 0x00879f21 (`mov edx,[edi]; mov eax,[edx]; push 0; call eax`) |
|
||||
| 0x04 | `int` | **`EvTurn`** | 0x008853d3 `cmp [edx+4], ebx` (ebx = the turn argument) |
|
||||
| 0x08 | `PlayerEvent*` | `Events._Myfirst` | `lea ecx,[esi+8]` handed to `vector::push_back` at 0x008863db |
|
||||
| 0x0c | `PlayerEvent*` | `Events._Mylast` | 0x008863f9 `mov eax,[esi+0xc]` |
|
||||
| 0x10 | `PlayerEvent*` | `Events._Myend` | |
|
||||
| 0x14 | `allocator` | `Events._Alval` | |
|
||||
|
||||
### `PlayerEvent` (0x74 = 116 bytes) — new
|
||||
|
||||
Stride confirmed two independent ways: the duplicate scanner's divisor at 0x00825d5f
|
||||
(`mov eax,0x8d3dcb09; imul ecx; add edx,ecx; sar edx,6` = signed divide by 116) and the
|
||||
post function's `mov [eax-0x70], ecx` writing `id` at `element+4` off `_Mylast`.
|
||||
|
||||
| off | type | on-disk tag | set by |
|
||||
|---|---|---|---|
|
||||
| 0x00 | `void*` | — | ctor: vftable `0x00a21958` |
|
||||
| 0x04 | `int` | `EvEID` | `PostEvent`: `EvNxID++` (or the duplicate's id) |
|
||||
| 0x08 | `std::string` (0x1c) | `EvDsc` | `PostEvent` arg 0 (summary / title) |
|
||||
| 0x24 | `std::string` (0x1c) | `EvMsg` | `PostEvent` arg 1 (body) |
|
||||
| 0x40 | `int` | `EvLoc` | `obj ? obj->[+4] : 0` |
|
||||
| 0x44 | `float[3]` | `EvPos` | `obj ? obj->[+0x18..0x20] : (pos ? *pos : ctor default)` |
|
||||
| 0x50 | `std::string` (0x1c) | `EvImg` | `PostEvent` arg `img` (`""` if NULL) |
|
||||
| 0x6c | `int` | `EvAct` | `PostEvent` arg `act`; forced to **2** if `act==0 && !obj && !pos` |
|
||||
| 0x70 | `int` | `EvCID` | ctor 0; never written by `PostEvent` |
|
||||
|
||||
**Correction to `formula-gaps.md`:** the default `EvPos` is **`FLT_MAX` (0x7f7fffff), not
|
||||
infinity**. `PlayerEvent::PlayerEvent` (0x0084ee30) copies the three floats from the global
|
||||
`Vector3` at `0x00af0dc8`, whose bytes are `FF FF 7F 7F` × 3. Confirmed in the save:
|
||||
`EvPos = 2139095039 (0x7f7fffff)` for `EVENT_RESEARCH_OVERBUDGET`. Writing `+inf`
|
||||
(0x7f800000) would produce a different save byte and a different oracle hash.
|
||||
|
||||
Ctor also sets `EvEID=0`, `EvLoc=0`, `EvAct=0`, `EvCID=0` and the three strings to `""`
|
||||
(`0x009e100c`).
|
||||
|
||||
### Serialization (matches the save exactly)
|
||||
|
||||
`PlayerEvent::Serialize` = vftable slot 1 at **0x00825970**, `__thiscall`, `ret 4`. Tag
|
||||
pointers all come from the table at `0x00a2bd88` (stride 8): `EvEID EvNxID EvTurn Events
|
||||
EvPos EvLoc EvMsg EvImg EvDsc EvCID EvAct sasc`. Emission order read off the instruction
|
||||
stream: `EvEID, EvDsc, EvMsg, EvImg, EvLoc, EvPos, EvAct, EvCID`.
|
||||
|
||||
`TurnEvents` write 0x00825bb0 / read 0x00825c40: `EvTurn` then the nested collection `Events`.
|
||||
`EventStorage` read 0x00825cc0: `EvNxID` then the nested collection `Events`.
|
||||
|
||||
So on disk the shape is **nested, not flat**:
|
||||
|
||||
```
|
||||
Events (EventStorage)
|
||||
EvNxID : int
|
||||
Events : n × TurnEvents
|
||||
EvTurn : int
|
||||
Events : m × PlayerEvent
|
||||
EvEID EvDsc EvMsg EvImg EvLoc EvPos{x,y,z} EvAct EvCID
|
||||
```
|
||||
|
||||
`findings/objects/save-editor-structs.md:271` models this as
|
||||
`SimPlayerEventsSaveStruct events (Int32 evNxId; ComplexArray<SimPlayerEvent>)` — a **flat**
|
||||
array of events. That is wrong (or at least the R1 C# editor's simplification): the
|
||||
`ComplexArray` elements are *turn groups*, each holding its own array. `save_reader.py`
|
||||
parses it correctly today because `Events` falls into the generic tree; nothing needs fixing
|
||||
in the reader, but the struct note should be corrected.
|
||||
|
||||
### Ground truth: `verify/results/saves/turn3-state.sav`
|
||||
|
||||
Player index 1 (`/Sim/Player[1]/Events`, file offset 120372):
|
||||
|
||||
```
|
||||
EvNxID = 4
|
||||
EvTurn = 2
|
||||
EvEID 1 EvDsc "Ships Constructed At Ke'Dolarra"
|
||||
EvMsg "1 ship built in system Ke'Dolarra"
|
||||
EvImg "EVENT_SHIPS_BUILT" EvLoc 288 EvPos {-11.9286, 4.71900, 2.31785}
|
||||
EvAct 0 EvCID 0
|
||||
EvTurn = 3
|
||||
EvEID 2 (the same EVENT_SHIPS_BUILT record, next turn)
|
||||
EvEID 3 EvDsc "Research Over Budget"
|
||||
EvMsg "Research for Waldo Units has gone overbudget."
|
||||
EvImg "EVENT_RESEARCH_OVERBUDGET" EvLoc 0
|
||||
EvPos {0x7f7fffff, 0x7f7fffff, 0x7f7fffff} EvAct 1 EvCID 0
|
||||
```
|
||||
|
||||
Player index 0 has two `EVENT_NO_RESEARCH` records (turns 2 and 3, ids 1 and 2, `EvNxID` 3).
|
||||
Players 2 and 3 have `EvNxID = 0` and an empty list — note **`EvNxID` starts at 0**, and
|
||||
`PostEvent` lazily promotes 0 → 1 on the first post (0x008863e3).
|
||||
|
||||
---
|
||||
|
||||
## 2. The entry point
|
||||
|
||||
```c
|
||||
// 0x008862b0
|
||||
int __thiscall EventStorage::PostEvent(
|
||||
EventStorage* this, // ecx
|
||||
std::string summary, // [ebp+0x08], BY VALUE, 0x1c bytes -> EvDsc
|
||||
std::string message, // [ebp+0x24], BY VALUE, 0x1c bytes -> EvMsg
|
||||
void* obj, // [ebp+0x40] may be NULL
|
||||
Vector3* pos, // [ebp+0x44] may be NULL
|
||||
int turn, // [ebp+0x48]
|
||||
const char* img, // [ebp+0x4c] may be NULL -> ""
|
||||
int act); // [ebp+0x50]
|
||||
// returns the event id; ret 0x4c
|
||||
```
|
||||
|
||||
`ret 0x4c` = 76 = 2 × 0x1c (the two by-value `std::string`s) + 5 × 4. Both string arguments
|
||||
are built **in the caller's frame by `sub esp,0x1c`** and a copy-construct, which is the MSVC
|
||||
by-value-`std::string` idiom; `PostEvent` frees their buffers itself before returning
|
||||
(0x00886415–0x00886446), so the caller must not.
|
||||
|
||||
Body, in order (all read from the instruction stream):
|
||||
|
||||
1. `PlayerEvent ev;` — default ctor `0x0084ee30` on a `[ebp-0x84]` temporary.
|
||||
2. `ev.EvDsc = summary; ev.EvMsg = message;` (0x0088630d, 0x0088631a).
|
||||
3. `ev.EvLoc = obj ? obj->[+4] : 0` (0x00886322).
|
||||
4. `ev.EvImg = img ? img : ""` — `""` is `0x009e100c`; length by inline `strlen` (0x00886330).
|
||||
5. `ev.EvAct = act`; **if `act == 0 && obj == NULL && pos == NULL` then `ev.EvAct = 2`**
|
||||
(0x00886353–0x0088636f). This default is easy to miss and changes the save bytes.
|
||||
6. Position: `obj` wins (`obj->[+0x18/+0x1c/+0x20]`), else `pos` (`pos->[0/4/8]`), else the
|
||||
ctor's `FLT_MAX` triple (0x0088637a–0x008863a7).
|
||||
7. `PruneOldTurns(turn)` — 0x00879eb0.
|
||||
8. `TurnEvents* bucket = GetOrCreateTurnBucket(turn)` — 0x00885380.
|
||||
9. `PlayerEvent* dup = FindDuplicate(bucket, &ev)` — 0x00825d40. **If non-NULL, return
|
||||
`dup->EvEID` and post nothing.**
|
||||
10. else `bucket->Events.push_back(ev)`; `if (EvNxID == 0) EvNxID = 1;`
|
||||
`id = EvNxID++; back().EvEID = id;` return `id`.
|
||||
|
||||
### `EventStorage::FindDuplicate` — 0x00825d40, `__thiscall (TurnEvents*, PlayerEvent*)`, `ret 8`
|
||||
|
||||
Linear scan of the bucket. Two events are the same when **all** of these match:
|
||||
`EvAct` (+0x6c), `EvLoc` (+0x40), the three `EvPos` floats (`fucompp`, so bitwise-unequal
|
||||
NaNs never match but the `FLT_MAX` sentinels always do), `EvMsg` (+0x24) and `EvImg` (+0x50).
|
||||
**`EvDsc` is NOT compared.** A NULL bucket returns 0 immediately.
|
||||
|
||||
This is why the two identical `EVENT_SHIPS_BUILT` records in `turn3-state.sav` survive as
|
||||
separate events: they are in different turn buckets, and dedup is per-bucket.
|
||||
|
||||
### `EventStorage::GetOrCreateTurnBucket` — 0x00885380, `__thiscall (int turn)`, `ret 4`
|
||||
|
||||
Scans `Events` for `EvTurn == turn`; **keeps scanning to the end and returns the *last*
|
||||
match** (0x008853d0–0x008853de has no early exit). If none, constructs a bucket
|
||||
(`0x00884cb0`, vtable `0x00a0f07c`), appends, and sets `_Mylast[-1].EvTurn = turn`
|
||||
(0x0088542d, `mov [eax-0x14], ecx`, i.e. `+4` off the new element at `_Mylast-0x18`).
|
||||
|
||||
### `EventStorage::PruneOldTurns` — 0x00879eb0, `__thiscall (int turn)`
|
||||
|
||||
Cutoff is `turn - 0x32` (**50 turns**), constant, not from config: `0x00879ec3 add ebx,-0x32`.
|
||||
|
||||
Precisely as coded — and this is a quirk worth reproducing rather than "fixing": it walks the
|
||||
**leading** run of buckets with `EvTurn < cutoff`, leaves `edi` pointing at the **last** one
|
||||
of that run, and then shifts from `edi` down to `_Myfirst`. So it erases `n-1` buckets, not
|
||||
`n`: **one stale bucket always survives**, and a single leading stale bucket is never
|
||||
removed at all (`0x00879ee2 cmp esi,edi; je` returns). It also stops at the first non-stale
|
||||
bucket, so a stale bucket after a fresh one is never reached.
|
||||
|
||||
### Convenience wrapper
|
||||
|
||||
`0x00886470` (`ret` and prototype not verified by this lane) posts via `PostEvent` twice at
|
||||
0x00886802 / 0x00886b22. **161 call sites in 110 functions** reference `PostEvent` directly —
|
||||
this is the single event API for the whole simulation, not a research-specific helper.
|
||||
|
||||
### Turn number
|
||||
|
||||
Every research-path site computes the turn as `*(int*)(*(char**)(player + 8) + 8)`.
|
||||
`ServerPlayer+8` is the `StrategyServer` *second* base; `0x0080e320` is
|
||||
`__thiscall void* ServerPlayer::GetServer()` = `[this+8] ? [this+8]-4 : 0`, so the field is
|
||||
`StrategyServer(primary base)+0x0c` — i.e. `ModCount`. This matches the B4 finding that
|
||||
`StrategyServer` has two bases four bytes apart.
|
||||
|
||||
---
|
||||
|
||||
## 3. Which events the research path posts
|
||||
|
||||
Order within `ServerPlayer::ProcessTurn` (0x00891340):
|
||||
|
||||
| # | site | event | condition |
|
||||
|---|---|---|---|
|
||||
| 1 | `TechTree::ProcessResearch` 0x00587b97 | `EVENT_RESEARCH_OVERBUDGET` | per node, see below |
|
||||
| 2 | → `SetResearched` → `OnTechResearched` 0x008919b5 | `EVENT_RESEARCH_COMPLETE` / `_UNDERBUDGET` | see below |
|
||||
| 3 | → `OnTechResearched` 0x00892427 | `EVENT_TEMPERANCE` | temperance tech cured ≥1 addicted system |
|
||||
| 4 | `TechTree::ProcessResearch` 0x00587ff4 | `EVENT_TECHS_UNLOCKED` | tail loop, ≥1 newly available node |
|
||||
| 5 | `ProcessTurn` 0x0089168c | `EVENT_NO_RESEARCH` | no target, no affordable tech, tree not exhausted |
|
||||
|
||||
`ProcessResearch`'s two posts bracket the per-node loop: OVERBUDGET fires **inside** the loop
|
||||
(so once per over-budget node), TECHS_UNLOCKED **once** after it.
|
||||
|
||||
### 3.1 `EVENT_RESEARCH_OVERBUDGET` — the B3 defect, fully explained
|
||||
|
||||
Posted at **0x00587b97**, in `TechTree::ProcessResearch` (0x005876c0).
|
||||
|
||||
Per-node arithmetic recovered from 0x00587732–0x00587907 (naming `cost =
|
||||
TechTree::GetNodeCost(node)` = 0x0057da00, `pts = node->points` at `node+0x1c`):
|
||||
|
||||
```
|
||||
minPts = max(cost * 50 / 100, 0) ; 0x51eb851f/sar 5 = /100
|
||||
maxPts = max(minPts, cost * 150 / 100)
|
||||
wasDone = (pts >= cost) ; [ebp-0xcd], setge @0x005877e1
|
||||
granted = min(requested, maxPts - pts)
|
||||
*overbudgetOut += requested - granted ; the out-param accumulates unspent points
|
||||
pts += granted
|
||||
nowDone = (pts >= cost) ; [ebp-0xce], setge @0x00587828
|
||||
|
||||
if (pts >= maxPts) chance = 1.0f, draw = 0.0f ; always completes
|
||||
else if (granted == 0) chance = 0.0f, draw = 1.0f ; never completes, no draw
|
||||
else chance = (float)(pts - minPts) / maxPts ; NOTE: / maxPts, not /(max-min)
|
||||
draw = rng.NextFloat()*(1.0-0.0) + 0.0 ; [0x009e1e68] == 0.0
|
||||
if (owner && owner->Species == 5) ; Zuul
|
||||
draw = max(draw, rng.NextFloat()*(1.0-0.0)+0.0)
|
||||
|
||||
if (chance < draw) { ; roll FAILED -> tech not completed this turn
|
||||
if (!wasDone && nowDone && owner) {
|
||||
PostEvent(EVENT_RESEARCH_OVERBUDGET); node->flag(+0x2c) = 2;
|
||||
}
|
||||
} else { ; roll succeeded
|
||||
log("Research completed at %d of %d (%.1f%%). (Odds: %.2f, Roll: %.2f)\n", ...)
|
||||
if (pts/cost < 0.8) node->flag(+0x2c) = 0;
|
||||
SetResearched(node->def, 2); ; 0x00581e10 -> OnTechResearched
|
||||
}
|
||||
```
|
||||
|
||||
So **over budget means: the node has accumulated at least its full cost, but the completion
|
||||
roll failed, and this is the first turn that has been true.** `!wasDone` is what makes it
|
||||
fire exactly once per node.
|
||||
|
||||
The record it posts:
|
||||
|
||||
| field | value | evidence |
|
||||
|---|---|---|
|
||||
| `EvDsc` | `"Research Over Budget"` | key `EVENTSUM_RESEARCH_OVERBUDGET`, slot `0x00ae48e0`, thunk key at `0x00a00cac` |
|
||||
| `EvMsg` | `"Research for %s has gone overbudget."` % `node->def->name` | key `EVENTMSG_RESEARCH_OVERBUDGET`, slot `0x00ae48e8`, key at `0x00a00ccc` |
|
||||
| `EvImg` | `"EVENT_RESEARCH_OVERBUDGET"` | literal `0x00a0078c`, pushed at 0x00587b24 |
|
||||
| `EvLoc` | `0` | `obj = NULL` (0x00587b2c) |
|
||||
| `EvPos` | `{FLT_MAX, FLT_MAX, FLT_MAX}` | `pos = NULL` → ctor default |
|
||||
| `EvAct` | `1` | literal, 0x00587b22 |
|
||||
| `EvCID` | `0` | ctor |
|
||||
|
||||
Both strings go through `0x008c97f0` (`__cdecl` format-into-`std::string`, 8 stack args:
|
||||
`out, fmt, a1..a6`) with the tech name as the single substitution; the summary format
|
||||
contains no `%s`, so it comes out literal. The tech name is `def+0x40` (a `std::string`;
|
||||
`_Myres` at `+0x54` selects heap vs. inline buffer, 0x00587a1d).
|
||||
|
||||
**Save cross-check:** `turn3-state.sav`, player 1, turn-3 bucket, `EvEID 3` is exactly this
|
||||
record, with `EvMsg "Research for Waldo Units has gone overbudget."` — the format string, the
|
||||
`%s` substitution, `EvAct 1`, `EvLoc 0` and the `FLT_MAX` position all match byte for byte.
|
||||
|
||||
### 3.2 `EVENT_RESEARCH_COMPLETE` / `EVENT_RESEARCH_UNDERBUDGET`
|
||||
|
||||
Posted at **0x008919b5** in `ServerPlayer::OnTechResearched` (0x00891790), guarded by
|
||||
`if (!silent)` (0x00891804 `cmp byte [ebp+0xc], 0; jne`).
|
||||
|
||||
```
|
||||
ratio = TechTree::GetProgressRatio(tree, def) ; 0x0057e950, float
|
||||
if (ratio >= 0.8f) img = "EVENT_RESEARCH_COMPLETE" (0x00a33368)
|
||||
sum = EVENTSUM_RESEARCH_COMPLETE (slot 0x00af09e8)
|
||||
msg = EVENTMSG_RESEARCH_COMPLETE (slot 0x00af09f0)
|
||||
else img = "EVENT_RESEARCH_UNDERBUDGET" (0x00a33380)
|
||||
sum = EVENTSUM_RESEARCH_UNDERBUDGET(slot 0x00af09f8)
|
||||
msg = EVENTMSG_RESEARCH_UNDERBUDGET(slot 0x00af0a00)
|
||||
PostEvent(sum, snprintf(msg, 0x100, def->name), NULL, NULL, turn, img, 1)
|
||||
```
|
||||
|
||||
The 0.8 constant is the `double` at `0x009e20c8` = `0.800000011920929`, i.e. `(double)0.8f` —
|
||||
the comparison is `fcomp` of the `float` ratio against that double. Text:
|
||||
|
||||
* `EVENTSUM_RESEARCH_COMPLETE` = `"Research Complete"`,
|
||||
`EVENTMSG_RESEARCH_COMPLETE` = `"Tech %s has been acquired"`
|
||||
* `EVENTSUM_RESEARCH_UNDERBUDGET` = `"Research Breakthrough!"`,
|
||||
`EVENTMSG_RESEARCH_UNDERBUDGET` = `"Your scientists made a breakthrough with %s. Research has completed ahead of schedule!"`
|
||||
|
||||
`EvAct = 1`, `obj = pos = NULL`, so `EvLoc = 0` and `EvPos = FLT_MAX³`. Message buffer is a
|
||||
0x100-byte stack buffer formatted with `0x008c8eb0` (`_snprintf`-shaped, 9 args), then
|
||||
assigned into a `std::string`, so **a message longer than 255 chars is truncated** — a real
|
||||
behaviour to reproduce.
|
||||
|
||||
Note the naming is counter-intuitive: `UNDERBUDGET` is the *cheap* completion (ratio < 0.8).
|
||||
|
||||
### 3.3 `EVENT_TEMPERANCE`
|
||||
|
||||
Posted at **0x00892427**, same function, after the per-species temperance sweep
|
||||
(0x00892280–0x008922b9: for each species with flag bit 5, cure every owned addicted system
|
||||
via 0x00745e40 / 0x00743800). Guarded by `!silent` **and** by a local "something was cured"
|
||||
flag (`[ebp-0x189]`, set at 0x008922a6). Strings `EVENTSUM_ADDICTION_TEMPERENCE` /
|
||||
`EVENTMSG_ADDICTION_TEMPERENCE` (slots `0x00af0a88` / `0x00af0a90`, note the shipped
|
||||
misspelling "TEMPERENCE").
|
||||
|
||||
`EvImg = "EVENT_TEMPERANCE"` (0x00a33340), `obj = pos = NULL`, and **`EvAct = 0`** — which
|
||||
means rule 5 of `PostEvent` fires and the stored `EvAct` becomes **2**, not 0. Any
|
||||
reimplementation that stores the literal 0 will differ from the oracle here.
|
||||
|
||||
### 3.4 `EVENT_TECHS_UNLOCKED`
|
||||
|
||||
Posted at **0x00587ff4**, at the tail of `ProcessResearch` (loop at 0x00587cc3). Collects
|
||||
every node `n` where `n != NULL`, `n->def != NULL`, `tree->nodes[n->def->index] != NULL`,
|
||||
that node's **`state` (`+0x14`) `== 2` (available)**, and **`n->turnAvailable` (`+0x20`)
|
||||
`== currentTurn`** (0x00587cfc–0x00587d42). If the collected vector is non-empty it posts
|
||||
once.
|
||||
|
||||
*Correction to `strategic-turn-internals.md:233`*, which reads the condition as "state==2 &&
|
||||
turnAvailable == currentTurn **&& parent researched**". There is no parent test: the
|
||||
`mov ecx,[ecx]; mov eax,[eax+ecx*4]` pair at 0x00587d0d–0x00587d19 dereferences `n->def` and
|
||||
then indexes `tree->nodes` by `def->[0]`, which is the tech's **own** index (the same
|
||||
indirection the entry loop uses at 0x00587738–0x00587740). It resolves back to `n` itself;
|
||||
the two null checks around it are defensive. The state test is therefore on `n`, not on a
|
||||
parent.
|
||||
|
||||
`EvDsc` = `EVENTSUM_UNLOCKEDTECHS` (slot `0x00ae48f0`) = `"New Technologies Available"`.
|
||||
`EvMsg` = `EVENTMSG_UNLOCKEDTECHS` (slot `0x00ae48f8`) =
|
||||
`"The following technologies are now available for research:"` followed by, per node, the
|
||||
separator string at `0x009e4588` and the node's `def->name` (0x00587f46–0x00587f82).
|
||||
`EvImg` = literal `"EVENT_TECHS_UNLOCKED"` (`0x00a00774`, length pushed as 0x14).
|
||||
`EvAct = 1`, `obj = pos = NULL`.
|
||||
|
||||
### 3.5 `EVENT_NO_RESEARCH`
|
||||
|
||||
Posted at **0x0089168c** in `ServerPlayer::ProcessTurn`. Condition (0x0089162a–0x0089167e):
|
||||
|
||||
```
|
||||
if (player->ResT (+0x294) == NULL)
|
||||
ListAvailableTechs(&out, turn, INT_MAX, 1) ; 0x00584e50
|
||||
if (out.empty() && TechTree::0x0057da90() != 0)
|
||||
PostEvent(EVENTSUM_NO_RESEARCH, EVENTMSG_NO_RESEARCH, NULL, NULL, turn,
|
||||
"EVENT_NO_RESEARCH" (0x00a3332c), 1)
|
||||
```
|
||||
|
||||
Both strings are `"No Research Project Assigned."` — matches `turn3-state.sav` player 0
|
||||
exactly (`EvAct 1`, `EvLoc 0`, `EvPos` FLT_MAX³).
|
||||
|
||||
Also in this window (0x008915ec–0x00891624): `RollResearchEvent` (0x0088df20) is called when
|
||||
`ResT != NULL && ResErrRoll(+0x3b4) != 0 && (const at 0x00a2c788) < progressRatio`, then
|
||||
`ResErrRoll` is cleared — the same draw the B2/B3 lanes measured. `RollResearchAccident`
|
||||
(0x00889dc0) posts the `EVENT_LABACCIDENT_*` family; not traced here.
|
||||
|
||||
---
|
||||
|
||||
## 4. What is *not* verified
|
||||
|
||||
* `0x00886470` — the wrapper that also calls `PostEvent`. Its prototype and its two call
|
||||
paths were not read; I only established that it exists and is not on the research path.
|
||||
* `TurnEvents`' vptr/vtable contents beyond slot 0 (the virtual dtor used by the pruner).
|
||||
* The `sasc` tag at `0x00a2bde0` — adjacent to the event tag block, owner unknown.
|
||||
* Whether `PostEvent`'s dedup can ever collapse two research events in practice. It cannot
|
||||
for OVERBUDGET vs COMPLETE (different `EvImg`), but two *different techs* going over budget
|
||||
in the same turn differ only in `EvMsg`, which **is** compared — so both are kept. Verified
|
||||
by reading the comparator, not by observing it.
|
||||
* `EventStorage::Write` (the save-side counterpart of 0x00825cc0) was not located; only the
|
||||
read/`0x8b9d50` direction and the two `TurnEvents` directions were disassembled. The field
|
||||
order is identical in both directions and is confirmed by the save, so this is a gap in
|
||||
coverage, not in confidence about the layout.
|
||||
* The 50-turn prune has never been observed running (our saves are at turn ≤ 3).
|
||||
|
|
@ -191,9 +191,14 @@ From the `ProcessResearch` trace/compare/replace runs (engine repo `docs/B3.md`;
|
|||
`SetResearched` itself. Relevant to the B2 lane: at least one strategic effect rolls.
|
||||
* **`EVENT_RESEARCH_OVERBUDGET` is raised in the same branch that sets `node.flag = 2`**, with
|
||||
`EvDsc "Research Over Budget"`, `EvMsg "Research for <tech> has gone overbudget."`,
|
||||
`EvImg "EVENT_RESEARCH_OVERBUDGET"`, `EvAct 1`, `EvPos {inf,inf,inf}`, and it bumps the player's
|
||||
`EvNxID`. A replace-mode run that sets only the flag differs from the oracle by exactly this one
|
||||
event and nothing else in 40,300 save items.
|
||||
`EvImg "EVENT_RESEARCH_OVERBUDGET"`, `EvAct 1`, `EvPos {FLT_MAX,FLT_MAX,FLT_MAX}`, and it bumps
|
||||
the player's `EvNxID`. A replace-mode run that sets only the flag differs from the oracle by
|
||||
exactly this one event and nothing else in 40,300 save items.
|
||||
**Corrected 2026-09-08 (lane E):** `EvPos` was recorded here as `{inf,inf,inf}`. It is
|
||||
**`FLT_MAX` (0x7f7fffff), not infinity** — the `PlayerEvent` constructor at 0x0084ee30 copies
|
||||
the `Vector3` global at 0x00af0dc8, whose bytes are `FF FF 7F 7F` x3, and the save carries
|
||||
2139095039 in all three slots. Writing `+inf` (0x7f800000) would change the save bytes.
|
||||
Full API: `findings/subsystems/events.md`.
|
||||
* Tech-tree size in this game: **293 nodes** per player tree.
|
||||
|
||||
---
|
||||
|
|
|
|||
|
|
@ -230,8 +230,14 @@ for each {node, points} in alloc: // one entry: the current Re
|
|||
}
|
||||
for each node with state==2 && progress!=0: progress = max(0, progress - ftol(Cost(node)*0.05f)) // 0x009e5060 = (double)0.05f
|
||||
// NB the funded node is state 3, so it is NOT in this sweep (B3 live trace; formula-gaps Q8 corrected)
|
||||
for each node with state==2 && turnAvailable == currentTurn && parent researched → EVENT_TECHS_UNLOCKED (names list)
|
||||
for each node with state==2 && turnAvailable == currentTurn → EVENT_TECHS_UNLOCKED (names list)
|
||||
```
|
||||
*(Corrected 2026-09-08, lane E: there is **no** "parent researched" clause. The
|
||||
`mov ecx,[ecx]; mov eax,[eax+ecx*4]` pair at 0x00587d0d–0x00587d19 dereferences `node->def`
|
||||
and indexes `tree->nodes` by `def->[0]`, which is the tech's **own** id — the same indirection
|
||||
the entry loop uses at 0x00587738 — so it resolves back to the node itself and the state test
|
||||
is on that node. The surrounding null checks are defensive. Event details:
|
||||
`findings/subsystems/events.md`.)*
|
||||
Net effect: the current tech gains `spend − 5 %·cost`; every other partially researched tech decays 5 % of its cost per turn.
|
||||
|
||||
### 2.4 Unlocking (`SetResearched`)
|
||||
|
|
|
|||
|
|
@ -2593,6 +2593,454 @@
|
|||
"prototype": "float** -- pointer slot; storage 0x00b212d4 (shipped value 0.33). NOTE min == max in the shipped data, so the stutter ramp collapses to a constant 0.33x inside any influence sphere",
|
||||
"status": "verified",
|
||||
"source": "handoff/b4-colony-movement.md"
|
||||
},
|
||||
{
|
||||
"name": "ServerPlayer_GetEventStorage",
|
||||
"addr": "0x0080db00",
|
||||
"convention": "thiscall",
|
||||
"prototype": "EventStorage* (ServerPlayer* this) /* whole body: lea eax,[ecx+0x29c]; ret. No stack args, plain RET */",
|
||||
"status": "verified",
|
||||
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
|
||||
},
|
||||
{
|
||||
"name": "ServerPlayer_GetServer",
|
||||
"addr": "0x0080e320",
|
||||
"convention": "thiscall",
|
||||
"prototype": "void* (ServerPlayer* this) /* eax = [this+8] ? [this+8]-4 : 0. StrategyServer primary base; turn = *(int*)(result+0x0c) */",
|
||||
"status": "verified",
|
||||
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
|
||||
},
|
||||
{
|
||||
"name": "EventStorage_PostEvent",
|
||||
"addr": "0x008862b0",
|
||||
"convention": "thiscall",
|
||||
"prototype": "int (EventStorage* this, std::string summary /*BY VALUE 0x1c -> EvDsc*/, std::string message /*BY VALUE 0x1c -> EvMsg*/, void* obj, Vector3* pos, int turn, const char* img, int act) RET 0x4c. Returns the event id (a duplicate's id if one already exists in the turn bucket). Callee frees both by-value string buffers. img==NULL -> \"\". act==0 && obj==NULL && pos==NULL -> stored EvAct becomes 2. EvLoc = obj ? obj[+4] : 0; EvPos = obj ? obj[+0x18..0x20] : pos ? *pos : FLT_MAX triple. 161 call sites in 110 functions: this is the whole simulation's event API",
|
||||
"status": "verified",
|
||||
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
|
||||
},
|
||||
{
|
||||
"name": "EventStorage_GetOrCreateTurnBucket",
|
||||
"addr": "0x00885380",
|
||||
"convention": "thiscall",
|
||||
"prototype": "TurnEvents* (EventStorage* this, int turn) RET 4. Linear scan with NO early exit, so it returns the LAST bucket whose EvTurn == turn; otherwise appends a new bucket (ctor 0x00884cb0, vtable 0x00a0f07c) and sets its EvTurn",
|
||||
"status": "verified",
|
||||
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
|
||||
},
|
||||
{
|
||||
"name": "EventStorage_FindDuplicate",
|
||||
"addr": "0x00825d40",
|
||||
"convention": "thiscall",
|
||||
"prototype": "PlayerEvent* (EventStorage* this, TurnEvents* bucket, PlayerEvent* candidate) RET 8. NULL bucket -> 0. Match requires EvAct, EvLoc, all three EvPos floats (fucompp), EvMsg and EvImg to be equal. EvDsc is NOT compared",
|
||||
"status": "verified",
|
||||
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
|
||||
},
|
||||
{
|
||||
"name": "EventStorage_PruneOldTurns",
|
||||
"addr": "0x00879eb0",
|
||||
"convention": "thiscall",
|
||||
"prototype": "void (EventStorage* this, int turn) RET 4. Cutoff = turn - 0x32 (50), a code constant. Shifts from the LAST bucket of the leading run with EvTurn < cutoff, so it erases n-1 of n leading stale buckets: one stale bucket always survives and a single leading stale bucket is never removed",
|
||||
"status": "verified",
|
||||
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
|
||||
},
|
||||
{
|
||||
"name": "EventStorage_Read",
|
||||
"addr": "0x00825cc0",
|
||||
"convention": "thiscall",
|
||||
"prototype": "void (EventStorage* this, IStreamable* s) RET 4. Order: EvNxID (int, tag 0x00a2bd90), then nested collection \"Events\" (tag 0x00a2bda0, descriptor vtable 0x00a2da8c) over this+4",
|
||||
"status": "verified",
|
||||
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
|
||||
},
|
||||
{
|
||||
"name": "TurnEvents_Write",
|
||||
"addr": "0x00825bb0",
|
||||
"convention": "thiscall",
|
||||
"prototype": "void (TurnEvents* this, IStreamable* s) RET 4. Order: EvTurn (int, tag 0x00a2bd98), then nested collection \"Events\" (descriptor vtable 0x00a2da7c) over this+8",
|
||||
"status": "verified",
|
||||
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
|
||||
},
|
||||
{
|
||||
"name": "TurnEvents_Read",
|
||||
"addr": "0x00825c40",
|
||||
"convention": "thiscall",
|
||||
"prototype": "void (TurnEvents* this, IStreamable* s) RET 4. Same field order as TurnEvents_Write",
|
||||
"status": "verified",
|
||||
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
|
||||
},
|
||||
{
|
||||
"name": "PlayerEvent_ctor",
|
||||
"addr": "0x0084ee30",
|
||||
"convention": "thiscall",
|
||||
"prototype": "PlayerEvent* (PlayerEvent* this) RET 0. vptr=0x00a21958; EvEID=EvLoc=EvAct=EvCID=0; the three std::strings = \"\" (0x009e100c); EvPos = the Vector3 global at 0x00af0dc8 = {FLT_MAX, FLT_MAX, FLT_MAX} (0x7f7fffff x3, NOT infinity)",
|
||||
"status": "verified",
|
||||
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
|
||||
},
|
||||
{
|
||||
"name": "PlayerEvent_Serialize",
|
||||
"addr": "0x00825970",
|
||||
"convention": "thiscall",
|
||||
"prototype": "void (PlayerEvent* this, IStreamable* s) RET 4. vftable slot 1. Field order on the wire: EvEID(+4) EvDsc(+8) EvMsg(+0x24) EvImg(+0x50) EvLoc(+0x40) EvPos(+0x44) EvAct(+0x6c) EvCID(+0x70)",
|
||||
"status": "verified",
|
||||
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
|
||||
},
|
||||
{
|
||||
"name": "ProcessResearch_PostEventOverbudget",
|
||||
"addr": "0x00587b97",
|
||||
"convention": "thiscall",
|
||||
"prototype": "call site: EventStorage::PostEvent for EVENT_RESEARCH_OVERBUDGET. Guard at 0x005879e9: !wasDone && nowDone && owner, inside the completion-roll-FAILED branch (chance < draw). Sets TechNode.flag(+0x2c)=2 at 0x00587ba3. EvAct=1, obj=NULL, pos=NULL",
|
||||
"status": "verified",
|
||||
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
|
||||
},
|
||||
{
|
||||
"name": "ProcessResearch_PostEventTechsUnlocked",
|
||||
"addr": "0x00587ff4",
|
||||
"convention": "thiscall",
|
||||
"prototype": "call site: EventStorage::PostEvent for EVENT_TECHS_UNLOCKED, once after the per-node loop, if any node has state==2 and turnAvailable==currentTurn. EvAct=1, obj=NULL, pos=NULL",
|
||||
"status": "verified",
|
||||
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
|
||||
},
|
||||
{
|
||||
"name": "OnTechResearched_PostEventComplete",
|
||||
"addr": "0x008919b5",
|
||||
"convention": "thiscall",
|
||||
"prototype": "call site: EventStorage::PostEvent for EVENT_RESEARCH_COMPLETE / _UNDERBUDGET. Guarded by !silent ([ebp+0xc]==0). Message is _snprintf'd (0x008c8eb0) into a 0x100-byte buffer, so >255 chars truncate. EvAct=1",
|
||||
"status": "verified",
|
||||
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
|
||||
},
|
||||
{
|
||||
"name": "OnTechResearched_PostEventTemperance",
|
||||
"addr": "0x00892427",
|
||||
"convention": "thiscall",
|
||||
"prototype": "call site: EventStorage::PostEvent for EVENT_TEMPERANCE. Guarded by !silent AND by the 'a system was cured' local at [ebp-0x189]. Pushed act=0 with obj=pos=NULL, so the STORED EvAct is 2",
|
||||
"status": "verified",
|
||||
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
|
||||
},
|
||||
{
|
||||
"name": "ServerPlayer_ProcessTurn_PostEventNoResearch",
|
||||
"addr": "0x0089168c",
|
||||
"convention": "thiscall",
|
||||
"prototype": "call site: EventStorage::PostEvent for EVENT_NO_RESEARCH. Condition at 0x0089162a: ResT(+0x294)==NULL && ListAvailableTechs(0x00584e50, turn, INT_MAX, 1) returned empty && TechTree 0x0057da90 != 0. EvAct=1",
|
||||
"status": "verified",
|
||||
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
|
||||
},
|
||||
{
|
||||
"name": "TechTree_ProcessResearch_LogFormat",
|
||||
"addr": "0x00a007a8",
|
||||
"convention": "data",
|
||||
"prototype": "const char* \"Research completed at %d of %d (%.1f%%). (Odds: %.2f, Roll: %.2f)\\n\" -- log line on the completion-roll-SUCCEEDED branch, 0x00587977",
|
||||
"status": "verified",
|
||||
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
|
||||
},
|
||||
{
|
||||
"name": "ServerPlayer_off_Events",
|
||||
"offset": "0x29c",
|
||||
"convention": "offset",
|
||||
"prototype": "EventStorage ServerPlayer::Events -- embedded, size 0x1c. Confirmed by ServerPlayer_GetEventStorage",
|
||||
"status": "verified",
|
||||
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
|
||||
},
|
||||
{
|
||||
"name": "EventStorage_off_Events",
|
||||
"offset": "0x04",
|
||||
"convention": "offset",
|
||||
"prototype": "std::vector<TurnEvents> _Myfirst (element stride 0x18; _Mylast +0x08, _Myend +0x0c, _Alval +0x10)",
|
||||
"status": "verified",
|
||||
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
|
||||
},
|
||||
{
|
||||
"name": "EventStorage_off_EvNxID",
|
||||
"offset": "0x14",
|
||||
"convention": "offset",
|
||||
"prototype": "int EvNxID -- next event id. Starts at 0; PostEvent promotes 0->1 on the first post, then post-increments. ServerPlayer+0x2b0",
|
||||
"status": "verified",
|
||||
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
|
||||
},
|
||||
{
|
||||
"name": "EventStorage_sizeof",
|
||||
"offset": "0x1c",
|
||||
"convention": "offset",
|
||||
"prototype": "sizeof(EventStorage)",
|
||||
"status": "verified",
|
||||
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
|
||||
},
|
||||
{
|
||||
"name": "TurnEvents_off_EvTurn",
|
||||
"offset": "0x04",
|
||||
"convention": "offset",
|
||||
"prototype": "int EvTurn (after the vptr at +0)",
|
||||
"status": "verified",
|
||||
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
|
||||
},
|
||||
{
|
||||
"name": "TurnEvents_off_Events",
|
||||
"offset": "0x08",
|
||||
"convention": "offset",
|
||||
"prototype": "std::vector<PlayerEvent> _Myfirst (stride 0x74; _Mylast +0x0c, _Myend +0x10, _Alval +0x14)",
|
||||
"status": "verified",
|
||||
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
|
||||
},
|
||||
{
|
||||
"name": "TurnEvents_sizeof",
|
||||
"offset": "0x18",
|
||||
"convention": "offset",
|
||||
"prototype": "sizeof(TurnEvents); the outer vector's stride, from the /24 divide at 0x008853b3",
|
||||
"status": "verified",
|
||||
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
|
||||
},
|
||||
{
|
||||
"name": "PlayerEvent_off_EvEID",
|
||||
"offset": "0x04",
|
||||
"convention": "offset",
|
||||
"prototype": "int EvEID (after the vptr at +0)",
|
||||
"status": "verified",
|
||||
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
|
||||
},
|
||||
{
|
||||
"name": "PlayerEvent_off_EvDsc",
|
||||
"offset": "0x08",
|
||||
"convention": "offset",
|
||||
"prototype": "std::string EvDsc (summary / title), 0x1c bytes",
|
||||
"status": "verified",
|
||||
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
|
||||
},
|
||||
{
|
||||
"name": "PlayerEvent_off_EvMsg",
|
||||
"offset": "0x24",
|
||||
"convention": "offset",
|
||||
"prototype": "std::string EvMsg (body), 0x1c bytes",
|
||||
"status": "verified",
|
||||
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
|
||||
},
|
||||
{
|
||||
"name": "PlayerEvent_off_EvLoc",
|
||||
"offset": "0x40",
|
||||
"convention": "offset",
|
||||
"prototype": "int EvLoc (object id, or 0)",
|
||||
"status": "verified",
|
||||
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
|
||||
},
|
||||
{
|
||||
"name": "PlayerEvent_off_EvPos",
|
||||
"offset": "0x44",
|
||||
"convention": "offset",
|
||||
"prototype": "float[3] EvPos (default FLT_MAX x3)",
|
||||
"status": "verified",
|
||||
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
|
||||
},
|
||||
{
|
||||
"name": "PlayerEvent_off_EvImg",
|
||||
"offset": "0x50",
|
||||
"convention": "offset",
|
||||
"prototype": "std::string EvImg (event-type name, e.g. \"EVENT_RESEARCH_OVERBUDGET\"), 0x1c bytes",
|
||||
"status": "verified",
|
||||
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
|
||||
},
|
||||
{
|
||||
"name": "PlayerEvent_off_EvAct",
|
||||
"offset": "0x6c",
|
||||
"convention": "offset",
|
||||
"prototype": "int EvAct",
|
||||
"status": "verified",
|
||||
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
|
||||
},
|
||||
{
|
||||
"name": "PlayerEvent_off_EvCID",
|
||||
"offset": "0x70",
|
||||
"convention": "offset",
|
||||
"prototype": "int EvCID -- ctor sets 0 and PostEvent never writes it",
|
||||
"status": "verified",
|
||||
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
|
||||
},
|
||||
{
|
||||
"name": "PlayerEvent_sizeof",
|
||||
"offset": "0x74",
|
||||
"convention": "offset",
|
||||
"prototype": "sizeof(PlayerEvent) = 116. Two independent confirmations: the /116 divide at 0x00825d5f and PostEvent's 'mov [_Mylast-0x70], id' writing EvEID at element+4",
|
||||
"status": "verified",
|
||||
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
|
||||
},
|
||||
{
|
||||
"name": "EventStorage_PruneWindowTurns",
|
||||
"offset": "0x32",
|
||||
"convention": "offset",
|
||||
"prototype": "prune cutoff = turn - 50; a code constant at 0x00879ec3, not config",
|
||||
"status": "verified",
|
||||
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
|
||||
},
|
||||
{
|
||||
"name": "g_vft_PlayerEvent",
|
||||
"addr": "0x00a21958",
|
||||
"convention": "data",
|
||||
"prototype": "void** -- PlayerEvent vftable. slot0 dtor 0x007694d0, slot1 Serialize 0x00825970, slot2 0x00825ab0. RTTI locator 0x00a80a7c",
|
||||
"status": "verified",
|
||||
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
|
||||
},
|
||||
{
|
||||
"name": "g_vft_TurnEvents",
|
||||
"addr": "0x00a0f07c",
|
||||
"convention": "data",
|
||||
"prototype": "void** -- TurnEvents vftable (bucket ctor 0x00884cb0); slot0 is the virtual dtor the pruner calls",
|
||||
"status": "verified",
|
||||
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
|
||||
},
|
||||
{
|
||||
"name": "g_EventTagTable",
|
||||
"addr": "0x00a2bd88",
|
||||
"convention": "data",
|
||||
"prototype": "const char[12][8] -- stride 8: EvEID EvNxID EvTurn Events EvPos EvLoc EvMsg EvImg EvDsc EvCID EvAct sasc",
|
||||
"status": "verified",
|
||||
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
|
||||
},
|
||||
{
|
||||
"name": "g_Vector3_Invalid",
|
||||
"addr": "0x00af0dc8",
|
||||
"convention": "data",
|
||||
"prototype": "float[3] = {0x7f7fffff, 0x7f7fffff, 0x7f7fffff} = FLT_MAX. PlayerEvent's default EvPos. NOT infinity",
|
||||
"status": "verified",
|
||||
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
|
||||
},
|
||||
{
|
||||
"name": "g_str_EVENT_RESEARCH_OVERBUDGET",
|
||||
"addr": "0x00a0078c",
|
||||
"convention": "data",
|
||||
"prototype": "const char* \"EVENT_RESEARCH_OVERBUDGET\" -- EvImg pushed at 0x00587b24",
|
||||
"status": "verified",
|
||||
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
|
||||
},
|
||||
{
|
||||
"name": "g_str_EVENT_RESEARCH_COMPLETE",
|
||||
"addr": "0x00a33368",
|
||||
"convention": "data",
|
||||
"prototype": "const char* \"EVENT_RESEARCH_COMPLETE\" -- EvImg for progress/cost >= 0.8",
|
||||
"status": "verified",
|
||||
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
|
||||
},
|
||||
{
|
||||
"name": "g_str_EVENT_RESEARCH_UNDERBUDGET",
|
||||
"addr": "0x00a33380",
|
||||
"convention": "data",
|
||||
"prototype": "const char* \"EVENT_RESEARCH_UNDERBUDGET\" -- EvImg for progress/cost < 0.8",
|
||||
"status": "verified",
|
||||
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
|
||||
},
|
||||
{
|
||||
"name": "g_str_EVENT_TEMPERANCE",
|
||||
"addr": "0x00a33340",
|
||||
"convention": "data",
|
||||
"prototype": "const char* \"EVENT_TEMPERANCE\" -- EvImg at 0x008923ae; posted with act=0 so the stored EvAct is 2",
|
||||
"status": "verified",
|
||||
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
|
||||
},
|
||||
{
|
||||
"name": "g_str_EVENT_TECHS_UNLOCKED",
|
||||
"addr": "0x00a00774",
|
||||
"convention": "data",
|
||||
"prototype": "const char* \"EVENT_TECHS_UNLOCKED\" (length 0x14 pushed at 0x00587eb6)",
|
||||
"status": "verified",
|
||||
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
|
||||
},
|
||||
{
|
||||
"name": "g_str_EVENT_NO_RESEARCH",
|
||||
"addr": "0x00a3332c",
|
||||
"convention": "data",
|
||||
"prototype": "const char* \"EVENT_NO_RESEARCH\" -- EvImg at 0x0089168c",
|
||||
"status": "verified",
|
||||
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
|
||||
},
|
||||
{
|
||||
"name": "g_ptr_EVENTSUM_RESEARCH_OVERBUDGET",
|
||||
"addr": "0x00ae48e4",
|
||||
"convention": "data",
|
||||
"prototype": "const char** -> slot 0x00ae48e0; Strings.csv key EVENTSUM_RESEARCH_OVERBUDGET = \"Research Over Budget\"",
|
||||
"status": "verified",
|
||||
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
|
||||
},
|
||||
{
|
||||
"name": "g_ptr_EVENTMSG_RESEARCH_OVERBUDGET",
|
||||
"addr": "0x00ae48ec",
|
||||
"convention": "data",
|
||||
"prototype": "const char** -> slot 0x00ae48e8; key EVENTMSG_RESEARCH_OVERBUDGET = \"Research for %s has gone overbudget.\"",
|
||||
"status": "verified",
|
||||
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
|
||||
},
|
||||
{
|
||||
"name": "g_ptr_EVENTSUM_UNLOCKEDTECHS",
|
||||
"addr": "0x00ae48f4",
|
||||
"convention": "data",
|
||||
"prototype": "const char** -> slot 0x00ae48f0; key EVENTSUM_UNLOCKEDTECHS = \"New Technologies Available\"",
|
||||
"status": "verified",
|
||||
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
|
||||
},
|
||||
{
|
||||
"name": "g_ptr_EVENTMSG_UNLOCKEDTECHS",
|
||||
"addr": "0x00ae48fc",
|
||||
"convention": "data",
|
||||
"prototype": "const char** -> slot 0x00ae48f8; key EVENTMSG_UNLOCKEDTECHS",
|
||||
"status": "verified",
|
||||
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
|
||||
},
|
||||
{
|
||||
"name": "g_ptr_EVENTSUM_RESEARCH_COMPLETE",
|
||||
"addr": "0x00af09ec",
|
||||
"convention": "data",
|
||||
"prototype": "const char** -> slot 0x00af09e8; key EVENTSUM_RESEARCH_COMPLETE = \"Research Complete\"",
|
||||
"status": "verified",
|
||||
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
|
||||
},
|
||||
{
|
||||
"name": "g_ptr_EVENTMSG_RESEARCH_COMPLETE",
|
||||
"addr": "0x00af09f4",
|
||||
"convention": "data",
|
||||
"prototype": "const char** -> slot 0x00af09f0; key EVENTMSG_RESEARCH_COMPLETE = \"Tech %s has been acquired\"",
|
||||
"status": "verified",
|
||||
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
|
||||
},
|
||||
{
|
||||
"name": "g_ptr_EVENTSUM_RESEARCH_UNDERBUDGET",
|
||||
"addr": "0x00af09fc",
|
||||
"convention": "data",
|
||||
"prototype": "const char** -> slot 0x00af09f8; key EVENTSUM_RESEARCH_UNDERBUDGET = \"Research Breakthrough!\"",
|
||||
"status": "verified",
|
||||
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
|
||||
},
|
||||
{
|
||||
"name": "g_ptr_EVENTMSG_RESEARCH_UNDERBUDGET",
|
||||
"addr": "0x00af0a04",
|
||||
"convention": "data",
|
||||
"prototype": "const char** -> slot 0x00af0a00; key EVENTMSG_RESEARCH_UNDERBUDGET",
|
||||
"status": "verified",
|
||||
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
|
||||
},
|
||||
{
|
||||
"name": "g_ptr_EVENTSUM_ADDICTION_TEMPERENCE",
|
||||
"addr": "0x00af0a8c",
|
||||
"convention": "data",
|
||||
"prototype": "const char** -> slot 0x00af0a88; key EVENTSUM_ADDICTION_TEMPERENCE (shipped misspelling)",
|
||||
"status": "verified",
|
||||
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
|
||||
},
|
||||
{
|
||||
"name": "g_ptr_EVENTMSG_ADDICTION_TEMPERENCE",
|
||||
"addr": "0x00af0a94",
|
||||
"convention": "data",
|
||||
"prototype": "const char** -> slot 0x00af0a90; key EVENTMSG_ADDICTION_TEMPERENCE",
|
||||
"status": "verified",
|
||||
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
|
||||
},
|
||||
{
|
||||
"name": "g_dbl_ResearchUnderbudgetThreshold",
|
||||
"addr": "0x009e20c8",
|
||||
"convention": "data",
|
||||
"prototype": "double 0.800000011920929 = (double)0.8f. progress/cost >= this -> EVENT_RESEARCH_COMPLETE, else EVENT_RESEARCH_UNDERBUDGET; the same constant gates TechNode.flag=0 in ProcessResearch",
|
||||
"status": "verified",
|
||||
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
|
||||
},
|
||||
{
|
||||
"name": "g_dbl_ResearchRollBias",
|
||||
"addr": "0x009e1e68",
|
||||
"convention": "data",
|
||||
"prototype": "double 0.0 -- the research completion draw is NextFloat()*(1.0-this)+this, so it is a plain NextFloat()",
|
||||
"status": "verified",
|
||||
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// GENERATED — do not edit. Facts about Sword of the Stars.exe (GOG 1.8.1).
|
||||
// Source: sots-re ghidra/addresses.json @ ff67ec0, generated 2026-09-08 by tools/gen_addresses.py
|
||||
// Source: sots-re ghidra/addresses.json @ 84d5609, generated 2026-09-08 by tools/gen_addresses.py
|
||||
// Runtime address = (uintptr_t)GetModuleHandle(NULL) + RVA (the exe is ASLR-relocated).
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
|
@ -655,5 +655,117 @@ constexpr uint32_t g_ptr_STUTTER_SYSTEM_INFLUENCE_RADIUS = 0x006ebc44;
|
|||
constexpr uint32_t g_ptr_STUTTER_MIN_SPEED = 0x006ebc48;
|
||||
// data float** -- pointer slot; storage 0x00b212d4 (shipped value 0.33). NOTE min == max in the shipped data, so the stutter ramp collapses to a constant 0.33x inside any influence sphere [verified]
|
||||
constexpr uint32_t g_ptr_STUTTER_MAX_SPEED = 0x006ebc4c;
|
||||
// thiscall EventStorage* (ServerPlayer* this) /* whole body: lea eax,[ecx+0x29c]; ret. No stack args, plain RET */ [verified]
|
||||
constexpr uint32_t ServerPlayer_GetEventStorage = 0x0040db00;
|
||||
// thiscall void* (ServerPlayer* this) /* eax = [this+8] ? [this+8]-4 : 0. StrategyServer primary base; turn = *(int*)(result+0x0c) */ [verified]
|
||||
constexpr uint32_t ServerPlayer_GetServer = 0x0040e320;
|
||||
// thiscall int (EventStorage* this, std::string summary /*BY VALUE 0x1c -> EvDsc*/, std::string message /*BY VALUE 0x1c -> EvMsg*/, void* obj, Vector3* pos, int turn, const char* img, int act) RET 0x4c. Returns the event id (a duplicate's id if one already exists in the turn bucket). Callee frees both by-value string buffers. img==NULL -> "". act==0 && obj==NULL && pos==NULL -> stored EvAct becomes 2. EvLoc = obj ? obj[+4] : 0; EvPos = obj ? obj[+0x18..0x20] : pos ? *pos : FLT_MAX triple. 161 call sites in 110 functions: this is the whole simulation's event API [verified]
|
||||
constexpr uint32_t EventStorage_PostEvent = 0x004862b0;
|
||||
// thiscall TurnEvents* (EventStorage* this, int turn) RET 4. Linear scan with NO early exit, so it returns the LAST bucket whose EvTurn == turn; otherwise appends a new bucket (ctor 0x00884cb0, vtable 0x00a0f07c) and sets its EvTurn [verified]
|
||||
constexpr uint32_t EventStorage_GetOrCreateTurnBucket = 0x00485380;
|
||||
// thiscall PlayerEvent* (EventStorage* this, TurnEvents* bucket, PlayerEvent* candidate) RET 8. NULL bucket -> 0. Match requires EvAct, EvLoc, all three EvPos floats (fucompp), EvMsg and EvImg to be equal. EvDsc is NOT compared [verified]
|
||||
constexpr uint32_t EventStorage_FindDuplicate = 0x00425d40;
|
||||
// thiscall void (EventStorage* this, int turn) RET 4. Cutoff = turn - 0x32 (50), a code constant. Shifts from the LAST bucket of the leading run with EvTurn < cutoff, so it erases n-1 of n leading stale buckets: one stale bucket always survives and a single leading stale bucket is never removed [verified]
|
||||
constexpr uint32_t EventStorage_PruneOldTurns = 0x00479eb0;
|
||||
// thiscall void (EventStorage* this, IStreamable* s) RET 4. Order: EvNxID (int, tag 0x00a2bd90), then nested collection "Events" (tag 0x00a2bda0, descriptor vtable 0x00a2da8c) over this+4 [verified]
|
||||
constexpr uint32_t EventStorage_Read = 0x00425cc0;
|
||||
// thiscall void (TurnEvents* this, IStreamable* s) RET 4. Order: EvTurn (int, tag 0x00a2bd98), then nested collection "Events" (descriptor vtable 0x00a2da7c) over this+8 [verified]
|
||||
constexpr uint32_t TurnEvents_Write = 0x00425bb0;
|
||||
// thiscall void (TurnEvents* this, IStreamable* s) RET 4. Same field order as TurnEvents_Write [verified]
|
||||
constexpr uint32_t TurnEvents_Read = 0x00425c40;
|
||||
// thiscall PlayerEvent* (PlayerEvent* this) RET 0. vptr=0x00a21958; EvEID=EvLoc=EvAct=EvCID=0; the three std::strings = "" (0x009e100c); EvPos = the Vector3 global at 0x00af0dc8 = {FLT_MAX, FLT_MAX, FLT_MAX} (0x7f7fffff x3, NOT infinity) [verified]
|
||||
constexpr uint32_t PlayerEvent_ctor = 0x0044ee30;
|
||||
// thiscall void (PlayerEvent* this, IStreamable* s) RET 4. vftable slot 1. Field order on the wire: EvEID(+4) EvDsc(+8) EvMsg(+0x24) EvImg(+0x50) EvLoc(+0x40) EvPos(+0x44) EvAct(+0x6c) EvCID(+0x70) [verified]
|
||||
constexpr uint32_t PlayerEvent_Serialize = 0x00425970;
|
||||
// thiscall call site: EventStorage::PostEvent for EVENT_RESEARCH_OVERBUDGET. Guard at 0x005879e9: !wasDone && nowDone && owner, inside the completion-roll-FAILED branch (chance < draw). Sets TechNode.flag(+0x2c)=2 at 0x00587ba3. EvAct=1, obj=NULL, pos=NULL [verified]
|
||||
constexpr uint32_t ProcessResearch_PostEventOverbudget = 0x00187b97;
|
||||
// thiscall call site: EventStorage::PostEvent for EVENT_TECHS_UNLOCKED, once after the per-node loop, if any node has state==2 and turnAvailable==currentTurn. EvAct=1, obj=NULL, pos=NULL [verified]
|
||||
constexpr uint32_t ProcessResearch_PostEventTechsUnlocked = 0x00187ff4;
|
||||
// thiscall call site: EventStorage::PostEvent for EVENT_RESEARCH_COMPLETE / _UNDERBUDGET. Guarded by !silent ([ebp+0xc]==0). Message is _snprintf'd (0x008c8eb0) into a 0x100-byte buffer, so >255 chars truncate. EvAct=1 [verified]
|
||||
constexpr uint32_t OnTechResearched_PostEventComplete = 0x004919b5;
|
||||
// thiscall call site: EventStorage::PostEvent for EVENT_TEMPERANCE. Guarded by !silent AND by the 'a system was cured' local at [ebp-0x189]. Pushed act=0 with obj=pos=NULL, so the STORED EvAct is 2 [verified]
|
||||
constexpr uint32_t OnTechResearched_PostEventTemperance = 0x00492427;
|
||||
// thiscall call site: EventStorage::PostEvent for EVENT_NO_RESEARCH. Condition at 0x0089162a: ResT(+0x294)==NULL && ListAvailableTechs(0x00584e50, turn, INT_MAX, 1) returned empty && TechTree 0x0057da90 != 0. EvAct=1 [verified]
|
||||
constexpr uint32_t ServerPlayer_ProcessTurn_PostEventNoResearch = 0x0049168c;
|
||||
// data const char* "Research completed at %d of %d (%.1f%%). (Odds: %.2f, Roll: %.2f)\n" -- log line on the completion-roll-SUCCEEDED branch, 0x00587977 [verified]
|
||||
constexpr uint32_t TechTree_ProcessResearch_LogFormat = 0x006007a8;
|
||||
// offset EventStorage ServerPlayer::Events -- embedded, size 0x1c. Confirmed by ServerPlayer_GetEventStorage [verified]
|
||||
constexpr uint32_t ServerPlayer_off_Events = 0x0000029c;
|
||||
// offset std::vector<TurnEvents> _Myfirst (element stride 0x18; _Mylast +0x08, _Myend +0x0c, _Alval +0x10) [verified]
|
||||
constexpr uint32_t EventStorage_off_Events = 0x00000004;
|
||||
// offset int EvNxID -- next event id. Starts at 0; PostEvent promotes 0->1 on the first post, then post-increments. ServerPlayer+0x2b0 [verified]
|
||||
constexpr uint32_t EventStorage_off_EvNxID = 0x00000014;
|
||||
// offset sizeof(EventStorage) [verified]
|
||||
constexpr uint32_t EventStorage_sizeof = 0x0000001c;
|
||||
// offset int EvTurn (after the vptr at +0) [verified]
|
||||
constexpr uint32_t TurnEvents_off_EvTurn = 0x00000004;
|
||||
// offset std::vector<PlayerEvent> _Myfirst (stride 0x74; _Mylast +0x0c, _Myend +0x10, _Alval +0x14) [verified]
|
||||
constexpr uint32_t TurnEvents_off_Events = 0x00000008;
|
||||
// offset sizeof(TurnEvents); the outer vector's stride, from the /24 divide at 0x008853b3 [verified]
|
||||
constexpr uint32_t TurnEvents_sizeof = 0x00000018;
|
||||
// offset int EvEID (after the vptr at +0) [verified]
|
||||
constexpr uint32_t PlayerEvent_off_EvEID = 0x00000004;
|
||||
// offset std::string EvDsc (summary / title), 0x1c bytes [verified]
|
||||
constexpr uint32_t PlayerEvent_off_EvDsc = 0x00000008;
|
||||
// offset std::string EvMsg (body), 0x1c bytes [verified]
|
||||
constexpr uint32_t PlayerEvent_off_EvMsg = 0x00000024;
|
||||
// offset int EvLoc (object id, or 0) [verified]
|
||||
constexpr uint32_t PlayerEvent_off_EvLoc = 0x00000040;
|
||||
// offset float[3] EvPos (default FLT_MAX x3) [verified]
|
||||
constexpr uint32_t PlayerEvent_off_EvPos = 0x00000044;
|
||||
// offset std::string EvImg (event-type name, e.g. "EVENT_RESEARCH_OVERBUDGET"), 0x1c bytes [verified]
|
||||
constexpr uint32_t PlayerEvent_off_EvImg = 0x00000050;
|
||||
// offset int EvAct [verified]
|
||||
constexpr uint32_t PlayerEvent_off_EvAct = 0x0000006c;
|
||||
// offset int EvCID -- ctor sets 0 and PostEvent never writes it [verified]
|
||||
constexpr uint32_t PlayerEvent_off_EvCID = 0x00000070;
|
||||
// offset sizeof(PlayerEvent) = 116. Two independent confirmations: the /116 divide at 0x00825d5f and PostEvent's 'mov [_Mylast-0x70], id' writing EvEID at element+4 [verified]
|
||||
constexpr uint32_t PlayerEvent_sizeof = 0x00000074;
|
||||
// offset prune cutoff = turn - 50; a code constant at 0x00879ec3, not config [verified]
|
||||
constexpr uint32_t EventStorage_PruneWindowTurns = 0x00000032;
|
||||
// data void** -- PlayerEvent vftable. slot0 dtor 0x007694d0, slot1 Serialize 0x00825970, slot2 0x00825ab0. RTTI locator 0x00a80a7c [verified]
|
||||
constexpr uint32_t g_vft_PlayerEvent = 0x00621958;
|
||||
// data void** -- TurnEvents vftable (bucket ctor 0x00884cb0); slot0 is the virtual dtor the pruner calls [verified]
|
||||
constexpr uint32_t g_vft_TurnEvents = 0x0060f07c;
|
||||
// data const char[12][8] -- stride 8: EvEID EvNxID EvTurn Events EvPos EvLoc EvMsg EvImg EvDsc EvCID EvAct sasc [verified]
|
||||
constexpr uint32_t g_EventTagTable = 0x0062bd88;
|
||||
// data float[3] = {0x7f7fffff, 0x7f7fffff, 0x7f7fffff} = FLT_MAX. PlayerEvent's default EvPos. NOT infinity [verified]
|
||||
constexpr uint32_t g_Vector3_Invalid = 0x006f0dc8;
|
||||
// data const char* "EVENT_RESEARCH_OVERBUDGET" -- EvImg pushed at 0x00587b24 [verified]
|
||||
constexpr uint32_t g_str_EVENT_RESEARCH_OVERBUDGET = 0x0060078c;
|
||||
// data const char* "EVENT_RESEARCH_COMPLETE" -- EvImg for progress/cost >= 0.8 [verified]
|
||||
constexpr uint32_t g_str_EVENT_RESEARCH_COMPLETE = 0x00633368;
|
||||
// data const char* "EVENT_RESEARCH_UNDERBUDGET" -- EvImg for progress/cost < 0.8 [verified]
|
||||
constexpr uint32_t g_str_EVENT_RESEARCH_UNDERBUDGET = 0x00633380;
|
||||
// data const char* "EVENT_TEMPERANCE" -- EvImg at 0x008923ae; posted with act=0 so the stored EvAct is 2 [verified]
|
||||
constexpr uint32_t g_str_EVENT_TEMPERANCE = 0x00633340;
|
||||
// data const char* "EVENT_TECHS_UNLOCKED" (length 0x14 pushed at 0x00587eb6) [verified]
|
||||
constexpr uint32_t g_str_EVENT_TECHS_UNLOCKED = 0x00600774;
|
||||
// data const char* "EVENT_NO_RESEARCH" -- EvImg at 0x0089168c [verified]
|
||||
constexpr uint32_t g_str_EVENT_NO_RESEARCH = 0x0063332c;
|
||||
// data const char** -> slot 0x00ae48e0; Strings.csv key EVENTSUM_RESEARCH_OVERBUDGET = "Research Over Budget" [verified]
|
||||
constexpr uint32_t g_ptr_EVENTSUM_RESEARCH_OVERBUDGET = 0x006e48e4;
|
||||
// data const char** -> slot 0x00ae48e8; key EVENTMSG_RESEARCH_OVERBUDGET = "Research for %s has gone overbudget." [verified]
|
||||
constexpr uint32_t g_ptr_EVENTMSG_RESEARCH_OVERBUDGET = 0x006e48ec;
|
||||
// data const char** -> slot 0x00ae48f0; key EVENTSUM_UNLOCKEDTECHS = "New Technologies Available" [verified]
|
||||
constexpr uint32_t g_ptr_EVENTSUM_UNLOCKEDTECHS = 0x006e48f4;
|
||||
// data const char** -> slot 0x00ae48f8; key EVENTMSG_UNLOCKEDTECHS [verified]
|
||||
constexpr uint32_t g_ptr_EVENTMSG_UNLOCKEDTECHS = 0x006e48fc;
|
||||
// data const char** -> slot 0x00af09e8; key EVENTSUM_RESEARCH_COMPLETE = "Research Complete" [verified]
|
||||
constexpr uint32_t g_ptr_EVENTSUM_RESEARCH_COMPLETE = 0x006f09ec;
|
||||
// data const char** -> slot 0x00af09f0; key EVENTMSG_RESEARCH_COMPLETE = "Tech %s has been acquired" [verified]
|
||||
constexpr uint32_t g_ptr_EVENTMSG_RESEARCH_COMPLETE = 0x006f09f4;
|
||||
// data const char** -> slot 0x00af09f8; key EVENTSUM_RESEARCH_UNDERBUDGET = "Research Breakthrough!" [verified]
|
||||
constexpr uint32_t g_ptr_EVENTSUM_RESEARCH_UNDERBUDGET = 0x006f09fc;
|
||||
// data const char** -> slot 0x00af0a00; key EVENTMSG_RESEARCH_UNDERBUDGET [verified]
|
||||
constexpr uint32_t g_ptr_EVENTMSG_RESEARCH_UNDERBUDGET = 0x006f0a04;
|
||||
// data const char** -> slot 0x00af0a88; key EVENTSUM_ADDICTION_TEMPERENCE (shipped misspelling) [verified]
|
||||
constexpr uint32_t g_ptr_EVENTSUM_ADDICTION_TEMPERENCE = 0x006f0a8c;
|
||||
// data const char** -> slot 0x00af0a90; key EVENTMSG_ADDICTION_TEMPERENCE [verified]
|
||||
constexpr uint32_t g_ptr_EVENTMSG_ADDICTION_TEMPERENCE = 0x006f0a94;
|
||||
// data double 0.800000011920929 = (double)0.8f. progress/cost >= this -> EVENT_RESEARCH_COMPLETE, else EVENT_RESEARCH_UNDERBUDGET; the same constant gates TechNode.flag=0 in ProcessResearch [verified]
|
||||
constexpr uint32_t g_dbl_ResearchUnderbudgetThreshold = 0x005e20c8;
|
||||
// data double 0.0 -- the research completion draw is NextFloat()*(1.0-this)+this, so it is a plain NextFloat() [verified]
|
||||
constexpr uint32_t g_dbl_ResearchRollBias = 0x005e1e68;
|
||||
|
||||
} // namespace sots::addr
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue