textmachine/backend/internal/pipeline/wave.go

42 lines
2.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}