[#87] let bindings: special forms DO work — surface the errors the bench was swallowing #90

Merged
robertismo merged 1 commit from 87-let-if into master 2026-09-30 01:13:43 +00:00
Owner

#87 — "can't put if/cond in let binding". Root cause found, and it is not the interpreter's special forms.

The actual failure

Lane B's broken v2 construct (8bfcf59) was a let whose bindings list was missing one closer, so the intended body (if ...) parsed as a SECOND binding — a 3-element list. The interpreter correctly rejected it with malformed let binding. The real bug: hook run-time errors print only when !headlessMode, so in every bench the dying hook was completely silent. That invisibility is what made a paren typo look like "the interpreter can't eval if inside let".

if/cond/nested let inside let bindings all evaluate fine — the production brains depend on the pattern (the super-v2 trunk prices its risk budget with (let ((mult (if ...))) ...)).

The fix

  • ai.go callHook: hook errors now ALWAYS print to stderr and append to the game log (headless or not). Benches keep clean stdout tables; the failure lands in the log/stderr where it can be grepped.
  • let_binding_test.go regression tests: (1) if, cond, nested let, and if-as-argument inside let bindings evaluate to the right values; (2) the exact malformed shape from #87 is rejected with malformed let binding; (3) a hook erroring at run time leaves a trace in the game log under headlessMode.

Gate: full go test green, go vet green, spectate smokes 0 ai-errors / 0 divergences. Branch 87-let-if @ 689911a.

#87 — "can't put if/cond in let binding". Root cause found, and it is not the interpreter's special forms. ## The actual failure Lane B's broken v2 construct (8bfcf59) was a `let` whose bindings list was missing one closer, so the intended body `(if ...)` parsed as a SECOND binding — a 3-element list. The interpreter correctly rejected it with `malformed let binding`. The real bug: hook run-time errors print only when `!headlessMode`, so in every bench the dying hook was completely silent. That invisibility is what made a paren typo look like "the interpreter can't eval if inside let". `if`/`cond`/nested `let` inside let bindings all evaluate fine — the production brains depend on the pattern (the super-v2 trunk prices its risk budget with `(let ((mult (if ...))) ...)`). ## The fix - `ai.go` `callHook`: hook errors now ALWAYS print to stderr and append to the game log (headless or not). Benches keep clean stdout tables; the failure lands in the log/stderr where it can be grepped. - `let_binding_test.go` regression tests: (1) `if`, `cond`, nested `let`, and `if`-as-argument inside let bindings evaluate to the right values; (2) the exact malformed shape from #87 is rejected with `malformed let binding`; (3) a hook erroring at run time leaves a trace in the game log under `headlessMode`. Gate: full `go test` green, `go vet` green, spectate smokes 0 ai-errors / 0 divergences. Branch 87-let-if @ 689911a.
Sign in to join this conversation.
No reviewers
No labels
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
Winnie/bankwars!90
No description provided.