re: fix record labelling and measure the stride; confirm 116 records == roster
Both defects from the previous iteration are fixed by measuring instead of assuming, and the fix immediately promotes a 🟡 result to ✅. Labelling: the previous probe assumed object+0x04 -> name_record+0x10 -> char* and resolved 0 of 116. wave3_probe.py searches for the chain per record instead, the way unit_discover.py does, and resolves 116 of 116 -- every one through the pointer at +0x04 with the string at delta 0x00, not 0x10. The 0x10 belongs to the definition object (vtable 0x820af844); the spawned-entity record (0x820af030) uses 0x00. Carrying one over to the other cost the last run. Stride: measured, not assumed. Gaps between consecutive records are min 32, median 800, with common values 800, 640, 608, 576, 416 and 32. There is no fixed record size, so the old RECLEN=0x200 window truncated large records and overran small ones -- which is why its busiest fields were the last words of the window. Future diffs must bound each record by the next record's address. With labels available, the "116 records == 116 roster members" claim was tested properly and is promoted from 🟡 to ✅. The multiset of unit types matches the static roster exactly: Turret 21/21, e106 Destroyer 19/19, f106 Destroyer 14/14, f105 Cruiser 11/11, ASFrigate 9/9, ISCMissile 9/9, Attacker_S 9/9, e105 Cruiser 7/7, DeltaSaber_T 7/7, ArrowHead 6/6 -- 10 of 10 exact. A coincidental total is possible; a coincidental distribution over ten unit types is not. The game allocates one record per roster member at mission load. Not settled: REMAINING OB at 0xbdb59668 held 95748078 unchanged all run. That address is known to be run-dependent, and this was one of the misses, so the run cannot say whether the pilot killed anything. Re-hunting it is a precondition for the kill-versus-no-kill test, not an optional extra.
This commit is contained in:
@@ -47,7 +47,33 @@ Perfectly flat. No steps at 90, 120, 170, 210 or anywhere else. The hypothesis
|
||||
that a live entity count would step up at the timetable's offsets is **refuted
|
||||
for this proxy** — but the reason matters more than the refutation.
|
||||
|
||||
## 🟡 116 records, 116 roster members
|
||||
## ✅ 116 records, 116 roster members — confirmed by composition (2026-08-24)
|
||||
|
||||
**Promoted from 🟡 to ✅.** The section below argued from a single number,
|
||||
116 = 116, and explicitly refused to promote it on that. It is now confirmed on
|
||||
a far stronger test: with the records labelled (see the labelling section at the
|
||||
end of this file), the **multiset of unit types** matches the static roster
|
||||
exactly, not just the total.
|
||||
|
||||
| unit | static roster | live records |
|
||||
|---|---|---|
|
||||
| `UN_e007_ADAN_Turret` | 21 | 21 |
|
||||
| `UN_e106_ADAN_Destroyer` | 19 | 19 |
|
||||
| `UN_f106_TCAF_Destroyer` | 14 | 14 |
|
||||
| `UN_f105_TCAF_Cruiser` | 11 | 11 |
|
||||
| `UN_e108_ADAN_ASFrigate` | 9 | 9 |
|
||||
| `UN_e201_ADAN_ISCMissile` | 9 | 9 |
|
||||
| `UN_e010_ADAN_Attacker_S` | 9 | 9 |
|
||||
| `UN_e105_ADAN_Cruiser` | 7 | 7 |
|
||||
| `UN_f001_TCAF_DeltaSaber_T` | 7 | 7 |
|
||||
| `UN_f003_TCAF_ArrowHead` | 6 | 6 |
|
||||
|
||||
10 of 10 exact, across counts from 21 down to 6. A coincidental total can happen;
|
||||
a coincidental *distribution* over ten unit types cannot. The game allocates one
|
||||
entity record per `UnitGroup` roster member at mission load, and every one of
|
||||
them exists from the first sample — long before the member could have "arrived".
|
||||
|
||||
## 🟡 116 records, 116 roster members (original argument, superseded above)
|
||||
|
||||
`UnitGroup_S02.tbl` has 111 squadrons whose `Count` fields sum to **116**
|
||||
members. The entity table holds **116** records, from the first sample onwards.
|
||||
@@ -245,3 +271,45 @@ the blocker already recorded above.
|
||||
A cheaper precondition worth checking first: whether `REMAINING OB` at
|
||||
`0xbdb59668` moves in Run B but not Run A. That is a known-good counter and
|
||||
needs no new decoding.
|
||||
|
||||
|
||||
---
|
||||
|
||||
# Labelling the records, and the real stride (2026-08-24)
|
||||
|
||||
Status: ✅ both defects from the previous section are fixed and measured.
|
||||
|
||||
## ✅ The id chain: `record+0x04` → pointer → `+0x00`
|
||||
|
||||
The previous probe assumed `object+0x04 → name_record+0x10 → char*` and resolved
|
||||
**0 of 116**. `tools/re-capture/wave3_probe.py` searches for the chain instead of
|
||||
assuming one — for each record it walks the first 24 words, treats any
|
||||
guest-range word as a pointer, chases it, and accepts the result only if it
|
||||
lands on a `UN_`/`NP_`-prefixed string, optionally through one more indirection.
|
||||
|
||||
Result: **116 of 116 resolved**, every one by the same chain — pointer at
|
||||
`+0x04`, string at delta **`0x00`**, not `0x10`. The `0x10` in
|
||||
[structures/unit-struct-runtime.md](structures/unit-struct-runtime.md) is the
|
||||
delta for the *definition* object (vtable `0x820af844`); the spawned-entity
|
||||
record (`0x820af030`) uses `0x00`. Assuming one from the other is what cost the
|
||||
previous run.
|
||||
|
||||
## ✅ The stride is variable — `0x200` was wrong
|
||||
|
||||
Measured gaps between consecutive record addresses: **min 32, median 800**, with
|
||||
common values 800, 640, 608, 576, 416 and 32. There is no fixed record size, so
|
||||
the previous probe's `RECLEN = 0x200` window both truncated large records and
|
||||
ran past small ones into their neighbours — which is exactly why its busiest
|
||||
"fields" were the last words of the window.
|
||||
|
||||
Any future diff must bound each record by the *next* record's address rather
|
||||
than by a constant.
|
||||
|
||||
## ❔ `REMAINING OB` at `0xbdb59668` did not read as a counter this run
|
||||
|
||||
It held `95748078` for the whole run, unchanging. That address is known to be
|
||||
run-dependent ([structures/mission-objective-counter.md](structures/mission-objective-counter.md)
|
||||
records it recurring in about 5 runs of 7), and this was one of the misses, so
|
||||
the run cannot say whether the pilot killed anything. Re-hunting the counter
|
||||
with `ob_hunt.py` is a precondition for the kill-versus-no-kill test, not an
|
||||
optional extra.
|
||||
|
||||
Reference in New Issue
Block a user