re: the bark firing sites and the Probability roll
36 of the 64 event names are executable strings, so the string-xref join names the eight sites that raise them; all eight share one entry, sub_8220FA98. Probability is a per-cent roll -- the scaling constant at 0x820856F8 is exactly 100.0. sub_82210670 suppresses an already-queued (speaker, rule) pair, treats a message as eligible only if its bit is clear in the runtime mask at +36, and picks uniformly, then hands Pattern/Priority/EffectiveTime to sub_822109B0. Measured negative: the pick path never reads +20, +24 or +32. 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,14 +265,18 @@ can never reach, because it skips the record first. Thirteen dead records out of
|
||||
|
||||
### 🟡 What this does *not* settle
|
||||
|
||||
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
|
||||
`Probability` and the line pick are settled above. **Still open: everything
|
||||
after the hand-off to `sub_822109B0`** — how `Priority` orders the queue, whether
|
||||
`Interval` is a cooldown per event or per speaker, what `IntervalFluctuation`
|
||||
randomises, and where `+36`'s bits are set. `+32` (the disc `Yes` mask) is still
|
||||
read by nothing that has been identified.
|
||||
|
||||
🔴 One handle was tried and refuted: searching the whole image for functions
|
||||
that touch `+16`/`+17`/`+18`/`+20`/`+28`/`+32`/`+36` returns dozens of unrelated
|
||||
matches, because those offsets recur across unrelated layouts. **Offset shape is
|
||||
not an identifier** — the call graph is. The bounded version of the same search,
|
||||
restricted to this compilation unit *after* the call graph had established which
|
||||
functions matter, is sound and is what pinned `+17`/`+18`. `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.
|
||||
|
||||
@@ -330,6 +334,76 @@ 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.
|
||||
|
||||
## ✅ Who fires a bark, and the first half of the policy
|
||||
|
||||
**Settled 2026-08-27**, reached through the **call graph**, not through offset
|
||||
shape (that route is refuted — see the note below).
|
||||
|
||||
### The event names are executable strings, so the firing sites name themselves
|
||||
|
||||
**36 of the 64 event names ship as strings** in `0x820AEEC8`…`0x820B2160`, and the
|
||||
functions referencing them are the sites that raise each event:
|
||||
|
||||
| site | events it raises | reading |
|
||||
|---|---|---|
|
||||
| `sub_823800A8` | 17 — `DAMAGED*`, `HP_LESS_*`, `KILLED*`, `KILL_UNIT`, `LOST_{BRIDGE,SHIELD,THRUSTER,TURRET}_BY_PLAYER`, `SHIELD_DOWNED*`, `ATTACK_SUCCEEDED` | damage / destruction |
|
||||
| `sub_82320B48` | 15 — all twelve `ORDER_`/`ORDOR_` names plus `COUNTER`, `COUNTER_TO_PLAYER`, `FOUND_PLAYER` | squadron orders |
|
||||
| `sub_8237B020` | 9 — `PLAYER_HP_LESS_*`, `KILL_PLAYER`, `DAMAGED_TO_PLAYER`, `AVOIDED_BY_PLAYER`, `DAMAGED`, `KILLED`, `SHIELD_DOWNED` | player state |
|
||||
| `sub_823B1438` | `PLAYER_AMMO_LESS_10/40` | ammunition |
|
||||
| `sub_82381B10` | `AVOID_ATTACK`, `AVOIDED_PLAYER_ATTACK` | evasion |
|
||||
| `sub_82398CC0` / `sub_823969D0` / `sub_82362F18` | `ATTACK_IS_AVOIDED` / `EXTENDED` / `FOUND_PLAYER` | one each |
|
||||
|
||||
[`squadron-orders.md`](squadron-orders.md) already names `sub_82320B48` as the
|
||||
selector for the twelve-string enum and warns it is a comms axis. **What it does
|
||||
not say** is that the function is one of eight *bark firing sites* that share a
|
||||
single entry point, and that it also raises three non-order events.
|
||||
|
||||
### `sub_8220FA98` — the shared entry, and the `Probability` roll
|
||||
|
||||
All eight sites call it, and nothing else in the file does more. It takes a lock
|
||||
around `this+4`, bails unless the enable byte at `[this+8843]` is set, then:
|
||||
|
||||
1. looks the **speaker** up in the map at `[this+8820]`;
|
||||
2. indexes a vector inside that entry by `[this+8816] − 1` (a byte; the natural
|
||||
reading is the phase, but that is a reading, not measured);
|
||||
3. looks the **event name** up in *that* entry's map → the 40-byte rule object;
|
||||
4. rolls: `f0 = rand01 · 100.0`, and **if `f0 > [rule+16]` it returns without
|
||||
firing.**
|
||||
|
||||
✅ **`Probability` is a straight per-occurrence percentage.** The scaling
|
||||
constant at `0x820856F8` is exactly **100.0**, read from the image — which is
|
||||
what turns the disc census (`1,2,3,5,10,20,25,30,40,50,60,100`) from "looks like
|
||||
per cent" into per cent. `sub_8220D970` builds the uniform: two `rand()` calls
|
||||
folded into 16 bits, converted to float.
|
||||
|
||||
### `sub_82210670` — pick a line
|
||||
|
||||
Called only when the roll passes. In order:
|
||||
|
||||
1. **Duplicate suppression.** It walks the list at `[this+8736]` for a node whose
|
||||
`+8` and `+12` match this (speaker, rule) pair; if one is pending it returns
|
||||
immediately. The same bark cannot be queued twice.
|
||||
2. **Eligibility.** `MessageCount` is recomputed from the vector
|
||||
(`([rule+8] − [rule+4]) >> 2`), and a message is eligible only if its bit is
|
||||
**clear** in the mask at **`[rule+36]`** — the word the loader zeroes at
|
||||
construction. So `+36` is a runtime *exclusion* mask over the message list;
|
||||
"already spoken" is the obvious reading, and the write site was not found this
|
||||
iteration.
|
||||
3. **Uniform pick.** `f0 = eligible_count · rand01`, then walk the eligible bits
|
||||
subtracting `1.0` (the constant at `0x8208583C`) until the counter runs out or
|
||||
`f0` drops to `0.0` (`0x8209FD28`). That index is the chosen line.
|
||||
4. **Hand-off.** It reads `[rule+18]` `Pattern`, `[rule+17]` `Priority` and
|
||||
`[rule+28]` `EffectiveTime`, and passes them with the chosen message to
|
||||
`sub_822109B0`.
|
||||
|
||||
✅ **A measured negative worth having:** the pick path reads `+17`, `+18`, `+28`
|
||||
and `+36` — and **not** `+20` (`Interval`), `+24` (`IntervalFluctuation`) or
|
||||
`+32` (the disc `Yes` mask). Those three are consumed somewhere after the
|
||||
hand-off, not on the way in. Within the whole chatter compilation unit
|
||||
(`0x8220C000`…`0x82216000`), `+17` and `+18` are read by exactly two functions —
|
||||
`sub_82213840` (the comparator) and `sub_82210670` — which is what makes this
|
||||
attribution safe where a binary-wide offset search is not.
|
||||
|
||||
## 🟡 Not settled
|
||||
|
||||
* The *policy* the seven fields drive — see "what this does not settle" above.
|
||||
|
||||
Reference in New Issue
Block a user