%% SCRAP: architecture/03-architecture/physics-engine/feedback-loops %% SOURCE: docs/working/architecture/03-architecture/physics-engine/feedback-loops.md %% STATUS: CURRENT %% FITS: dev-guide/ch-physics %% EDITORIAL: lifted — prose rewritten to press voice \section{The Seven Feedback Loops} StarForth's adaptive runtime is governed by seven configurable feedback loops. Each loop pairs a positive (accumulation) signal that drives the runtime toward optimization against a negative (stabilization) signal that prevents runaway growth and holds the system at equilibrium. Every loop can be toggled independently through a Makefile flag of the form \texttt{ENABLE\_LOOP\_N\_*}, and the loops are designed to compose cumulatively so that Design of Experiments (DoE) campaigns can measure each increment in isolation. The loops borrow thermodynamic language as a modeling convenience. Using execution frequency as a proxy for thermal energy, the runtime tracks ``heat'' per word, lets it ``decay'' over time, and reasons about ``stability'' in statistical terms. These are descriptive tools, not claims of physical thermodynamics. \begin{table}[ht] \centering \small \begin{tabular}{llll} \toprule Loop & Name & Accumulation signal & Stabilization signal \\ \midrule \#1 & Execution Heat & word execution increments heat & Loop \#3 decay; cold words demoted \\ \#2 & Rolling Window & every execution recorded to buffer & wrap overwrite; adaptive shrinking \\ \#3 & Linear Decay & slope starts at $1/3$ & decay reduces heat; slope adjusts $\pm 5\%$ \\ \#4 & Pipelining & word$\rightarrow$word transitions recorded & counts age out; miss-rate feedback \\ \#5 & Window Inference & window grows with diversity & Levene's test shrinks to minimum \\ \#6 & Decay Inference & regression extracts decay rate & ANOVA early-exit; fit-quality bound \\ \#7 & Adaptive Heartrate & tick counter increments & inference cadence falls when stable \\ \bottomrule \end{tabular} \caption{The seven feedback loops, with their accumulation and stabilization mechanisms.} \end{table} \subsection{Loop \#1 --- Execution Heat Tracking} Loop \#1 counts word usage to identify hot execution paths. Every execution increments \texttt{DictEntry.execution\_heat}, and words crossing the promotion threshold migrate into the \texttt{hotwords\_cache} for constant-time lookup. Stabilization comes from Loop \#3, which decays heat over time, and from cache demotion: words falling below \texttt{HEAT\_CACHE\_DEMOTION\_THRESHOLD}~(10) are evicted. The \texttt{PHYSICS-RESET-STATS} word clears all heat counters. Build flag: \texttt{ENABLE\_LOOP\_1\_HEAT\_TRACKING}. \subsection{Loop \#2 --- Rolling Window History} Loop \#2 captures the execution sequence in a circular buffer (\texttt{execution\_history}) to seed metrics deterministically. The window grows to \texttt{ROLLING\_WINDOW\_SIZE}~(default 4096) before wrapping and becomes ``warm'' after 1024 executions, the point at which it holds representative data. Stabilization is twofold: oldest entries are overwritten on wrap, and adaptive shrinking reduces \texttt{effective\_window\_size} whenever pattern-diversity growth falls below \texttt{ADAPTIVE\_GROWTH\_THRESHOLD}~(1\%). The check runs every \texttt{ADAPTIVE\_CHECK\_FREQUENCY}~(256) executions, retains \texttt{ADAPTIVE\_SHRINK\_RATE}~(75\%) of the window per cycle, and never shrinks below \texttt{ADAPTIVE\_MIN\_WINDOW\_SIZE}~(256). Build flag: \texttt{ENABLE\_LOOP\_2\_ROLLING\_WINDOW}. \subsection{Loop \#3 --- Linear Decay} Loop \#3 ages words to prevent unbounded heat accumulation; it is primarily a negative-feedback loop. Heat decays at \texttt{DECAY\_RATE\_PER\_US\_Q16} ($1/65536$ heat per microsecond), giving a half-life of roughly six to seven seconds for a 100-heat word. The decay slope, stored as a \Qtype{} value (\texttt{decay\_slope\_q48}, initialized to $1/3$), self-adjusts by $\pm 5\%$ against the hot-to-stale word ratio: an excess of hot words increases the slope, an excess of stale words decreases it. A minimum interval \texttt{DECAY\_MIN\_INTERVAL}~($1\,\mu$s) guards against over-decay. Build flag: \texttt{ENABLE\_LOOP\_3\_LINEAR\_DECAY}. \subsection{Loop \#4 --- Pipelining Metrics} Loop \#4 tracks word-to-word transitions to enable speculative prefetch. \texttt{WordTransitionMetrics} records transitions per word and builds context windows of depth \texttt{TRANSITION\_WINDOW\_SIZE}~(default 2) for prediction, while \texttt{prefetch\_attempts} and \texttt{prefetch\_hits} track accuracy. Transition counts age out over time, and a miss-rate signal drives a binary-chop search over window size through \texttt{PipelineGlobalMetrics.suggested\_next\_size}. Build flag: \texttt{ENABLE\_LOOP\_4\_PIPELINING\_METRICS}. \subsection{Loop \#5 --- Window Width Inference} Loop \#5 finds the optimal rolling-window size through statistical testing. The \texttt{effective\_window\_size} expands toward \texttt{ROLLING\_WINDOW\_SIZE} when pattern diversity rises above threshold. For stabilization it applies Levene's test for equality of variance: the heat trajectory is split into $K$ disjoint chunks, each chunk's variance is computed independently, and the test statistic $W$ is compared against the critical value ($W \approx 6.5$ at $\alpha = 0.05$). When $W \le$ critical, the variances are stable and the window has reached its minimum sufficient size, preventing over-capture of redundant pattern data. Build flag: \texttt{ENABLE\_LOOP\_5\_WINDOW\_INFERENCE}. \subsection{Loop \#6 --- Decay Slope Inference} Loop \#6 extracts the optimal decay rate by exponential regression over the heat trajectory. Fitting the log-linear model \begin{equation} \ln(\mathrm{heat}[t]) = \ln(h_0) - \mathrm{slope} \cdot t \end{equation} yields a closed-form \texttt{adaptive\_decay\_slope}. An ANOVA early-exit guards the cost: if \texttt{has\_variance\_stabilized()} reports that variance changed by less than 5\% since the last inference, the computation is skipped and the cached result reused, saving an estimated 5--10k CPU cycles. The slope is bounded by fit quality (\texttt{slope\_fit\_quality\_q48}), and \texttt{inference\_outputs\_validate()} rejects invalid slopes (zero or greater than 100). Build flag: \texttt{ENABLE\_LOOP\_6\_DECAY\_INFERENCE}. \subsection{Loop \#7 --- Adaptive Heartrate} Loop \#7 adjusts tick frequency to balance responsiveness against inference cost. \texttt{tick\_count} increments every heartbeat; the heartbeat thread wakes at \texttt{HEARTBEAT\_TICK\_NS}~(1\,ms default) and samples hot-word count, total heat, and window width. When the system is stable, full inference runs less often, gated by \texttt{HEARTBEAT\_INFERENCE\_FREQUENCY}~(5000 ticks): expensive inference triggers a slower tick cadence. The background \texttt{HeartbeatWorker} thread can be disabled entirely with \texttt{HEARTBEAT\_THREAD\_ENABLED=0}. Build flag: \texttt{ENABLE\_LOOP\_7\_ADAPTIVE\_HEARTRATE}. \subsection{Principal Stabilizers} Five mechanisms carry the bulk of the system's negative feedback: \begin{itemize} \item \textbf{Heat decay} (Loop \#3) --- the primary stabilizer, preventing unbounded heat accumulation. \item \textbf{Window shrinking} (Loops \#2 and \#5) --- a diversity plateau triggers size reduction via Levene's test. \item \textbf{ANOVA early-exit} (Loop \#6) --- variance stability below 5\% change skips expensive inference. \item \textbf{Cache demotion} (Loop \#1) --- words below the demotion threshold leave the hot-words cache. \item \textbf{Slope reversal} (Loop \#3) --- an inverted hot-to-stale ratio adjusts the decay slope by $\pm 5\%$. \end{itemize} \subsection{Configuration} Any loop can be disabled at build time. Setting all seven flags to zero yields the baseline configuration used as the DoE reference point. \begin{lstlisting}[language=bash] # Disable a specific loop make ENABLE_LOOP_3_LINEAR_DECAY=0 # Baseline build (all loops off) make ENABLE_LOOP_1_HEAT_TRACKING=0 \ ENABLE_LOOP_2_ROLLING_WINDOW=0 \ ENABLE_LOOP_3_LINEAR_DECAY=0 \ ENABLE_LOOP_4_PIPELINING_METRICS=0 \ ENABLE_LOOP_5_WINDOW_INFERENCE=0 \ ENABLE_LOOP_6_DECAY_INFERENCE=0 \ ENABLE_LOOP_7_ADAPTIVE_HEARTRATE=0 \end{lstlisting} \begin{table}[ht] \centering \small \begin{tabular}{lll} \toprule Knob & Default & Description \\ \midrule \texttt{ROLLING\_WINDOW\_SIZE} & 4096 & Initial/maximum window size \\ \texttt{ADAPTIVE\_SHRINK\_RATE} & 75 & \% retained when shrinking \\ \texttt{ADAPTIVE\_MIN\_WINDOW\_SIZE} & 256 & Floor for window shrinking \\ \texttt{ADAPTIVE\_CHECK\_FREQUENCY} & 256 & Executions between diversity checks \\ \texttt{ADAPTIVE\_GROWTH\_THRESHOLD} & 1 & Growth rate (\%) signalling saturation \\ \texttt{DECAY\_RATE\_PER\_US\_Q16} & 1 & Heat decay per microsecond (\Qtype{}) \\ \texttt{HEARTBEAT\_INFERENCE\_FREQUENCY} & 5000 & Ticks between full inference runs \\ \texttt{TRANSITION\_WINDOW\_SIZE} & 2 & Context depth for pipelining prediction \\ \bottomrule \end{tabular} \caption{Tuning knobs for the feedback loops.} \end{table} The implementation is distributed across \texttt{src/rolling\_window\_of\_truth.c} (Loop \#2), \texttt{src/dictionary\_heat\_optimization.c} (Loops \#1 and \#3), \texttt{src/inference\_engine.c} (unified Loops \#5 and \#6), \texttt{src/physics\_pipelining\_metrics.c} (Loop \#4), and the \texttt{vm\_tick()} heartbeat dispatcher in \texttt{src/vm.c} (Loop \#7). Knob definitions live in \texttt{include/rolling\_window\_knobs.h}.