package pipeline // wave.go: the W0 (deterministic, $0) pre-compute of the wave runner (WS1 §1б). This is the FIRST piece // of the wave executor built ahead of the driver switch (R1 residual): the sticky-chain memory selection // is a CROSS-CHUNK sequential dependency (a chunk's sticky_prev is the union of the prior chunks' exact // matches in the same chapter), so the parallel waves cannot recompute it per-chunk — it must be // precomputed once in W0. precomputeSticky replicates EXACTLY the sequential driver's inline computation // (bookrun.go's per-chapter reset + memory.Select + sticky advance), and the sequential driver now // CONSUMES it — so it is byte-identical by construction, and the golden test proves the injection bytes // did not move. When the driver switches to waves (residual), the waves consume this same precompute. // // Select is a PURE deterministic function of (chunk, chapter, sticky_prev, budget, frozen-bank) with NO // model output, so precomputing it reproduces byte-for-byte the injection the sequential runner rendered // (parallelism does not change the bytes — the order is fixed and the input carries no model state). // precomputeSticky computes the per-chunk memory selection for EVERY chunk in one ordered W0 pass // (WS1 §1б): the sticky window is reset at each chapter boundary (a new chapter is a scene change) and // advanced by each chunk's exact matches, exactly as the sequential loop did inline. Returns a slice // aligned index-for-index with `chunks`. A nil bank (report path / no glossary) yields zero-valued // selections (no injection), so a caller can index it unconditionally. func precomputeSticky(chunks []Chunk, mem *MemoryBank, budget int) []memorySelection { out := make([]memorySelection, len(chunks)) if mem == nil { return out } var stickyWin []map[string]injectionDisposition prevChapter := 0 for i, ch := range chunks { if ch.Chapter != prevChapter { stickyWin = nil // chapter boundary: reset scene-inertia (A5) prevChapter = ch.Chapter } sel := mem.Select(ch.Text, ch.Chapter, unionSticky(stickyWin), budget) out[i] = sel // Advance the sticky window with THIS chunk's exact matches (keep the last stickyDepth). stickyWin = append(stickyWin, sel.activeIDs) if len(stickyWin) > stickyDepth { stickyWin = stickyWin[len(stickyWin)-stickyDepth:] } } return out }