From f78565fe9e1b4bcdbcb7cd543bb6d0ba00ac3a88 Mon Sep 17 00:00:00 2001 From: "Claude (auto)" Date: Thu, 27 Aug 2026 17:17:01 +0000 Subject: [PATCH] re: the bark queue, Priority ordering, and Yes = a one-shot line sub_822109B0 allocates 68-byte nodes from a pool capped at 128; the Pattern byte at +23 doubles as the occupancy flag. Priority orders the pending list descending; the >=10 front-push arm is dead because the disc's range is 1..9. The tick sub_8220FC50 does rule[+36] |= (1 << node[+25]) & rule[+32], so a line enters the used mask only if its Yes bit is set: Yes = one-shot, No = repeatable. 388 one-shot lines on the disc. Closes what reads +32. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE --- docs/re/BACKLOG.md | 46 +++++++++++++ docs/re/INDEX.md | 2 +- docs/re/structures/preset-message-rules.md | 79 ++++++++++++++++++++-- 3 files changed, 121 insertions(+), 6 deletions(-) diff --git a/docs/re/BACKLOG.md b/docs/re/BACKLOG.md index b79c685..09e6bf0 100644 --- a/docs/re/BACKLOG.md +++ b/docs/re/BACKLOG.md @@ -4298,3 +4298,49 @@ the unbounded one is what was refuted.** queue, whether `Interval` is a per-event or per-speaker cooldown, what `IntervalFluctuation` randomises, where `+36`'s bits get set, and what reads `+32`. Also still open: which of the 49 speakers are wingmen. + +--- + +## ✅ 2026-08-27 — the bark queue, `Priority`, and `Yes`/`No` settled + +Continues the chatter thread, bounded to the CU as the previous entry proposed. +**Docs checked:** `preset-message-rules.md` (mine). Nothing else in the corpus +mentions `sub_822109B0`, `sub_8220FC50` or `sub_82210AF0`. + +✅ **The pending queue.** `sub_822109B0` allocates 68-byte nodes from a pool at +`this+32`; `[this+8740]` is the count and **the enqueue returns 0 once it reaches +128** — barks past that are dropped. A slot is free iff `[node+23] == 0`, and +`+23` holds **`Pattern`**, which is never 0 for a loaded rule: the presentation +field doubles as the occupancy flag. Node layout recorded in the doc. + +✅ **`Priority` orders the list, and its fast path is dead data-wise.** +Insertion tests `Priority` against **10**: ≥ 10 pushes straight to the front with +no ordering; < 10 splices ahead of the first node with a strictly smaller +`Priority`, leaving the list **descending by `Priority`**. **The disc's range is +1…9**, so the front-push arm never runs on retail data — every shipped bark takes +the ordered path. `sub_82210AF0` returns the largest `[node+20]` among pending +nodes of *higher* priority whose `[node+24] != 0x10`: a preemption/ducking query. + +✅ **`Yes`/`No` is a ONE-SHOT flag, and the `+32`/`+36` pair closes.** In the +per-frame tick `sub_8220FC50` (callers `sub_821AB650`, `sub_821B5FB8`), at +`0x82210160`: + + rule[+36] = ((1 << node[+25]) & rule[+32]) | rule[+36] + +The line just spoken enters the **used** mask **only if its `Yes` bit is set**. +`sub_82210670` picks only from messages whose `+36` bit is clear, so `Yes` = the +line is heard once and never again, `No` = it stays in the pool. That is what the +census counts: **8 940 `No`, 388 `Yes`** ⇒ 388 one-shot lines on the disc. It also +answers the previous entry's open question "what reads `+32`" — nothing, until a +line finishes, and then only to gate this AND. + +✅ The tick branches on `Pattern < 3` (taken side uses `this+8776`), so `Window` +(3) is a different presentation path from `Sound` (1) — the two values the disc +actually ships. + +❔ **Still open, and now it is only two fields:** whether `Interval` (`+20`) is a +cooldown per event, per rule or per speaker, and what `IntervalFluctuation` +(`+24`) randomises against. Neither is read by the entry, pick, enqueue or tick +paths read so far — `sub_82210320` was checked as a candidate and is a different +structure (a deque at `this+8744`, with a divide-by-60 frames→seconds), not the +rule object. Also still open: which of the 49 speakers are wingmen. diff --git a/docs/re/INDEX.md b/docs/re/INDEX.md index cbc2bcb..aa2034d 100644 --- a/docs/re/INDEX.md +++ b/docs/re/INDEX.md @@ -98,7 +98,7 @@ files, which is how the same ground got covered twice. | [`structures/sound-pak-contents.md`](structures/sound-pak-contents.md) | Census of `sound.pak`, and the limit of the leading-region rule | ✅ CONFIRMED, 5 135/5 135 names hash into the TOC, **9 519/9 519** entries accounted for, and a full 4 114-bank manifest (408.3 min of audio) computed from PsuedoBytesPerSec without decoding; ⚠️ leading-region rule holds for 1 571/4 382 eng and 0/5 100 jpn | | [`structures/sound-cue-table.md`](structures/sound-cue-table.md) | The cue index in `tables.pak` — message id -> cue -> sound id -> `.slb` bank | ✅ CONFIRMED, 1 326/1 338 script message ids bind to a bank; SOUNDS and FILES agree on the same 12 absentees, 0 orphan files | | [`structures/cutscene-message-table.md`](structures/cutscene-message-table.md) | Cutscene dialogue — speaker, portrait, on-screen seconds, audio cue per page | ✅ CONFIRMED, field count = 9·PageCount+2 for all 7 PageCounts, 1 252/1 252 caption keys match, 138 ids close both ways | -| [`structures/preset-message-rules.md`](structures/preset-message-rules.md) | The reactive-chatter rule table — 64 events, the (message, Yes/No) pair payload, the `Sperkers` roster | ✅ CONFIRMED, the seven fields read off `sub_82213980`, phase tables merge non-additively (224 variants discarded); 8 firing sites share one entry, Probability is a per-cent roll against rand*100.0 (three are seconds ×60, the Yes/No element is a u32 mask, hence the 32 clamp); MessageCount·2 == positional count in 9 216/9 216; five naming routes union to 144/144 (six names predicted from the speaker roster before hashing, control 0/4); 2 388/2 405 message ids bind to a cue | +| [`structures/preset-message-rules.md`](structures/preset-message-rules.md) | The reactive-chatter rule table — 64 events, the (message, Yes/No) pair payload, the `Sperkers` roster | ✅ CONFIRMED, the seven fields read off `sub_82213980`, phase tables merge non-additively (224 variants discarded); 8 firing sites share one entry, Probability is a per-cent roll against rand*100.0; Yes = one-shot line; queue is Priority-ordered and capped at 128 (three are seconds ×60, the Yes/No element is a u32 mask, hence the 32 clamp); MessageCount·2 == positional count in 9 216/9 216; five naming routes union to 144/144 (six names predicted from the speaker roster before hashing, control 0/4); 2 388/2 405 message ids bind to a cue | | [`structures/isl-mission-timer.md`](structures/isl-mission-timer.md) | The mission timer — two sequential countdowns, not a limit and a warning | ✅ CONFIRMED — read from `sub_822639B8`; the threshold reading is | | [`structures/archive-naming.md`](structures/archive-naming.md) | Which archives the disc can name — 100% for menu paks, **0.0%** for the six 2D paks and `GP_READY_ROOM` | ✅ CONFIRMED — 6027 names × 16 prefixes, bimodal | | [`structures/hud-config.md`](structures/hud-config.md) | The in-game HUD configuration — 16 records, 419 asset paths, and a per-stage `ResourceTable` | ✅ CONFIRMED — 0 of 419 config paths resolve as pak entries | diff --git a/docs/re/structures/preset-message-rules.md b/docs/re/structures/preset-message-rules.md index 09bbe61..d4c7c81 100644 --- a/docs/re/structures/preset-message-rules.md +++ b/docs/re/structures/preset-message-rules.md @@ -265,11 +265,11 @@ can never reach, because it skips the record first. Thirteen dead records out of ### 🟡 What this does *not* settle -`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. +`Probability`, the pick, the queue, `Priority` and the `Yes`/`No` flag are all +settled above. **What remains is only the two interval fields:** whether +`Interval` (`+20`) is a cooldown per event, per rule or per speaker, and what +`IntervalFluctuation` (`+24`) randomises against. Neither is read by the entry, +the pick, the enqueue or the tick paths read so far. 🔴 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 @@ -404,6 +404,75 @@ hand-off, not on the way in. Within the whole chatter compilation unit `sub_82213840` (the comparator) and `sub_82210670` — which is what makes this attribution safe where a binary-wide offset search is not. +## ✅ The queue, `Priority`, and what `Yes`/`No` actually means + +**Settled 2026-08-27** from `sub_822109B0` (enqueue) and `sub_8220FC50` (the +per-frame tick, called from `sub_821AB650` and `sub_821B5FB8`). + +### The pending queue + +`sub_822109B0` allocates from a fixed pool: **68-byte nodes starting at +`this+32`**, with `[this+8740]` as the live count and `[this+8736]` as the list +head. **If the count is already 128 it returns 0 and the bark is dropped.** + +A slot is free iff its byte at `+23` is zero — and `+23` holds **`Pattern`**, +which is never 0 for a loaded rule (the enum is 1…4). The presentation field +doubles as the occupancy flag. + +| node | content | +|---|---| +| `+4` | the chosen message id | +| `+8` | the speaker entry | +| `+12` | the rule object | +| `+16` | `EffectiveTime` (frames) | +| `+20`, `+21` | zero at enqueue | +| `+22` | `Priority` | +| `+23` | `Pattern` — also the in-use flag | +| `+24` | state byte, zero at enqueue | +| `+25` | the chosen message **index** | + +### `Priority` orders the list, and its fast path is dead + +Insertion compares `Priority` against **10**. At or above 10 the node is pushed +straight onto the front with no ordering; below 10 it is spliced ahead of the +first node with a strictly smaller `Priority`, leaving the list **descending by +`Priority`**. **No shipped record reaches 10** — the disc's range is 1…9 — so the +front-push arm never runs on the retail data, and every bark takes the ordered +path. + +`sub_82210AF0` (called only from the tick) walks the list and returns the largest +`[node+20]` among pending nodes whose `Priority` is **higher** than the one being +considered and whose `[node+24]` is not `0x10` — a preemption / ducking query +against higher-priority speech already queued. + +### ✅ `Yes`/`No` is a one-shot flag — the `+32`/`+36` pair closes + +The tick ends a line with exactly this, at `0x82210160`: + +``` +r10 = node[+25] ; the index of the line just spoken +r9 = rule[+32] ; the Yes mask, straight off the disc +r8 = rule[+36] ; the used mask, zeroed by the loader +rule[+36] = ((1 << r10) & r9) | r8 +``` + +The spoken line's bit enters the **used** mask **only if its `Yes` bit is set**. +Since `sub_82210670` treats a message as eligible only when its bit in `+36` is +clear, the meaning is exact: + +* **`Yes` → the line is one-shot.** Once spoken it is permanently removed from + that rule's pool. +* **`No` → the line repeats.** Its bit never enters `+36`. + +That is what the census counts: **8 940 `No` and 388 `Yes`** — 388 lines across +the disc are authored to be heard once. It also closes the earlier open question +"what reads `+32`": nothing until a line finishes, and then only to gate this +one AND. + +The tick also branches on `Pattern < 3` (using `this+8776` on the taken side), so +`Window` (3) is on a different presentation path from `Sound` (1) — consistent +with the two values the disc ships. + ## 🟡 Not settled * The *policy* the seven fields drive — see "what this does not settle" above.