File Details
v3.2.0
- R
- May 11, 2026
- 5.16 MB
- 13
- 12.0.5
- Retail
File Name
WHEREDNGN-v3.2.0.zip
Supported Versions
- 12.0.5
Loot & Baloot
v3.2.0 (2026-05-11)
Full Changelog Previous Releases
- docs: amend v3.2.0 release notes per Codex review
Three Codex amends to the v3.2.0 CHANGELOG entry:- Bot/Bidding.lua bullet no longer claims a public
Bot.PickAshkal
function (there is none — Ashkal is an internal decision path
insideBot.PickBid). Lists the actual public symbols:
Bot.PickBid,Bot.PickPreempt,Bot.PickOvercall, plus a
note about the internal Ashkal path. - Bot.lua line-count claim softened to "about 8.4k → about 6.1k,
roughly 2,300 lines" to avoid tying public release notes to a
brittle exact wc-l number across tool/line-ending variation. - Harness count framing: "1,106 → 1,219 passing checks, covering
behavioral paths plus source-pin regression guards" instead of
calling every assertion a "behavioral pin" (the harness mixes
behavioral checks with source-pin regression assertions).
CHANGELOG.md only. No runtime, test, .toc, .pkgmeta, workflow, or
experimental-branch changes. Tag still held for Codex review.
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com
- Bot/Bidding.lua bullet no longer claims a public
- docs: add v3.2.0 release notes (maintenance / internal restructure)
Document the v3.2.0 cleanup wave outcomes:- Bot/Tiers.lua, Bot/PlayPrimitives.lua, Bot/Bidding.lua,
Bot/Escalation.lua, UI/Themes.lua extractions - Bot.lua reduced from 8,451 to 6,078 lines (-28%)
- Harness up from 1,106 to 1,219 behavioral pins
- Explicit "no intended gameplay / UI / protocol / saved-variable /
scoring changes" framing
Release-prep only. No runtime / test / .toc / .pkgmeta edits in this
commit. Tag is held for Codex review per release-prep workflow.
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com
- Bot/Tiers.lua, Bot/PlayPrimitives.lua, Bot/Bidding.lua,
- docs: add v3.2.0 release-readiness checkpoint
- docs(batch9): normalize whitespace + correct harness counts (+30 / 1219)
Three doc/whitespace amendments after Codex review of Batch 9
implementation. No runtime behavior changes.- Bot/Escalation.lua line-ending normalization
The original verbatim move concatenated the new file's header
(Python\nLF literals) with the moved escalation cluster (which
uses CRLF throughout the rest of the repo on Windows). Result:
~33 lines of bare LF in the module header followed by ~595 lines
of CRLF in the moved code.git diff --check fed7a46...HEAD
flagged every line where the diff hunk straddled an LF/CRLF
boundary as "trailing whitespace" (CR before LF reads as trailing
CR). Normalize the whole file to CRLF + strip trailing whitespace. - .swarm_findings/v3_2_0_batch9_escalation_design.md
Replace the design-pass estimated+18 / ~1207references with
the actual implementation result:- 45 AJ.9g asserts added (not 18)
- -6 AJ.9f-bind retirements (the Batch 8 Bidding re-binding
header on Bot.lua was removed in Batch 9, so the 6 AJ.9f-bind
asserts that previously checked for its presence are now
stale and were retired) - -9 retarget side-effects net
- Total: +30 (1189 baseline -> 1219 final harness)
Updated 7 spots: AJ.9g delta line, delta breakdown table,
§6D expected count, §6A implementation step 10, §8C tests
added, §8D harness count delta, §8 summary table.
- tests/test_state_bot.lua AJ.9f block comment refresh
The block-leading comment claimed "Bot.lua re-binds 6 helpers
(suitStrengthAsTrump + ...) consumed by escalation deciders."
That was true post-Batch 8 but stale post-Batch 9 (Batch 9
moved the consumers AND the re-binding header to Bot/Escalation
.lua). Replace with a HISTORICAL NOTE clearly stating:- Batch 8 originally added the 6-locals header to Bot.lua
- Batch 9 migrated the header WITH the escalation deciders to
Bot/Escalation.lua - AJ.9g (below) enforces both the new location AND the absence
of the old header from Bot.lua
ThebidderHoldsBidcardstays-in-Bot.lua note is preserved.
Verification:
- git diff --check fed7a46...HEAD: clean
- Full harness: 1219/1219 pass (unchanged)
- tests/test_H7_sun_shortest_lead.lua: 9/0 pass
No runtime Lua touched (Bot/Escalation.lua's whitespace
normalization is metadata-only; the semantic content is unchanged).
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com
- Bot/Escalation.lua line-ending normalization
- v3.2.0 cleanup batch 9: extract Bot/Escalation.lua
Move the four-rung escalation chain (Bel x2 -> Triple x3 -> Four x4 ->
Gahwa match-win) out of Bot.lua into a new Bot/Escalation.lua module.
Mirrors the Batch 5B / 5C / 8 extraction pattern: new file loaded
before Bot.lua, public picker functions set directly on the shared
B.Bot table (no Bot.Escalation sub-table), file-local helpers and
jitter constants live inside the new module.
Moved to Bot/Escalation.lua:
Public on B.Bot.:
* Bot.PickDouble (defender's Bel response, ~231 lines)
* Bot.PickTriple (bidder's Triple counter, ~71 lines)
* Bot.PickFour (defender's Four counter, ~83 lines)
* Bot.PickGahwa (bidder's terminal Gahwa, ~55 lines)
File-local helpers (3):
* escalationStrength -- shared bid-strength calc for the 3
bidder-side pickers (~77 lines)
* selfStyleJitterBonus -- Fzloky+ jitter-widening based on the
seat's lifetime escalation count
* styleBelTendency -- partner-style tendency consumed only
by Bot.PickTriple
Per-rung jitter constants (4):
* BEL_JITTER = 10 (Bel)
* TRIPLE_JITTER = 12
* FOUR_JITTER = 15
* GAHWA_JITTER = 18 (highest variance for terminal rung)
Total: 7 file-local moved symbols/consts + 4 public picker functions
= 11 moved symbols. Plus inline copies of jitter + shuffledSuits
(utility duplicates, mirroring Batch 8's Bot/Bidding.lua pattern).
Stays in Bot.lua:
* Bot.OnEscalation, Bot.OnRoundEnd -- style-ledger updaters
called from State.lua's ApplyDouble/Triple/Four/Gahwa
* emptyStyle -- style-ledger initializer
* styleTrumpTempo -- consumed by pickLead/pickFollow
* bidderHoldsBidcard, meldKnownHeld, anyOpponentVoidIn -- play helpers
* jitter, shuffledSuits, BEL_JITTER (intentionally NOT in Bot.lua
after this move; the original BEL_JITTER lived inside the
escalation cluster, now in Bot/Escalation.lua)
* Bot._partnerStyle, Bot._memory tables (on shared B.Bot.)
Bot.lua re-binding header removal:
The 6-locals Bidding re-binding header introduced in Batch 8 (to
feed escalation deciders consuming Bidding helpers) is now DELETED
because all consumers (escalation deciders) moved to
Bot/Escalation.lua. Net.lua and other Bot.lua call sites that
previously consumed those Bidding helpers via the re-binding are
grep-confirmed absent. Bot.lua's surface SHRINKS rather than grows.
Style-ledger comment refresh:
The stale shared "Convenience derived metrics... Currently unused
by the picker code" comment that previously sat above both
styleBelTendency and styleTrumpTempo was incorrect (both ARE used
by picker code). The comment is refreshed in Bot.lua above the now-
alone styleTrumpTempo to truthfully name its consumers
(pickLead/pickFollow). styleBelTendency carries a concise truthful
comment to Bot/Escalation.lua naming PickTriple as its only caller.
Bot/Escalation.lua imports:
* 6 helpers from Bot.Bidding (suitStrengthAsTrump, sunStrength,
partnerBidBonus, partnerEscalatedBonus, combinedUrgency,
opponentUrgency) re-bound as file-locals
* Bot.IsAdvanced / Bot.IsBotSeat / Bot.IsFzloky via shared B.Bot
* Bot._partnerStyle / Bot._memory via shared B.Bot
* K / C / R / S (Constants / Cards / Rules / State)
* inline jitter + shuffledSuits
WHEREDNGN.toc: Bot/Escalation.lua loads after Bot/Bidding.lua and
before Bot.lua.
11 test loaders each gain one new load line for Bot/Escalation.lua
(between Bot/Bidding.lua and Bot.lua).
Source-pin retargets in tests/test_state_bot.lua (7 do-blocks):
AA.1a/b (escalationStrength void/sideAce bonuses)
AB.2 (PickGahwa DEAD-2 rationale)
AD.3 (duplicate PickGahwa DEAD-2)
AD.7a/b (PickDouble eltrace helper + eval line)
AH.3 (PickTriple floor cap)
AH.7 (escalationStrength Sun-penalty neutralization comment)
AI.4 (PickDouble bid-history inflection marker)
AJ.9f-bind block retired:
The 6-iteration AJ.9f-bind assertions that previously checked
Bot.lua's Bidding re-binding header are now obsolete (header
removed). Replaced with a commentary block; new AJ.9g block
contains stricter assertions including ABSENCE of the header in
Bot.lua.
New AJ.9g source-pin block (45 asserts):
* 3 helper presence asserts in Bot/Escalation.lua
* 4 public picker presence asserts
* 4 jitter constant presence asserts
* 6 Bidding helper import asserts
* 5 .toc-order asserts (including escalation slot)
* 2 negative-export asserts (no Bot.Escalation sub-table)
* 5 Bot.lua state asserts (no Bidding header, styleTrumpTempo +
OnEscalation + OnRoundEnd stay, escalation helpers absent)
* 7 "did NOT define" asserts (3 helpers + 4 pickers absent from
Bot.lua) -- the loop runs 4 times for pickers
Plus 9 misc
Bot.lua dropped from 6,673 raw lines to 6,078 raw lines (-595 net).
Bot/Escalation.lua is 682 raw lines.
Verification:
* Full harness: 1219/1219 pass (1189 baseline + 30 net)
* Standalone smokes all green:
- tests/test_H7_sun_shortest_lead.lua 9 passed
- tests/test_H1_pin_J9_trump.lua 11 passed
- tests/test_numworlds_scaling.lua 21 passed
- tests/test_v0.5_traced_game.lua 10 passed
- tests/test_bel_decision_quality.lua 1000x3 sweep clean
No behavior change. No release tag. No Bot.Escalation sub-table
introduced. Net.lua call sites for B.Bot.PickDouble/Triple/Four/Gahwa
resolve through the shared table unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com - docs: add v3.2.0 cleanup batch 9 (Bot/Escalation.lua) design/inventory doc
- docs(batch8): fix remaining stale counts in design doc
Five more stale references in.swarm_findings/v3_2_0_batch8_bidding_design.md
left over from the original "14 helpers / 5-locals / 1178 harness"
plan, written before the latesuitStrengthAsTrumpmove:- AJ.9f helper presence asserts count: 14 -> 15
- AJ.9f delta table: +32 -> actual +41 (with -1 retarget side
effect = net +40) - Risk register row #2: re-binding header "7 locals" -> "6 locals"
with the explicit name list - Section 9 "Why this scope (not smaller)" rationale:
"Bot.lua re-binding header stays compact at 7 locals" -> "6 locals" - Section 9 implementation expected harness: "1178 / 1178" ->
actual "1189 / 1189" with the verified commit hash
Comment / doc only. No runtime logic changes.
Verification:- Full harness still 1189/1189 pass
- tests/test_H7_sun_shortest_lead.lua still 9/0 pass
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com
- docs(batch8): align wording with final 15-helper / 6-locals implementation
Stale comment / doc wording from the original "14 helpers / 5 Bot.lua
re-bindings" plan, written before the latesuitStrengthAsTrump
move that was forced by grep-confirming PickDouble/PickTriple still
called it.
Updates (comment / doc only — no runtime logic changes):- .swarm_findings/v3_2_0_batch8_bidding_design.md
- 14 file-local helpers -> 15
- 5-locals re-binding header -> 6-locals (suitStrengthAsTrump added)
- 18 in-scope symbols -> 19 in-scope
- 23 symbols moved -> 25 symbols moved
- Three non-contiguous source ranges -> four (adds 962-1014 for
suitStrengthAsTrump) - Expected harness count 1181 -> actual 1189 (verified on this
branch at commit c699812) - AJ.9f assert count 32 -> actual 41
- Bot/Bidding.lua header comment
- 14 bidding helpers -> 15 (suitStrengthAsTrump prepended to the list)
- "Five of them are also re-exported" -> "Six of them"
- Bot.lua breadcrumb at original PickBid location
- "The 5-locals re-binding header" -> "The 6-locals re-binding header"
- Adds suitStrengthAsTrump to the explicit helper list
- tests/test_H7_sun_shortest_lead.lua loadFile comment
- "14 file-local helpers (incl. sunStrength / ...)" -> "15 file-local
helpers (incl. suitStrengthAsTrump / sunStrength / ...)" - "Bot.lua's 5-locals re-binding header" -> "6-locals"
Verification: - Full harness still 1189/1189 pass
- tests/test_H7_sun_shortest_lead.lua still 9/0 pass
No runtime behavior changes. No release tag.
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com
- "14 file-local helpers (incl. sunStrength / ...)" -> "15 file-local
- .swarm_findings/v3_2_0_batch8_bidding_design.md
- v3.2.0 cleanup batch 8: extract Bot/Bidding.lua
Move the bidding-window deciders + 15 file-local helpers out of
Bot.lua into a new Bot/Bidding.lua module. Mirrors the Batch 5B / 5C
extraction pattern: new file loaded before Bot.lua, public functions
set directly on the shared B.Bot table, file-local helpers exposed
via a narrow B.Bot.Bidding.* sub-table for Bot.lua's re-binding
header.
Moved to Bot/Bidding.lua:
Public on B.Bot.:
* Bot.PickBid (R1/R2 bidding decision; ~590-line decider)
* Bot.PickPreempt (triple-on-Ace pre-emption)
* Bot.PickOvercall (Sun overcall window)
* Bot.OpponentUrgency (consumed by BotMaster.lua)
Test-internal export:
* Bot._beloteBypassQualifies = beloteBypassQualifies
File-local helpers (15):
* suitStrengthAsTrump, sideSuitAceBonus, hokmMinShape, sunMinShape
* beloteSuit, beloteBypassQualifies, aceCountAndMardoofa
* withBidcard, sunStrength
* partnerBidBonus, scoreUrgency, opponentUrgency
* matchPointUrgency, combinedUrgency, partnerEscalatedBonus
Tuning aliases (moved from Bot.lua:24-60):
* TH_HOKM_R1_BASE, TH_HOKM_R2_BASE, TH_SUN_BASE, BID_JITTER
* BEL_JITTER (new file-local in Bot/Bidding.lua for PickPreempt's
jitter call; mirrors Bot.lua:5777 which stays for escalation)
Plus inline copies of jitter + shuffledSuits (4-line pure helpers).
Stays in Bot.lua:
* bidderHoldsBidcard (line 979 — only consumed by pickLead/pickFollow
trump-J inference at Bot.lua:2150)
* meldKnownHeld (only consumed by pickLead/pickFollow)
* jitter, shuffledSuits, BEL_JITTER (used everywhere else)
* Escalation deciders (PickDouble/Triple/Four/Gahwa), escalationStrength,
styleBelTendency, selfStyleJitterBonus
* All play deciders (pickLead, pickFollow, PickAKA, PickPlay, PickMelds,
PickKawesh, PickTakweesh, PickSWA, PickSWAResponse)
* Memory/style ledgers (Bot._memory, Bot._partnerStyle)
Bot.lua re-binding header (6 file-locals, just below the Batch 5C
Primitives header):
local suitStrengthAsTrump = Bidding.suitStrengthAsTrump
local sunStrength = Bidding.sunStrength
local partnerBidBonus = Bidding.partnerBidBonus
local partnerEscalatedBonus = Bidding.partnerEscalatedBonus
local combinedUrgency = Bidding.combinedUrgency
local opponentUrgency = Bidding.opponentUrgency
scoreUrgency and matchPointUrgency intentionally stay file-local in
Bot/Bidding.lua — grep-confirmed they have zero call sites in
Bot.lua post-move (only 2 comment references at lines 6040, 6289).
WHEREDNGN.toc: Bot/Bidding.lua loads between Bot/PlayPrimitives.lua
and Bot.lua.
11 test loaders each gain one new load line for Bot/Bidding.lua.
Source-pin retargets in tests/test_state_bot.lua: ~19 pins
(R.2, T.2, T.3, X.2, X.3, Y.2b, Y.3, Z.1-Z.3, Z.5, AD.1, AD.9,
AF.1-AF.3, AH.6, P.8) switched io.open path from /Bot.lua to
/Bot/Bidding.lua.
New AJ.9f source-pin block (41 asserts):
* 15 local-function presence asserts
* 4 public-function presence asserts
* 1 Bot._beloteBypassQualifies test-export assert
* 1 BEL_JITTER local presence assert
* 6 Bidding. export asserts
* 2 negative-export asserts (scoreUrgency, matchPointUrgency NOT exported)
* 4 .toc-order asserts
* 6 Bot.lua re-binding header asserts
* 2 bidderHoldsBidcard location asserts
Bot.lua dropped from ~8070 raw lines to ~6671 raw lines (-1399).
Bot/Bidding.lua is 1534 raw lines.
Verification:
* Full harness: 1189/1189 pass (1149 baseline + 40 net)
* Standalone smokes:
- tests/test_H7_sun_shortest_lead.lua 9 passed
- tests/test_H1_pin_J9_trump.lua 11 passed
- tests/test_numworlds_scaling.lua 21 passed
- tests/test_v0.5_traced_game.lua 10 passed
- tests/test_bel_decision_quality.lua 1000x3 sweep clean
No behavior change. No release tag.
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com - docs: add v3.2.0 cleanup batch 8 (Bot/Bidding.lua) design/inventory doc
- docs(batch7): align inventory table with final scope
- v3.2.0 cleanup batch 7: convert bidding pins to behavior
- docs: add v3.2.0 cleanup batch 7 design/inventory doc
- docs(batch6): correct projected test-count delta
- v3.2.0 cleanup batch 6: convert bidding pins to behavior
- docs: add v3.2.0 cleanup batch 6 design/inventory doc
- docs(checkpoint): amend per Codex review
Documentation accuracy fixes flagged by Codex:- Current State table:
maincommit was listed as 6ae91e1 (the H7
comment polish); update to the actual current HEAD 3b333eb (the
checkpoint doc commit). Keep 6ae91e1 as a separate "previous
polish commit" row so the H7 polish is still visible. - Module line-count table: switch from raw
wc -lcounts to
non-blank-line counts (empty rows excluded, comment rows
included). This is the metric Codex normalizes against
batch-to-batch. New totals:
Bot.lua 7 875 (was 8 070 raw)
Net.lua 6 205 (was 6 430 raw)
UI.lua 4 534 (was 4 745 raw)
State.lua 2 464 (was 2 570 raw)
... and the rest, total 26 918 (was 27 987 raw). - Subheadings updated: "What remains in Bot.lua (7 875 lines)",
same for UI.lua / Net.lua / State.lua. - Summary sentence under "Why not immediate 5D?" updated to use the
new totals and corrects the "1 040 lines moved" → "694 non-blank
lines moved" (UI/Themes 247 + Bot/Tiers 75 + Bot/PlayPrimitives
372). - Added a clarifying note inside "What remains in Bot.lua" that the
internal ~xxxx-yyyy address ranges are raw source-line addresses
(what your editor shows), to avoid confusion with the non-blank
total in the section heading.
Documentation-only amend. No runtime Lua touched. No release tag.
Full harness still 1150/1150.
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com
- Current State table:
- docs: add v3.2.0 post-cleanup checkpoint
Pause point after the v3.2.0 cleanup/decomposition wave. No new
extraction batch from this doc — design/audit checkpoint only.
Contents:
1. Current state (commit, last tag, harness, branches)
2. Cleanup stack summary (Batches 1-5C + bel-quality shim +
H7 comment polish)
3. Module map (line counts, what remains in Bot.lua / UI.lua /
Net.lua / State.lua post-extraction)
4. Test and source-pin health (1150/1150 + 5 standalone smokes,
pin counts per file, AJ.9c/d/e migrations)
5. Remaining risk register (4B/4C/4D, Bot/Memory, Bot/Escalation,
Bot/Bidding, UI renderer)
6. Recommended next work (ranked design-pass candidates) — top
recommendation is a Batch 3-style source-pin-to-behavioral
conversion of 3-6 high-value pins as prep before Bot/Bidding.
7. Release guidance (v3.1.14 remains shipped; criteria for what
would justify v3.2.0)
No runtime code changed.
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com - test(H7): refresh stale comment about hunk-1 injection point
The "Apply H-7 patch" prose at the top of the file still said hunk 1
adds cardsOfSuit "immediately after the closing end of lowestByRank."
That was true pre-Batch 5C when both lowestByRank and highestByRank
lived in Bot.lua. After Batch 5C those primitives moved to
Bot/PlayPrimitives.lua and the actual anchor (already updated below
at ANCHOR_HELPER) is\nlocal function pickLead, so cardsOfSuit now
lands immediately before pickLead.
Comment-only change. Anchor / helper block / Sun-branch anchor / load
order all unchanged. H7 standalone still 9/0; full harness still
1150/1150.
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com - v3.2.0 cleanup batch 5C: extract Bot/PlayPrimitives.lua
Move the ten play-primitive helpers out of Bot.lua into a new
Bot/PlayPrimitives.lua module:
* pickRandomTied
* lowestByRank
* highestByRank
* highestByFaceValue
* holdsBeloteThusFar
* highestTrump
* legalPlaysFor
* wouldWin
* tahreebClassify
* applyClosedTrumpLeadGate
Exported via the B.Bot.Primitives sub-table (UI/Themes pattern).
Inter-primitive calls (lowest/highest/highestByFaceValue →
pickRandomTied) stay as file-local closures inside the new module.
Bot.lua gains an 11-locals re-binding header at the top of the
chunk, right after the Batch 5B tier breadcrumb, so every existing
call site below (pickLead / pickFollow / PickPlay / PickAKA /
escalation deciders / etc.) resolves through file-locals unchanged.
The "-- Play" section header at the original location stays in
Bot.lua with a short breadcrumb in place of the moved block.
WHEREDNGN.toc: addBot/PlayPrimitives.luabetween
Bot/Tiers.luaandBot.luain the# Game runtimesection.
Test loaders (11 files) each gain one new line:
load("Bot/PlayPrimitives.lua") -- between Tiers and Bot.lua
H7 anchor update:tests/test_H7_sun_shortest_lead.luaswaps
ANCHOR_HELPER from\nlocal function highestByRankto
\nlocal function pickLead.highestByRankmoved to
PlayPrimitives.lua so the old anchor no longer matches; pickLead
is the next stable file-scope symbol and is deferred indefinitely
from extraction. The patched code'slowestByRankreferences
resolve through Bot.lua's re-binding header. The Sun-branch hunk
anchor (inside pickLead's body) is unchanged.
Tests:
* AJ.9e (new): 10 def + 10 export + 3 toc + 10 bind = 33 source
pins asserting Bot/PlayPrimitives.lua presence, exports, .toc
order, and Bot.lua's re-binding header.
* AN.1a / AN.1b (UPDATED per Codex correction): now scan
Bot/PlayPrimitives.lua for the v3.0.3 GAP-01 marker and the
return "want_hint"string (both moved with tahreebClassify).
AN.1c (cls == "want_hint"consumer in pickLead/pickFollow)
keeps scanning Bot.lua.
* AN.8a / AN.8b / AN.8c (UPDATED per Codex correction): now scan
Bot/PlayPrimitives.lua for the v3.0.6 SENDER-INTENT marker,
classifier-sidelenAtFirstDiscardread, andif lenAtFirst >= 3 thengate. The recorder-sidelenAtFirstDiscardwrite
stays in Bot.lua so scanning Bot.lua would give false positives
for the classifier code.
Verification:
* Full harness: 1150/1150 pass (+33 from AJ.9e source pins).
* Standalone smokes:
- test_H1_pin_J9_trump.lua 11 passed
- test_H7_sun_shortest_lead.lua 9 passed <- anchor swap
- test_numworlds_scaling.lua 21 passed
- test_v0.5_traced_game.lua 10 passed
- test_bel_decision_quality.lua 1000×3 sweep clean
No behavior change. No release tag. No Bot.PickBid / pickLead /
pickFollow / memory / style ledger / escalation / BotMaster /
Net / State / UI / Slash / saved-variable / protocol / gameplay
changes.
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com - docs: add v3.2.0 cleanup batch 5C design/inventory doc
Plans the Bot/PlayPrimitives.lua extraction — the deferred Option B
from the Batch 5B design pass.
Implementation shape:
* 10 functions moved to Bot/PlayPrimitives.lua under B.Bot.Primitives
* Bot.lua gains a 14-line re-binding header (UI/Themes pattern)
* inter-primitive calls stay file-local in the new module
* test_H7 anchor swaps from\nlocal function highestByRankto
\nlocal function pickLead(pickLead is deferred indefinitely
so the new anchor is stable)
* 11 test loader edits mirror the Batch 5B shape
Risk class MEDIUM (Batch 5B was VERY LOW, 5A was LOW). Failure
modes:
- Re-binding header mis-order
- ~155 lines of audit comments in tahreebClassify must move
verbatim
- test_H7 anchor must land at file-scope, not inside a body
Defer: pickLead/pickFollow/PickBid/memory/escalation deciders.
No runtime code changed.
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com - test(bel_quality): fix math.random shim arity dispatch
The standalone bel_decision_quality runner errored under Lupa when
Bot.lua picker code called math.random() (0 args) or math.random(n)
(1 arg) — the shim unconditionally forwarded as origRandom(a, b),
passing nil arguments that Lua's stdlib rejects.
Dispatch on argument presence:
math.random() → origRandom() — Fisher-Yates, probability
math.random(n) → origRandom(n) — shuffledSuits, pickRandomTied
math.random(-10, 10) → 0 — jitter freeze (unchanged)
math.random(a, b) → origRandom(a, b) — general 2-arg
The jitter freeze guard at the top still works as before because it
requires both args present.
Refresh bel_decision_quality.json with the now-real 1000-hand sweep
output (the prior file was stale since the runner errored before
producing data).
Full harness still 1117/1117.
Standalone smokepython tests/run_bel_decision_quality.pynow
completes the full sweep cleanly.
No runtime addon code changed — test-harness fix only.
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com - v3.2.0 cleanup batch 5B: extract Bot/Tiers.lua
Move the five tier-detection predicates out of Bot.lua into a new
Bot/Tiers.lua module:
* Bot.IsAdvanced
* Bot.IsM3lm
* Bot.IsFzloky
* Bot.IsSaudiMaster
* Bot.IsBotSeat
These are already public on the shared B.Bot.* table, so every
existing call site inside Bot.lua (Bot.IsAdvanced(), etc.)
continues to resolve throughlocal Bot = B.Botunchanged. No
re-binding header in Bot.lua is needed — the predicates simply
arrive on the shared table earlier in load order.
WHEREDNGN.toc: addBot/Tiers.luabetween State.lua and Bot.lua in
the# Game runtimesection.
Test loaders: 11 test files load Bot.lua directly. Each now also
loads Bot/Tiers.lua after State.lua and before Bot.lua:
* tests/test_state_bot.lua
* tests/test_botmaster.lua
* tests/test_multiseed_metrics.lua
* tests/test_asymmetric_metrics.lua
* tests/test_baseline_metrics.lua
* tests/probe_defender_strength.lua
* tests/test_bel_decision_quality.lua
* tests/test_v0.5_traced_game.lua
* tests/test_H1_pin_J9_trump.lua
* tests/test_numworlds_scaling.lua
* tests/test_H7_sun_shortest_lead.lua
The H7 test patches Bot.lua source manually; the new loadFile call
runs before the patched chunk compiles. The H7 anchor on
\nlocal function highestByRankis preserved by this batch — the
PlayPrimitives extraction that would invalidate it is deferred to
Batch 5C.
Tests (new section AJ.9d in test_state_bot.lua):
* 5 source pins asserting Bot/Tiers.lua defines each function
* 3 .toc-order pins asserting Bot/Tiers.lua loads before Bot.lua
Full harness: 1117/1117 pass (+8 from new AJ.9d asserts).
No behavior change. No release tag.
Tier API symmetry preserved: Bot.IsBotSeat is kept as a thin proxy
to S.IsSeatBot (NOT collapsed into direct S.IsSeatBot calls), per
the Tier 3 audit comment in Bot/Tiers.lua.
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com - docs: add v3.2.0 cleanup batch 5B design/inventory doc
Evaluates Bot-side decomposition slices after UI/Themes.lua.
Inventories two candidate areas:
* Bot/Tiers.lua (5 tier-detection functions, ~60 lines)
* Bot/PlayPrimitives.lua (10 hand-shape helpers, ~330 lines)
Cross-module coupling check confirms both blocks are consumed only
inside Bot.lua. BotMaster / Net / State / UI / Slash are uncoupled.
Source-pin sweep: zero existing pins on any of the 15 candidate
function names in test_state_bot.lua. One hard pin in
test_H7_sun_shortest_lead.lua (anchor on highestByRank) only affects
the PlayPrimitives option.
Recommended scope: Option A — extract Bot/Tiers.lua only. Smallest
slice that proves the Bot-side .toc extraction pattern. No Bot.lua
re-binding header needed because tiers are already on the public
B.Bot.* table.
Defer Option B (PlayPrimitives) to Batch 5C.
No runtime code changed.
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com - v3.2.0 cleanup batch 5A: extract UI/Themes.lua
Move the card/felt theme data + palette + theme helpers out of
UI.lua into UI/Themes.lua. UI.lua now binds the moved symbols as
file-locals from the shared U.Theme table near the top of the file,
so every existing call site (COL.feltDark, cardTexturePath(...),
applyThemeColors(), CARD_STYLES[name], etc.) resolves unchanged.
Moved:
* CARD_STYLES, FELT_THEMES (independent theme axes)
* COL palette table (shared reference; in-place mutations from
applyThemeColors() propagate to UI.lua readers automatically)
* migrateLegacyTheme (one-shot WHEREDNGNDB.cardTheme migration)
* activeCardStyleName / activeFeltThemeName resolvers
* cardStyleData / feltThemeData accessors
* applyThemeColors palette stamp
* CARD_TEX_DIR + cardTexturePath texture resolver
The file-load-time calls (migrateLegacyTheme(), applyThemeColors())
now run inside UI/Themes.lua, which the .toc loads immediately
before UI.lua. Identical ordering to the pre-extraction code, just
one file earlier in the chain.
Kept in UI.lua (still need shared upvalues f / tablePanel /
lobbyPanel / seatBadges / cardBackEntries):
* U.SetCardStyle, U.SetFeltTheme, U.SetTheme
* U.GetCardStyles, U.GetFeltThemes, U.GetThemes
* U.GetActiveCardStyle, U.GetActiveFeltTheme, U.GetActiveTheme
WHEREDNGN.toc: addUI/Themes.luato the# UIsection before
UI.lua.
Tests:
* AJ.9 (deck-name renames): now scans UI.lua + UI/Themes.lua
concatenated so the "4 Colors" / "Ba8ala SET" name assertions
stay valid no matter which file currently owns the table.
* New AJ.9c: source-pin that .toc lists UI/Themes.lua before
UI.lua (reordering would break load).
Full harness: 1109/1109 pass (+3 from new AJ.9c asserts).
No behavior change. No release tag.
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com - docs: add v3.2.0 cleanup batch 5 design/inventory doc
Inventory + decomposition proposal for the large UI/Bot surfaces.
No runtime code changed.
Recommended Batch 5A scope: extract UI/Themes.lua (CARD_STYLES,
FELT_THEMES, COL, theme helpers, ~180 lines moved out of UI.lua).
Pure data + almost-pure helpers with zero behavioral coupling and
a clean .toc load-order seam.
Explicit deferrals: pickLead/pickFollow, Bot._memory move,
PickBid, UI frame renderers, Net.lua/State.lua decomposition.
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com - v3.2.0 cleanup batch 4A: skip/preempt retry coverage
Per CLAUDE_V3_2_0_CLEANUP_BATCH4A_PROMPT.md scope. Tight 5-item
batch: 2 raw→helper migrations + 3 helper retry additions. No
UI/bot strategy/rule changes. SendSkipTriple/Four/Gahwa intentionally
remain one-shot (no clean post-apply guard available).
Net.lua senders changed:- _HostBelTimeout preempt_pass AFK path (Net.lua:5438 pre-fix)
Rawbroadcast(MSG_PREEMPT_PASS, seat)→N.SendPreemptPass(seat).
AFK path now inherits the v3.2.0 retry below. - MaybeRunBot bot SWA dispatch (Net.lua:6147 pre-fix)
Rawbroadcast(MSG_SWA_REQ, seat, enc)→N.SendSWAReq(seat, enc).
Bot SWA path now inherits the v3.1.12 SendSWAReq retry. - N.SendSkipDouble — retry added
Post-apply guard: phase==PHASE_DOUBLE AND belPending~=nil AND
not pendingContains(belPending, seat). Empty belPending (non-host
final-skip case where host echo hasn't returned yet) is accepted.
Receiver-side_OnSkipDoubleis idempotent on already-removed
seats. Triple/Four/Gahwa rungs remain one-shot — phase advances
immediately and no per-rung state token exists for a meaningful
post-apply guard. - N.SendPreempt — retry added with TWO-BRANCH guard (codex correction)
Two state orderings exist for preempt-claim:
(a) Non-host LocalPreempt applies state to PHASE_DEAL2BID/
contract=nil BEFORE send. If first frame drops, contract
never becomes Sun-by-seat locally — contract-only guard
would dead-code this case.
(b) Host/bot post-ApplyContract: contract.bidder == seat,
contract.type == SUN.
Guard:
(phase == PHASE_DEAL2BID and contract == nil) or
(contract and contract.bidder == seat and contract.type == SUN)
Receiver-side_OnPreemptis idempotent on already-removed seats. - N.SendPreemptPass — retry added
Guard: phase<mark>PHASE_PREEMPT AND not preemptContains(preemptEligible,
seat). preemptEligible</mark>nil (final-pass _FinalizePreempt) is
accepted. Receiver-side_OnPreemptPassis idempotent.
Tests added in AZ section:
- AZ.29e split into 3 pins (Triple/Four/Gahwa still one-shot)
- AZ.33a-e: SendSkipDouble initial + 3 retry-suppress branches
- empty-belPending final-skip non-host case
- AZ.34a-e: SendPreempt initial + branch (a) + branch (b) +
retry-suppress when neither branch holds - AZ.35a-d: SendPreemptPass initial + seat-removed + preemptEligible
-nil + retry-suppress when phase moves past PREEMPT - AZ.36a-c: bot SWA migration source-pin + helper retry verification
- AZ.37a-c: AFK preempt-pass migration source-pin verification
Tests: 1106/1106 pass (was 1084; +22 new pins: +2 AZ.29e split,
+20 AZ.33-37). Lua syntax: 26 files clean.
Explicitly deferred (Codex review):- SendSkipTriple/Four/Gahwa retry
- preempt window-open (seat=0, eligCsv) frame
- N.SendSWA direct claim/fallback
- SendSWAOut, SendTrick, SendRound, SendGameEnd
- MSG_DEAL redeal banner
- MSG_TAKWEESH_REVIEW, MSG_TAKWEESH_OUT
- Any new runtime state fields or protocol tags
Branch: v3.2.0-cleanup-batch4a
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com
- _HostBelTimeout preempt_pass AFK path (Net.lua:5438 pre-fix)
- docs: add v3.2.0 cleanup batch 4 design/inventory doc
Pre-implementation design pass inventorying remaining one-shot/raw
wire broadcasts around skip, preempt, and adjacent SWA/Takweesh
outcome paths.
Reviewed by Codex in CLAUDE_V3_2_0_BATCH4_DESIGN_REVIEW.md (not in
repo): approved with corrections, applied to this commit:
- Total raw/one-shot count corrected from 13 to 18 (matches table
rows: 4 + 4 + 3 + 7)
- Area D heading fixed from "5 sites" to "7 sites"
- Area C split: SendSWA helper, raw bot MSG_SWA_REQ, raw C_Timer-
unavailable MSG_SWA fallback now distinct rows
- SendPreempt mutation-order row corrected: LocalPreempt applies
BEFORE send but lands in PHASE_DEAL2BID/contract=nil on non-host
clients; bot path sends BEFORE host apply. Two orderings require
a two-branch post-send guard (Codex's critical correction)
- Summary numbers updated; codex-questions section replaced with
review-status outcome
Implementation will follow corrected scope in
CLAUDE_V3_2_0_CLEANUP_BATCH4A_PROMPT.md (not in repo): 5 items only
— 2 raw-broadcast → helper migrations + 3 helper retry additions.
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com - v3.2.0 cleanup batch 3: convert 4 source pins to behavioral
Per CLAUDE_V3_2_0_BATCH3_DESIGN_REVIEW.md scope. Test-only commit;
no runtime addon code changes.
AD.4a — BotMaster single-card-shortcut diagnostic
Source pin retired from tests/test_state_bot.lua. Behavioral test
added in tests/test_botmaster.lua section F.1 (F.1a-d): sets up
a single-card hand, calls BM.PickPlay(2), asserts the returned
card AND BM._lastShortCircuit == "single-card" AND
BM._lastWorldsCompleted == 0 AND Bot._inRollout restored.
AD.4b Slash message pin retained (out of scope per prompt).
AC.6 — PickFour unconditional +5 partner-open-Bel calibration
Source pin (literalstrength = strength + 5scan) replaced with
behavioral. Uses the codex-provided fixture
{ "QD", "JC", "8S", "7H", "8H", "9C", "AD", "JD" } for seat 2 at
PHASE_FOUR. math.random overridden to return 0 (zero jitter),
then restored. With +5 bonus applied: strength=80, jth=80 →
PickFour fires. Pre-fix removal of the +5 would drop strength
below jth → test fails loudly. Deterministic.
AI.6 — saveForPartnerTouch source pin retired
Per codex review, the existing AK.4 behavioral (touching-honors
save in pickFollow smother → donate QH instead of AH) already
exhaustively exercises this code path end-to-end. The AI.6 source
pin added no incremental coverage; deleted. AK.4 unchanged.
AC.4 + AC.5 — combined PickOvercall Bel-fear boundary
Per codex review: AC.4's source pin was stale/mislabelled (it
scanned for the OVERCALL strict-gate code, not PickDouble).
Both AC.4 and AC.5 protected the same overcall Bel-fear path —
replaced with one behavioral boundary test using H.13's PickOvercall
scaffolding (seat 3, hand AS/AH/AD/AC/TS/TH/TD/TC, bidcard 9C,
dealer 4, M3lm enabled). Two-direction assertion:
cumulative.A == 100 → Bot.PickOvercall(3) == "TAKE" (strict gate, not >=)
cumulative.A == 101 → Bot.PickOvercall(3) == "WAIVE" (Bel-fear flips it)
Proves both invariants AC.4 and AC.5 claimed.
Deferred (out of scope per design review):
- AH.4 BM-04-FALLBACK sampler internals (would require exporting
internal helpers — risk too high for the gain)
- Any other source-pin conversion not in the approved list
Tests: 1084/1084 pass. Net pin change vs main: +4 botmaster
behavioral (F.1a-d), -1 AD.4a source, ±0 AC.6 swap, -1 AI.6 source,
±0 AC.4+AC.5 swap = 1082 + 2 net.
Branch: v3.2.0-cleanup-batch3
Files changed: tests/test_botmaster.lua, tests/test_state_bot.lua
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com - docs: add v3.2.0 cleanup batch 3 design/inventory doc
Pre-implementation design pass for source-pin → behavioral
conversion. Inventories ~244 source-pin assertions, prioritizes 5
low-risk candidates with existing nearby behavioral scaffolding.
Reviewed and approved with corrections by Codex in
CLAUDE_V3_2_0_BATCH3_DESIGN_REVIEW.md (not in repo):
- AC.4 source pin is stale/mislabelled (matches overcall code,
not PickDouble) → combine AC.4+AC.5 into one PickOvercall
boundary test
- AC.6 needs deterministic RNG override
- AH.4 deferred (sampler internals not safely testable)
- AD.4a behavioral preferably in tests/test_botmaster.lua
Implementation will follow corrected scope in
CLAUDE_V3_2_0_CLEANUP_BATCH3_PROMPT.md (not in repo).
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com - v3.2.0 cleanup batch 2: extract retry broadcast helper
Per CLAUDE_V3_2_0_CLEANUP_BATCH2_PROMPT.md scope and the Codex design
review (CLAUDE_V3_2_0_BATCH2_DESIGN_REVIEW.md): mechanical
abstraction only, no behavior changes.
Helper added nearbroadcast(Net.lua:~58):
local function broadcastWithRetry(frame, guardFn)
broadcast(frame)
if not (C_Timer and C_Timer.After) then return end
C_Timer.After(0.25, function()
if guardFn() then broadcast(frame) end
end)
end
Module-local, not exported, not directly tested (per Codex's
"test through migrated senders" guidance).
Migrated 17 N.Send* sites:
- SendBidCard, SendTurn, SendBid, SendContract
- SendDouble, SendTriple, SendFour, SendGahwa (Pattern C — closure
capturescontractAtSendupvalue for post-apply identity guard)
- SendBelote, SendMeld, SendAKA
- SendSWAReq, SendSWAResp (the v3.1.14 LocalSWAResp(false) deny
path stays intact — its 0.35s delayed-clear timer lives outside
the helper)
- SendTakweesh, SendKawesh
- SendPlay, SendOvercallDecision
Each migration:
- Preserves the exact same wire frame string
- Preserves the exact same guard semantics (closure captures match
the previous inline gate)
- Preserves per-site documentation comments, updated to reference
broadcastWithRetry instead of inline C_Timer.After
Per R4 decision in the design doc: N._HostResolveOvercall's inline
retry (Net.lua:~2094) is intentionally LEFT UNTOUCHED. Removing it
would change the overcall MSG_CONTRACT rebroadcast count from 4
frames across ~0.5s to 2 frames. That's a behavior change; deferred
to a separate audit batch with explicit behavioral test coverage.
U.1 source pin therefore unchanged.
Source-pin updates (4 pins):
- AX.2: now looks forbroadcastWithRetry(in SendPlay body
- AX.3: now looks for PHASE_PLAY guard inside SendPlay (no longer
requires literal C_Timer.After)
- AX.5: now looks forbroadcastWithRetry(in SendOvercallDecision
- AX.6: now looks for PHASE_OVERCALL guard inside SendOvercallDecision
New behavioral tests (AZ.30, AZ.31, AZ.32 + suppress variants):
- AZ.30a-e: SendTurn initial + retry + 2 suppress cases (S.s.turn
changed, turnKind changed)
- AZ.31a-d: SendPlay initial + retry + suppress (phase moves past
PLAY)
- AZ.32a-d: SendOvercallDecision initial + retry + suppress (phase
moves past OVERCALL)
Tests: 1082/1082 pass (was 1065, +17 new behavioral pins). Lua
syntax: 26 files clean.
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com - docs: add v3.2.0 cleanup batch 2 design/inventory doc
Pre-implementation design pass for the retry-abstraction batch.
Inventories all 18 C_Timer.After(0.25) sites in Net.lua, categorizes
the guard patterns (A: phase-only, B: phase+state-identity, C:
contract-table-identity+post-apply, D: contract-by-value, E:
redundant recursive retry), proposes the broadcastWithRetry helper
API, and flags migration risks.
Reviewed and approved by Codex with corrections in
CLAUDE_V3_2_0_BATCH2_DESIGN_REVIEW.md (not in repo):
- R4: leave _HostResolveOvercall alone in this batch
- 4 source pins need updates (AX.2/3/5/6), not 2
- helper stays module-local, no direct tests
- 3 new behavioral tests needed (SendTurn/Play/OvercallDecision)
- drop optional delay param (every site uses 0.25)
Implementation will follow the corrected scope.
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com - v3.2.0 cleanup batch 1: remove dead helpers and extract skip senders
Per CLAUDE_V3_2_0_CLEANUP_BATCH1_PROMPT.md scope: mechanical cleanup
only, no gameplay behavior changes, no retry logic, no phase guards.
Task 1 — Bot.lua dead-code removal:- Bot.lua:2397 highestNonTrump — confirmed unused (grep hit only its
definition). Removed. - Bot.lua:7742 escalateDecision — confirmed unused (PickDouble/Triple
/Four/Gahwa each have their own inline yes-wantOpen computation
that doesn't call this helper). Removed.
Stale doc references in docs/strategy/glossary.md and
.swarm_findings/ point to old line numbers from prior versions; not
runtime callers. Glossary cleanup is a separate concern.
Task 2 — Net.lua skip-sender extraction: - Added N.SendSkipDouble/Triple/Four/Gahwa one-shot broadcast
helpers near other N.Send* functions. - Migrated 16 raw-broadcast call sites to the new helpers:
- N._OnDouble Race-A recovery (1 site)
- N.LocalSkipDouble/Triple/Four/Gahwa (4 sites)
- N._HostBelTimeout AFK paths (4 sites: double/triple/four/gahwa)
- N.MaybeRunBot bot Bel-decision botSkips loop (1 site)
- N.MaybeRunBot bot Triple-decision skip + error-recovery (2 sites)
- N.MaybeRunBot bot Four-decision skip + error-recovery (2 sites)
- N.MaybeRunBot bot Gahwa-decision skip + error-recovery (2 sites)
All migrations preserve existing state mutations around the call
sites. Helpers are intentionally one-shot — no retry, no phase
guards — matching prior single-emit semantics exactly. Retry
coverage for skip messages is deferred to a later batch per the
v3.2.0 cleanup plan.
Untouched per scope: MSG_PREEMPT_PASS, MSG_PREEMPT, MSG_SWA_REQ,
MSG_SWA, MSG_TAKWEESH_*, MSG_DEAL;redeal.
Task 3 — Tests (AZ.29a-e):
- AZ.29a: SendSkipDouble emits one MSG_SKIP_DBL frame
- AZ.29b: SendSkipTriple emits one MSG_SKIP_TRP frame
- AZ.29c: SendSkipFour emits one MSG_SKIP_FOR frame
- AZ.29d: SendSkipGahwa emits one MSG_SKIP_GHW frame
- AZ.29e: SendSkipDouble queues no C_Timer retry (one-shot by design)
Tests: 1065/1065 pass (was 1060, +5 helper-extraction tests).
Branch: v3.2.0-cleanup-batch1.
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com
- Bot.lua:2397 highestNonTrump — confirmed unused (grep hit only its

