From 5976ca1bb9c8a8126d9ba3c32feb0ad968ccaddb Mon Sep 17 00:00:00 2001 From: InauguralPhysicist Date: Mon, 3 Aug 2026 05:25:30 -0500 Subject: [PATCH] Pin v0.35.1, drop the record_history opt-out, re-point planted fault 1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit EigenScript#829 (v0.35.1) bounds the temporal assignment history by program TEXT and arms it per NAME, so the two things orbit.eigs was defending against — an unbounded append-only table, and one dead-code `prev of` in physics.eigs arming it for the whole program — are gone. - .devcontainer/Dockerfile: EIGS_REF v0.35.0 -> v0.35.1 (the only pin site). Preflighted first: the whole suite run against a worktree build of the v0.35.1 tag before the bump, not against `--version`. - orbit.eigs `_run`: the `record_history of 0` / `record_history of hist_was` pair and its comment block are gone. Peak RSS is unchanged by the removal — 134.3 / 134.5 / 134.4 MB with the opt-out, 134.7 / 134.3 / 134.4 MB without, at 30 / 100 / 300 bifurcation frames. The point of the change is the gate, not the cleanup. Planted fault 1 flipped that opt-out back on and required the memory gate to reject the build; on v0.35.1 it no longer produces any growth — measured, not assumed: the same planted tree runs 304 MB at 30 frames and 674 MB at 100 on v0.35.0, and 134.5 / 134.9 / 134.8 MB dead flat on v0.35.1. It sailed through the gate, which is a self-test going green for the wrong reason. So fault 1 is re-pointed rather than deleted: it now makes the bifurcation tick KEEP the frame's plotted points, breaking the same invariant the view's headline comment claims (bifurcation mode does no per-frame work) and re-creating the class the gate exists for. It leaves every structural counter correct — built=1, series=1, points=4000, markers=4 — so only the memory curve can see it, and it fails BOTH RSS checks: 176,384 KB at 30 frames, 279,104 KB at 100, i.e. the 200 MB ceiling on the second rung and +102,720 KB against the 25,600 KB flatness limit. Suite green on v0.35.1: lint (17 files), smoke, lab, orbit_hist, bifurcation, orbit_oracle, orbit_mouse, bif_mem. Closes #24 --- .devcontainer/Dockerfile | 2 +- FINDINGS.md | 20 ++++++++++----- README.md | 9 +++++-- orbit.eigs | 28 ++++++++------------ tests/test_bif_mem.sh | 55 ++++++++++++++++++++++++++++++++-------- 5 files changed, 78 insertions(+), 36 deletions(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 18b38a2..a892478 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -44,7 +44,7 @@ RUN apt-get update \ # Full EigenScript source at the pinned tag; build the gfx variant (the # binary hard-links to src/eigenscript, which finds lib/ next to it). -ARG EIGS_REF=v0.35.0 +ARG EIGS_REF=v0.35.1 RUN git clone --depth 1 --branch "${EIGS_REF}" \ https://github.com/InauguralSystems/EigenScript.git /opt/eigenscript \ && make -C /opt/eigenscript gfx CC=gcc diff --git a/FINDINGS.md b/FINDINGS.md index fd8bd51..c9f151f 100644 --- a/FINDINGS.md +++ b/FINDINGS.md @@ -269,12 +269,20 @@ non-numeric coercion of the same builtin, and its "non-findings" note that `prev` works "provided `record_history` is never called" is now qualified — `prev` works, and arms an unbounded table for the whole program while doing it. -**In this repo:** `orbit.eigs`'s `_run` calls the documented `record_history of 0` -for the lifetime of a window session (a lab window asks no temporal questions) -and restores the previous setting on exit. `tests/test_bif_mem.sh` carries a -planted fault that removes that call and must go red, so when #827 lands the -opt-out can come out and the gate still holds. For -`eigenscript-runtime-engineer` / `eigenscript-trace-tape-engineer`. +**FIXED upstream** by EigenScript#829, shipped in **v0.35.1** (the pin this repo +now carries): the history table is bounded by program TEXT — entries no backward +query can reach are pruned at append time — and arming is per NAME instead of +whole-program, so `physics.eigs`'s dead-code `prev of` arms nothing here. + +**In this repo:** `orbit.eigs`'s `_run` used to call `record_history of 0` for +the lifetime of a window session and restore it on exit; that opt-out is gone +(dynamics#24). Removing it changed peak RSS by nothing measurable — 134.3 / +134.5 / 134.4 MB with it, 134.7 / 134.3 / 134.4 MB without, at 30 / 100 / 300 +frames. The same tree with history explicitly ON measures 304 MB at 30 frames +and 674 MB at 100 on v0.35.0, and 134.5 / 134.9 / 134.8 MB flat on v0.35.1 — +which is why `tests/test_bif_mem.sh`'s planted fault 1 could no longer be +planted and was re-pointed at a per-frame retention fault instead of deleted. +For `eigenscript-runtime-engineer` / `eigenscript-trace-tape-engineer`. ## F-DYN-14 — lib/ui: `chart` allocates a list per plotted point per frame → upstream EigenScript#828 diff --git a/README.md b/README.md index 0bdc7fb..39b97e1 100644 --- a/README.md +++ b/README.md @@ -132,14 +132,19 @@ is untouched): by two planted faults. This is not decoration: the first working build of this view grew **3.9 MB per frame** and died at 859 MB, and every correctness oracle stayed green the whole time. The cause was a runtime bug, not the - sweep — see FINDINGS F-DYN-13 / EigenScript#827. + sweep — see FINDINGS F-DYN-13 / EigenScript#827, fixed upstream in v0.35.1. + That fix retired one of the planted faults (the bug it recreated no longer + reproduces), so the fault was re-pointed at a per-frame retention leak rather + than dropped: a gate validated by one fewer fault is a gate that has quietly + stopped discriminating. Forcing-function findings (runtime gaps surfaced while building) are logged in [FINDINGS.md](FINDINGS.md) — most have graduated to upstream fixes (#255/#256/#280/#375, and #819/#820 which closed the two lib/ui plot gaps); a calling-convention edge remains open, and building this rung surfaced two more: EigenScript#827 (unbounded temporal assignment history — the one that -froze a box) and #828 (chart render allocation). +froze a box, fixed by #829 and shipped in v0.35.1, the pin this repo runs) and +#828 (chart render allocation). ## Develop locally diff --git a/orbit.eigs b/orbit.eigs index d9d43e2..038a560 100644 --- a/orbit.eigs +++ b/orbit.eigs @@ -688,23 +688,18 @@ define _build(zeta) as: # dumps its trajectory, so it keeps every point (cap 0). # ---- the window entry point ------------------------------------------ # -# WHY THIS TURNS OFF ASSIGNMENT HISTORY. `record_history of 0` is the -# runtime's documented control for per-assignment temporal history — the -# append-only table `prev of x` and ` is x at ` read. The -# compiler arms that table for the WHOLE PROGRAM as soon as it sees a -# `prev of` anywhere in the source, including in a function that is -# never called; physics.eigs has exactly one, in the `frame_velocity` -# demo, which this window never invokes. The table is append-only with -# no cap and it holds a REFERENCE to every assigned value, so an -# unbounded loop over it — a 60 fps render — grows without bound: the -# bifurcation view measured 3.9 MB per frame, 859 MB by frame 300, and -# then died on the memory cap. Minimal repro and the upstream issue: -# EigenScript#827 (F-DYN-13). A lab window asks no temporal questions, -# so it opts out for the lifetime of the session and restores the -# previous setting on the way out; the observer predicates the side -# panel shows are live slot reads and are NOT affected. +# This used to call `record_history of 0` for the lifetime of a session, +# because the runtime armed its per-assignment temporal history for the +# WHOLE PROGRAM on sighting one `prev of` anywhere — physics.eigs has +# exactly one, in the never-called `frame_velocity` demo — and the table +# was append-only with no cap, so a 60 fps render grew without bound +# (3.9 MB/frame here; F-DYN-13, EigenScript#827). EigenScript#829, in +# v0.35.1, bounds that table by program TEXT and arms it per NAME, so +# neither half of the problem exists any more and the opt-out is gone. +# Removing it moved peak RSS by nothing measurable (134 MB either way at +# 30 / 100 / 300 frames); tests/test_bif_mem.sh is what keeps that +# honest, on every run. define _run(zeta, frames, out_path, mode, ticks) as: - local hist_was is record_history of 0 _app.sim is new_sim of zeta if frames < 0: _app.sim.cap is HIST_CAP @@ -726,7 +721,6 @@ define _run(zeta, frames, out_path, mode, ticks) as: gfx_open of [WIN_W, WIN_H, "dynamics - orbit lab"] ui.app_loop of [root, _on_key, _tick] gfx_close of null - record_history of hist_was return null define run_session(zeta, frames, out_path) as: diff --git a/tests/test_bif_mem.sh b/tests/test_bif_mem.sh index 8de23bb..a126b31 100755 --- a/tests/test_bif_mem.sh +++ b/tests/test_bif_mem.sh @@ -34,9 +34,11 @@ # frozen workstation, and it is the reason this gate could be developed # at all. # -# The gate is validated with TWO planted faults: one restoring the actual -# leak that was found (the temporal-history opt-out removed), one -# removing the build-once guard. Each must be caught. +# The gate is validated with TWO planted faults: one that retains the +# frame's plotted points (per-frame retention, the class the original +# leak belonged to), one that removes the build-once guard. Each must be +# caught, and each is measured — a fault nobody has watched go red is a +# gate that has stopped discriminating. # # Exits 2 (= failure in CI, skip locally) when the runtime has no gfx # builtins, so it can never be silently dropped. @@ -197,17 +199,50 @@ PY_END echo "$tree" } -echo "--- planted fault 1: the temporal-history opt-out removed (the real bug) ---" -# physics.eigs contains one `prev of`, which arms the runtime's -# append-only assignment history for the whole program; the chart's -# per-point allocations are then pinned, at ~3.9 MB per frame. The gate -# must reject this on its FIRST rung. EigenScript#827. -T1=$(plant hist " local hist_was is record_history of 0" " local hist_was is record_history of 1") +echo "--- planted fault 1: the bifurcation tick keeps its frame's points ---" +# The headline invariant of this view is that bifurcation mode does NO +# per-frame work (orbit.eigs `_tick`). This fault breaks exactly that: +# every frame rebuilds the plotted point set and KEEPS it — the shape any +# naive per-frame cache (a pick index, a frame history, an undo trail) +# would have, and the same class as the two retention bugs this repo has +# already had: the unbounded `sim.pts` trail and the pinned per-point +# allocations of F-DYN-13. Every structural counter stays CORRECT under +# it — built=1, series=1, points=4000, markers=4 — so only the shape of +# the memory curve can see it, which is the thing this fault has to +# prove. Measured on v0.35.1: 176,384 KB at 30 frames, 279,104 KB at 100 +# (~1.46 MB/frame) — it fails BOTH RSS checks, the 200 MB ceiling on the +# second rung and the flatness limit by 102,720 KB against 25,600. +# +# WHAT USED TO BE HERE, and why it went. This leg used to flip +# `record_history of 0` — the opt-out `_run` carried against the +# runtime's unbounded temporal history (EigenScript#827) — back on, and +# require the resulting 3.9 MB/frame build to be rejected. EigenScript#829 +# (v0.35.1) bounds that table by program TEXT and arms it per NAME, so +# turning history on grows nothing any more: measured, not assumed, the +# flipped build ran flat at ~134 MB and SAILED THROUGH this gate. The +# opt-out is gone from `_run` and a fault that can no longer be planted +# validates nothing — so it is re-pointed, not deleted (dynamics#24). +T1=$(plant framepts " if _app.mode == \"bif\": + if _app.hook != null: + _app.hook of _app.sim + return null" " if _app.mode == \"bif\": + if _app.frame_pts == null: + _app.frame_pts is [] + local fs is _app.chart.series[0] + local pts is [] + local i is 0 + loop while i < (len of fs.data): + append of [pts, [fs.x[i], fs.data[i]]] + i is i + 1 + append of [_app.frame_pts, pts] + if _app.hook != null: + _app.hook of _app.sim + return null") if run_gate "$T1" quiet "static" "30 100"; then echo "FAIL: the leaking build passed the memory gate — the gate can't discriminate" exit 1 fi -echo "PASS: planted fault 'hist' is caught (restores the 3.9 MB/frame growth)" +echo "PASS: planted fault 'framepts' is caught (per-frame retention, every structural counter still correct)" echo "--- planted fault 2: the build-once sweep guard removed ---" T2=$(plant guard " if _app.sweep_done == 1: