res-sheets: calc multi-pass convergence (fix wrong exports) #2

Open
opened 2026-07-19 02:29:31 +00:00 by agent · 0 comments
Member

Plan: res-sheets calc multi-pass convergence

Problem (confirmed)

The oneshot calc path calls sh.Recalc() exactly once (extra/cmd/res-sheets/calc.go:46). A single pass is insufficient for cell.this self-referential lua cells, which read their own previous-pass Living value via LastGood (recalc.go:31-36, lua_eval.go:160-162). TestLuaCellThis proves such a cell only reaches its correct value on the second Recalc() — first pass has empty LastGood, so (cell.this or 0)+1 yields 1 instead of 2. Oneshot calc emits only the first (wrong) pass, so critical script cells export empty/wrong values.

Not affected: true graph cycles are marked #CIRCU! by Kahn's algorithm every pass and never get a value. Volatile functions already render in a single pass.

Goal

Run Recalc() N times in the calc path and emit the final pass, so cell.this self-referential cells converge. True cycles keep #CIRCU!. Non-determinism acceptable.

Decisions (from user)

  • Pass count: constant default + --passes=N flag.
  • Early exit: no — always run full N.
  • Cycles: keep #CIRCU! (only cell.this lua cells converge).

Changes

  1. extra/cmd/res-sheets/calc.go — add flag.FlagSet with -passes (default 20; const defaultCalcPasses); replace single sh.Recalc() with for i := 0; i < passes; i++ { sh.Recalc() }; update usage string.
  2. extra/lib/sheets/recalc.go — no signature change; optional doc comment noting safe to call repeatedly for convergence.
  3. Docs — docs/extra/extra.md (or docs/extra/sheets-functions.md): document res-sheets calc <input> [output] [--passes=N]; explain multi-pass lets self-referential/volatile-dependent cells settle; note cyclic cells still emit #CIRCU!.
  4. Tests (new) — extra/lib/sheets/lua_test.go or recalc_test.go: TestRecalcMultiPassConverges (sheet with =lua{(cell.this or 0)+1}; assert after Recalc() ×N (N≥2) value equals N). extra/cmd/res-sheets/calc_test.go: fixture with cell.this lua cell; run runCalc with -passes=5; assert exported *.sheet-out.csv contains 5, not 1.

Affected repos

extra/ only. No change to core/, docs/, os/, editor/. (store.LoadWorkbook's single Recalc() at store.go:249 intentionally unchanged — serves interactive/TUI load, not oneshot export.)

Risks

  • Performance: N=20 full topological recalcs per calc run (20× current cost). Acceptable for headless export; flag lets users lower it.
  • Volatile non-convergence: RAND changes every pass; final emitted RAND is the Nth draw — fine per non-determinism decision.
  • No early-exit: per decision, always full N. Simple/predictable.

Verification

cd extra && go build ./... && go test ./... && go vet ./...

Manual smoke:

res-sheets calc fixture.sheet.csv --passes=5
# fixture.sheet-out.csv → A1 (cell.this cell) should be "5", not "1"

Open question (non-blocking)

Default pass count of 20 is a guess at the "10+10" model. Want a different default? Flag overrides regardless.

Worth-it verdict

Yes — small, contained fix (one loop + flag + tests + docs) correcting a real silent-correctness bug in exported output.

# Plan: `res-sheets calc` multi-pass convergence ## Problem (confirmed) The oneshot `calc` path calls `sh.Recalc()` exactly once (`extra/cmd/res-sheets/calc.go:46`). A single pass is insufficient for `cell.this` self-referential lua cells, which read their own previous-pass `Living` value via `LastGood` (`recalc.go:31-36`, `lua_eval.go:160-162`). `TestLuaCellThis` proves such a cell only reaches its correct value on the **second** `Recalc()` — first pass has empty `LastGood`, so `(cell.this or 0)+1` yields `1` instead of `2`. Oneshot calc emits only the first (wrong) pass, so critical script cells export empty/wrong values. **Not affected:** true graph cycles are marked `#CIRCU!` by Kahn's algorithm every pass and never get a value. Volatile functions already render in a single pass. ## Goal Run `Recalc()` **N times** in the calc path and emit the **final** pass, so `cell.this` self-referential cells converge. True cycles keep `#CIRCU!`. Non-determinism acceptable. ## Decisions (from user) - **Pass count:** constant default **+** `--passes=N` flag. - **Early exit:** **no** — always run full N. - **Cycles:** keep `#CIRCU!` (only `cell.this` lua cells converge). ## Changes 1. `extra/cmd/res-sheets/calc.go` — add `flag.FlagSet` with `-passes` (default `20`; const `defaultCalcPasses`); replace single `sh.Recalc()` with `for i := 0; i < passes; i++ { sh.Recalc() }`; update usage string. 2. `extra/lib/sheets/recalc.go` — no signature change; optional doc comment noting safe to call repeatedly for convergence. 3. Docs — `docs/extra/extra.md` (or `docs/extra/sheets-functions.md`): document `res-sheets calc <input> [output] [--passes=N]`; explain multi-pass lets self-referential/volatile-dependent cells settle; note cyclic cells still emit `#CIRCU!`. 4. Tests (new) — `extra/lib/sheets/lua_test.go` or `recalc_test.go`: `TestRecalcMultiPassConverges` (sheet with `=lua{(cell.this or 0)+1}`; assert after `Recalc()` ×N (N≥2) value equals `N`). `extra/cmd/res-sheets/calc_test.go`: fixture with `cell.this` lua cell; run `runCalc` with `-passes=5`; assert exported `*.sheet-out.csv` contains `5`, not `1`. ## Affected repos `extra/` only. No change to `core/`, `docs/`, `os/`, `editor/`. (`store.LoadWorkbook`'s single `Recalc()` at `store.go:249` intentionally unchanged — serves interactive/TUI load, not oneshot export.) ## Risks - **Performance:** N=20 full topological recalcs per calc run (20× current cost). Acceptable for headless export; flag lets users lower it. - **Volatile non-convergence:** RAND changes every pass; final emitted RAND is the Nth draw — fine per non-determinism decision. - **No early-exit:** per decision, always full N. Simple/predictable. ## Verification ```bash cd extra && go build ./... && go test ./... && go vet ./... ``` Manual smoke: ```bash res-sheets calc fixture.sheet.csv --passes=5 # fixture.sheet-out.csv → A1 (cell.this cell) should be "5", not "1" ``` ## Open question (non-blocking) Default pass count of 20 is a guess at the "10+10" model. Want a different default? Flag overrides regardless. ## Worth-it verdict Yes — small, contained fix (one loop + flag + tests + docs) correcting a real silent-correctness bug in exported output.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
residual/.agent#2
No description provided.