re: the chatter phase merge is not additive; 224 authored variants never play
sub_82215A58 reads CrewCount + PresetMessage_Phase1/2/3 and reaches the loader from ONE call site, so the three phase tables fold into one map keyed by the event record name. Every merge collides; sub_82213840 reconciles on the message list plus +16/+17/+18/+20/+24/+28 (NOT the +32 Yes mask), and the incumbent always wins. Measured: 26432 collisions, 26208 identical, 224 different (189 differ only in the message list), 0 mask-only differences. Refuted handle: intersecting functions by the object's offsets finds dozens of unrelated layouts -- offset shape is not an identifier. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE
This commit is contained in:
@@ -265,13 +265,71 @@ can never reach, because it skips the record first. Thirteen dead records out of
|
||||
|
||||
### 🟡 What this does *not* settle
|
||||
|
||||
The loader tells us the units and the storage, not the policy. `Priority`'s
|
||||
comparison rule, whether `Interval` is a cooldown per event or per speaker, and
|
||||
what `IntervalFluctuation` randomises against are all in the *consumer* of the
|
||||
40-byte object, which this iteration did not read. `Pattern`'s two live values
|
||||
The loader and the merge tell us units, storage and duplicate handling — not
|
||||
the *firing* policy. `Priority`'s comparison rule, whether `Interval` is a
|
||||
cooldown per event or per speaker, and what `IntervalFluctuation` randomises
|
||||
against all live in the consumer of the map, reachable via
|
||||
`sub_82215A58` ← `sub_82214050`. 🔴 One handle was tried and refuted: searching
|
||||
for functions that touch `+16`/`+17`/`+18`/`+20`/`+28`/`+32`/`+36` returns
|
||||
dozens of unrelated matches, because those offsets recur across unrelated object
|
||||
layouts. Offset shape is not an identifier. `Pattern`'s two live values
|
||||
are named, but that `Window` means an on-screen text window is a reading of the
|
||||
name, not something measured.
|
||||
|
||||
## ✅ How the three phase tables are merged — and what that costs
|
||||
|
||||
**Settled 2026-08-27.** `sub_82215A58` is the `UnitMessageSet` reader: it
|
||||
references exactly `CrewCount`, `PresetMessage_Phase1`, `PresetMessage_Phase2`
|
||||
and `PresetMessage_Phase3`, and it reaches the loader from **one** call site
|
||||
(`0x82215D98`). So a crew's three phase tables are loaded into **one** map, not
|
||||
three. The map is keyed by a hash of the *event record's name*
|
||||
(`sub_82455C78`, the rolling-accumulator / character-sum-top-byte shape
|
||||
[`idxd-tag-hash.md`](idxd-tag-hash.md) documents; which member of that family
|
||||
this entry point is was not pinned here, and the finding does not depend on it).
|
||||
|
||||
Because all 64 event names recur in every phase table, **every merge collides**.
|
||||
`sub_82213980`'s insert therefore has a reconciliation arm, and
|
||||
`sub_82213840` — a function called from nowhere else — is its comparator.
|
||||
|
||||
### `sub_82213840` compares six of the eight fields
|
||||
|
||||
It walks the two message-id vectors elementwise, then compares `+16`
|
||||
(`Probability`), `+17` (`Priority`), `+18` (`Pattern`), `+20` (`Interval`),
|
||||
`+24` (`IntervalFluctuation`) and `+28` (`EffectiveTime`), and returns a bool.
|
||||
It does **not** look at `+32` (the `Yes` mask) or `+36`.
|
||||
|
||||
On a colliding key the loader compares, and then — whatever the answer —
|
||||
**frees the newcomer and keeps the incumbent**. A mismatch additionally clears
|
||||
the byte the loader returns (`lbz r3, 80(r31)` at the epilogue); the caller
|
||||
turns that into a flag at `0x82215D9C`…`0x82215DDC` and propagates it.
|
||||
|
||||
### What the disc actually does with that
|
||||
|
||||
Measured over the 22 `UnitMessageSet_S<NN>.tbl` (numbers in the artefact):
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| `UnitMessageSet` records naming ≥ 2 phase tables | **298** |
|
||||
| duplicate-key insertions to reconcile | **26 432** |
|
||||
| identical on the compared fields | **26 208** (99.15 %) |
|
||||
| **different** — loader clears its flag | **224** |
|
||||
| compared fields equal but `Yes` mask differs | **0** |
|
||||
|
||||
Of the 224, **189 differ only in the message list** — per-phase dialogue
|
||||
variants for the same event and the same timings. The other 35 also move
|
||||
`Probability`/`Priority`/`Pattern`/`Interval`.
|
||||
|
||||
✅ **The phase tables are not additive.** For a given crew and event, phase 1's
|
||||
rule wins and the later phases' are discarded — so **224 authored variants never
|
||||
play**. A port that merges them additively, or that lets a later phase override,
|
||||
will not behave like the game.
|
||||
|
||||
✅ **The comparator's blind spot is never exercised.** There is no record pair
|
||||
that agrees on the six compared fields and disagrees on the `Yes` mask, so the
|
||||
fact that `+32` is excluded from the comparison changes nothing on the shipped
|
||||
disc. Worth recording precisely because it is the kind of gap that looks like a
|
||||
latent bug until it is counted.
|
||||
|
||||
## 🟡 Not settled
|
||||
|
||||
* The *policy* the seven fields drive — see "what this does not settle" above.
|
||||
|
||||
Reference in New Issue
Block a user