eval(): symbol t self-evaluates BEFORE env lookup — t params receive #t (super/mill loan hooks return NaN, super never bids loans) #98

Closed
opened 2026-09-30 03:08:33 +00:00 by Winnie · 3 comments
Owner

Found by exp4 during its #97 duel work, verified by the communicator against source.

Bug

eval() in lisp.go (line ~433): for a symbol, the check if v.str == "t" { return trueVal } runs BEFORE env.get(v.str). So any function with a parameter named t can never read its argument: the self-evaluating #t constant shadows the binding.

Repro: ((lambda (t) t) 7) evaluates to the symbol t (trueVal), not 7.

Consequences (measured)

  • ai/super.lisp: q-term (h t), loan-charge (n t), bid-for (n t level) all take a t param. loan-charge computes NaN (0/0), the cap check in bid-for fails, and bid-for returns 0. Super NEVER bids on a loan, in ANY preset. Its 59-win canonical baseline is deposits/bonds/auctions only (diag spectate: super's pending bids = empty map while empire bids all 5 loans).
  • ai/mill.lisp has the same class of bug (loan-rate param t -> NaN). Mill wins the duel by being last standing in the corpse cascade, not by lending.

Fix options (Robert to rule)

  1. Engine: env lookup FIRST, fall back to trueVal only when unbound. Preserves the #89 fix (#t used as a value still works — it's unbound, so the fallback fires) AND restores t params. One-order change in eval().
  2. Brains: rename the params (exp4's P0, confined to ai/super.lisp). Cheap but leaves the landmine for every future brain; anyone writing (defun f (t) ...) silently gets #t.

Option 1 is a 1-line order swap plus tests; option 2 is what exp4 is already measuring locally for its duel lane. Whichever lands, super's true baseline (with live loan bidding) is unknown and will move — every experimenter's numbers should be re-baselined after the fix.

Refs: #97 (live work), exp4 report /root/bw85-reports/exp4.md on the host.

Found by exp4 during its #97 duel work, verified by the communicator against source. **Bug** `eval()` in lisp.go (line ~433): for a symbol, the check `if v.str == "t" { return trueVal }` runs BEFORE `env.get(v.str)`. So any function with a parameter named `t` can never read its argument: the self-evaluating `#t` constant shadows the binding. Repro: `((lambda (t) t) 7)` evaluates to the symbol `t` (trueVal), not 7. **Consequences (measured)** - `ai/super.lisp`: `q-term (h t)`, `loan-charge (n t)`, `bid-for (n t level)` all take a `t` param. `loan-charge` computes NaN (0/0), the cap check in `bid-for` fails, and `bid-for` returns 0. Super NEVER bids on a loan, in ANY preset. Its 59-win canonical baseline is deposits/bonds/auctions only (diag spectate: super's pending bids = empty map while empire bids all 5 loans). - `ai/mill.lisp` has the same class of bug (`loan-rate` param `t` -> NaN). Mill wins the duel by being last standing in the corpse cascade, not by lending. **Fix options (Robert to rule)** 1. Engine: env lookup FIRST, fall back to trueVal only when unbound. Preserves the #89 fix (`#t` used as a value still works — it's unbound, so the fallback fires) AND restores `t` params. One-order change in eval(). 2. Brains: rename the params (exp4's P0, confined to ai/super.lisp). Cheap but leaves the landmine for every future brain; anyone writing `(defun f (t) ...)` silently gets #t. Option 1 is a 1-line order swap plus tests; option 2 is what exp4 is already measuring locally for its duel lane. Whichever lands, super's true baseline (with live loan bidding) is unknown and will move — every experimenter's numbers should be re-baselined after the fix. Refs: #97 (live work), exp4 report /root/bw85-reports/exp4.md on the host.

my understand is t should have nothing to do with #t as those are completely different symbols, they shouldn't even be muddieable. I think your fix should tackle this aspect first. @Winnie

my understand is t should have nothing to do with #t as those are completely different symbols, they shouldn't even be muddieable. I think your fix should tackle this aspect first. @Winnie
Author
Owner

Fixed. PR #99 (branch 98-tfix @ 3fdce18), review requested. Root cause confirmed: my #89 fix ordered the checks wrong — t self-evaluated before the env lookup, shadowing every t-named parameter. super.lisp's q-term/loan-charge/bid-for all take t, so loan-charge went NaN and super never bid a loan; mill has the same class.

Fix: bindings first (env, then builtins); the t constant is only the fallback for a genuinely unbound t, so #t-as-value (#89) still works. Tests pin both directions: t params receive arguments, and #t values stay truthy. Full suite + vet + smoke seed 33 clean.

After the merge the #97 team re-baselines: super's true number with live loan bidding is unknown and will move.

Fixed. **PR #99** (branch 98-tfix @ 3fdce18), review requested. Root cause confirmed: my #89 fix ordered the checks wrong — t self-evaluated before the env lookup, shadowing every t-named parameter. super.lisp's q-term/loan-charge/bid-for all take t, so loan-charge went NaN and super never bid a loan; mill has the same class. Fix: bindings first (env, then builtins); the t constant is only the fallback for a genuinely unbound t, so #t-as-value (#89) still works. Tests pin both directions: t params receive arguments, and #t values stay truthy. Full suite + vet + smoke seed 33 clean. After the merge the #97 team re-baselines: super's true number with live loan bidding is unknown and will move.
Author
Owner

Ruling implemented exactly as stated: t and #t are now completely different symbols. PR #99 @ 3a2d449 (force-pushed over the earlier order-swap draft).

  • #t = its own value kind (the boolean true, self-evaluating, truthy).
  • t = a plain symbol: bindable, shadowable, loud error when unbound. The #89 t-reservation is removed.
  • The cond else-matcher no longer accepts a bare t symbol as else.

No muddling is possible by construction. Tests cover both directions (t params receive args; t definable; #t truthy alongside a numeric t; unbound symbols still error). Full suite + vet + a seed-33 spectate with the canonical super.lisp clean. After merge the #97 team re-baselines everything.

Ruling implemented exactly as stated: t and #t are now completely different symbols. PR #99 @ 3a2d449 (force-pushed over the earlier order-swap draft). - #t = its own value kind (the boolean true, self-evaluating, truthy). - t = a plain symbol: bindable, shadowable, loud error when unbound. The #89 t-reservation is removed. - The cond else-matcher no longer accepts a bare t symbol as else. No muddling is possible by construction. Tests cover both directions (t params receive args; t definable; #t truthy alongside a numeric t; unbound symbols still error). Full suite + vet + a seed-33 spectate with the canonical super.lisp clean. After merge the #97 team re-baselines everything.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
2 participants
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#98
No description provided.