re: Interval and IntervalFluctuation are the cooldown; the chatter system closes
sub_82210C38 retires a line and is the only chatter function reading the rule's +20/+24 as words: node[+16] = Fluctuation * rand01 + Interval frames, state 0x20. Interval is a floor, Fluctuation a uniform additive jitter. The queue node IS the cooldown timer -- the tick counts +16 down and only then frees the slot, so the already-queued bail in sub_82210670 and the cooldown are one mechanism: a speaker cannot repeat an event until its node expires. A lingering node still holds one of the 128 pool slots. Also: +20 on the node is a ducking level, raised while a higher-priority line plays and released on retirement. 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,11 +265,12 @@ can never reach, because it skips the record first. Thirteen dead records out of
|
||||
|
||||
### 🟡 What this does *not* settle
|
||||
|
||||
`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.
|
||||
All seven fields and the `Yes`/`No` flag are settled above; the static side of
|
||||
this system is complete. What is **not** established: the numeric effect of the
|
||||
ducking level at `node[+20]` (its mechanism is clear, its attenuation is not),
|
||||
what the state values other than `0x10`/`0x20` mean, and whether
|
||||
`sub_822168E8`/`sub_82217980` do anything beyond stop/resume. None of these
|
||||
change the data model a port needs.
|
||||
|
||||
🔴 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
|
||||
@@ -473,6 +474,64 @@ The tick also branches on `Pattern < 3` (using `this+8776` on the taken side), s
|
||||
`Window` (3) is on a different presentation path from `Sound` (1) — consistent
|
||||
with the two values the disc ships.
|
||||
|
||||
## ✅ `Interval` and `IntervalFluctuation` — the cooldown, and the system closes
|
||||
|
||||
**Settled 2026-08-27** from `sub_82210C38`, the retire-a-line routine (called
|
||||
from the tick `sub_8220FC50` and from `sub_8220FB90`). It is the only place in
|
||||
the chatter code that reads the rule's `+20` and `+24` as **words**, which is
|
||||
what distinguishes them from the byte fields at the same offsets in the queue
|
||||
node.
|
||||
|
||||
When a line finishes:
|
||||
|
||||
```
|
||||
rule = node[+12]
|
||||
if rule:
|
||||
f1 = rand01() ; sub_8220D970
|
||||
f0 = (float)rule[+24] ; IntervalFluctuation, frames
|
||||
f13 = (float)rule[+20] ; Interval, frames
|
||||
node[+16] = (int)(f0 * f1 + f13) ; fmadds
|
||||
else:
|
||||
node[+16] = 0
|
||||
node[+24] = 0x20 ; state: cooling down
|
||||
```
|
||||
|
||||
✅ **`Interval` is a floor and `IntervalFluctuation` is uniform additive jitter**:
|
||||
the cooldown is `Interval + IntervalFluctuation · rand01` frames, i.e. uniform
|
||||
over `[Interval, Interval + IntervalFluctuation)`. On the disc that is
|
||||
`Interval ∈ {0,3,4,5,30,120}` seconds with a jitter of `0` or up to `+2` seconds
|
||||
(813 records carry `IntervalFluctuation 2`).
|
||||
|
||||
✅ **The cooldown is per (speaker, event), and the queue node *is* the timer.**
|
||||
`node[+16]` — the field that held `EffectiveTime` while the line was playing — is
|
||||
reused for the cooldown. In state `0x20` the tick decrements it each frame and,
|
||||
only when it reaches zero, unlinks the node, clears `[node+23]` to free the pool
|
||||
slot and decrements the count:
|
||||
|
||||
```
|
||||
node[+16] -= elapsed
|
||||
if node[+16] > 0: keep waiting
|
||||
else: unlink; node[+23] = 0; [this+8740]--
|
||||
```
|
||||
|
||||
That closes the loop with the duplicate check in `sub_82210670`, which refuses to
|
||||
fire when a node with the same `(speaker, rule)` is already in the list. **The
|
||||
"already queued" bail and the cooldown are the same mechanism**: a retired line
|
||||
keeps its node alive for `Interval + jitter` frames, and for exactly that long the
|
||||
same speaker cannot repeat the same event. Nothing per-rule or global is
|
||||
involved — the rule object itself holds no timestamp.
|
||||
|
||||
⚠️ **Port note:** the lingering node still occupies one of the **128** pool slots.
|
||||
A long `Interval` (120 s = 7 200 frames) parks a slot for two minutes.
|
||||
|
||||
The same routine also stops the presentation — `sub_822168E8` on `this+8776` for
|
||||
`Pattern` 1, or on `node+28` when `Pattern ≥ 3` — and, when `[node+20] == 1`,
|
||||
sweeps the pending list calling `sub_82217980(…, 0)` on every node whose state is
|
||||
not `0x10`. Together with `sub_82210AF0` (which returns the largest `[node+20]`
|
||||
among *higher*-priority pending nodes) that makes `+20` a ducking level: raised
|
||||
while a higher-priority line plays, released when it retires. The exact
|
||||
attenuation is not established here.
|
||||
|
||||
## 🟡 Not settled
|
||||
|
||||
* The *policy* the seven fields drive — see "what this does not settle" above.
|
||||
|
||||
Reference in New Issue
Block a user