File Details
v3.2.17
- R
- May 16, 2026
- 5.17 MB
- 8
- 12.0.5
- Retail
File Name
WHEREDNGN-v3.2.17.zip
Supported Versions
- 12.0.5
Loot & Baloot
v3.2.17 (2026-05-16)
Full Changelog Previous Releases
- docs(CHANGELOG): add v3.2.17 release notes
Combined release entry covering all unreleased mainline work after
v3.2.14: v3.2.15 non-host escalation/Belote/preempt action feedback,
v3.2.16 deal-finish desync guard + Codex trust-gate blocker, and
v3.2.17 bot-generated false AKA not auto-Takweeshed by opposing bots.
No v3.2.15/v3.2.16 tags; those ship together under v3.2.17.
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com - fix(Bot.lua): bot-generated false AKA not auto-Takweeshed by opposing bots (v3.2.17)
A bot caller of Bot.PickTakweesh must not auto-punish a "false AKA"
bluff whose offender seat is itself a bot. Bot noise-AKA
(Bot.PickAKANoise) is intentional uncertainty/flavor; auto-Takweeshing
the (often human-led) opposing team for another bot's bluff is
undesired. New narrow helper botFalseAkaImmune(p) gates on BOTH
illegalReason == "false AKA" AND a bot offender seat, applied to both
the completed-tricks scan and the current-trick scan. The completed-
trick scan is restructured to if/elseif so a bot-immune false AKA
cannot leak through the v1.5.1 realism gate.
Unchanged: human-offender false AKA is still caught; non-false-AKA
revoke/off-suit illegal plays are still caught (v1.5.1 realism gate);
Bot.PickAKANoise; State.ApplyPlay false-AKA marking; human/manual
TAKWEESH via HostBeginTakweeshReview / HostResolveTakweesh.
Tests: BM.1 and BM.6 (v3.2.6 fixtures, bot offender seat 2) flipped
in-place to expect nil under the new policy. New BW section: BW.1
(human-offender completed -> caught), BW.2 (human-offender current ->
caught), BW.3 control (bot-offender non-false-AKA revoke with later
reveal -> still caught), BW.4 source pins. Full harness 1499/0;
H1 11/0; H7 9/0.
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com - fix(Net.lua): trust-gate the v3.2.16 off-phase resync request (Codex blocker)
Codex re-review blocker on dealfinish-desync-v3.2.16: _OnPlay has no
top-level fromHost gate (peers legitimately broadcast their own
plays), so the v3.2.16 off-phase resync request fired for ANY
non-replay off-phase MSG_PLAY. On RAID/INSTANCE_CHAT an uninvited
addon sender could spoof a P;... frame and induce real players to
broadcast MSG_RESYNC_REQ.
Fix: the off-phase resync now only triggers for TRUSTED frames —
not isReplay and (fromHost(sender) or authorizeSeat(seat, sender)).
isReplayis hoisted above the phase guard and reused by the existing
downstreamif isReplay and S.s.isHostcheck (single declaration, no
behavior change to the in-phase path). Replay frames still never
self-trigger. _OnTurn already had a top-level fromHost gate so it is
unaffected and unchanged.
Tests: BV.7 (untrusted sender off-phase _OnPlay → NO resync, no play
applied) + BV.6f source pin for the trust condition. BV.2/BV.3 still
cover the trusted-host path. Harness 1489/0 (1486 + 3). H1 11/0,
H7 9/0.
Scope: single _OnPlay hunk. v3.2.15, F2/F3/F4, _OnContract/_OnLobby/
_OnHost, safeOnPlayObserved, SendTurn/_OnHeartbeat, bid-card,
raid-lobby untouched. No CHANGELOG/tag/release/packaging.
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com - fix(Net.lua): deal-finish desync guard — off-phase play → resync (v3.2.16)
Screenshots: host PHASE_PLAY/8-card while non-host stuck PHASE_DOUBLE/
5-card. Root cause: HostFinishDeal's final 8-card MSG_HAND (one-shot
whisper) and MSG_DEAL;play (one-shot broadcast) have no retry; if
either drops, the retry-protected MSG_TURN;play arrives underneath and
_OnTurn applied a play turn into a non-play phase (mixed state) while
_OnPlay silently dropped off-phase play frames — no recovery path
(heartbeat carries turn only; no runtime auto-resync). Hard stall
until manual /reload.
Minimal safe fix:- New non-host-only, one-shot/rate-limited (5s) helper
requestPlayDesyncResync() → existing N.SendResyncReq(gameID); never
mutates game state; logs + freezeLog("DESYNC"). Test handle
N._ResetPlayDesyncGuard (mirrors N._SafeOnPlayObserved convention). - _OnTurn: a "play" turn while not PHASE_PLAY → request resync and
return (do NOT ApplyTurn into a non-play phase → no mixed state). - _OnPlay: a non-replay play frame while not PHASE_PLAY → request
resync, then return (replay frames never self-trigger). The existing
off-phase return behaviour is preserved. - _OnDealPhase("play"): idempotent — only ApplyPlayPhase() if not
already PHASE_PLAY, so a duplicate/late MSG_DEAL;play cannot wipe an
in-flight trick.
The host's existing SendResyncRes replays full phase+hand → the
stranded client auto-heals in ~1 RTT.
Tests: new BV section (17 checks) — off-phase _OnTurn/_OnPlay request
resync without mutating state; one-shot burst → exactly 1 request;
in-phase _OnTurn(play) still applies with no resync; idempotent
MSG_DEAL;play preserves an in-flight trick while a legitimate
transition still installs a fresh one; source pins. Harness 1486/0
(1469 + 17). H1 11/0, H7 9/0.
Untouched: v3.2.15 (localrefresh), F2/F3/F4, _OnContract/_OnLobby/
_OnHost, safeOnPlayObserved, SendTurn/_OnHeartbeat, bid-card,
raid-lobby. No CHANGELOG/tag/release/packaging.
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com
- New non-host-only, one-shot/rate-limited (5s) helper
- fix(Net.lua): host-side escalation/preempt refresh + Belote host refresh (v3.2.15 blockers)
Codex review blockers on localrefresh-actions-v3.2.15:
Blocker 1 — host open/non-final escalation & preempt could stale:
the host branches that mutate state then call MaybeRunBot() with a
possibly-human next actor never refreshed the host's own UI
(MaybeRunBot only dispatches a bot). Added deferredRefresh() AFTER
MaybeRunBot on exactly these branches, leaving HostFinishDeal
branches untouched (they already refresh — no double refresh):
- LocalDouble (host else → open non-Sun Bel → MaybeRunBot)
- LocalTriple (host if open → MaybeRunBot)
- LocalFour (host if open → MaybeRunBot)
- LocalPreempt (host, SunBel allowed → MaybeRunBot)
- LocalPreemptPass (host, non-final → MaybeRunBot)
Blocker 2 — LocalBelote is announce-only with no host step. Changed
its v3.2.15 tail fromif not S.s.isHost then deferredRefresh() end
to an unconditionaldeferredRefresh()so the BALOOT! button hides
on the immediate repaint for the host actor too
(beloteAnnounced[localSeat]).
Tests: BU.11 adds host open/non-final coverage (Double/Triple/Four
open, Preempt SunBel-allowed, PreemptPass non-final each take
MaybeRunBot AND schedule a deferred refresh) + host LocalBelote
refresh. BU.10e pins exactly 5v3.2.15 blocker1host sites;
BU.10f pins LocalBelote unconditional + old non-host-only guard
removed. Non-host BU.1-9 unchanged. Harness 1469/0 (1430 + 39).
H1 11/0, H7 9/0.
Untouched: LocalBid/LocalPlay (v3.2.14), LocalGahwa host
(HostFinishDeal-only — no double refresh), request-only deferred
paths, F2/F3/F4, raid-lobby, bid-card, duplicate-play,
safeOnPlayObserved, SendTurn/_OnTurn/ApplyTurn. UI.lua unchanged
this round (Gahwa gate from prior commit retained).
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com - fix(Net/UI): extend non-host echo-gap feedback to escalation/Belote/preempt (v3.2.15 M1)
Audit M1: the v3.2.14 no-loopback fix was scoped to LocalBid/LocalPlay
only. Extend the same pattern to the remaining confirmed
self-applied non-host Local* actions.
Net.lua — host branch unchanged; add the v3.2.14else deferredRefresh()tail to:
LocalDouble, LocalTriple, LocalFour, LocalGahwa, LocalPreempt,
LocalPreemptPass
and an announce-onlyif not S.s.isHost then deferredRefresh() end
tail to LocalBelote (no host post-step). Each S.Apply* already
mutated the UI-gating state (s.phase for Double/Triple/Four/Preempt;
beloteAnnounced for Belote; preemptEligible for Preempt/Pass), so the
single deferred repaint also drops the now-stale affordance — no new
UI state introduced.
UI.lua — LocalGahwa is the one exception: ApplyGahwa sets
contract.gahwa but local phase only advances on the host echo, and
the PHASE_GAHWA action block was phase-only-gated. Add a minimal
not (S.s.contract and S.s.contract.gahwa)guard so the Gahwa/Skip
affordance is non-actionable in the echo gap (uses the
already-mutated flag; LocalGahwa is also idempotent).
Request-only paths deliberately NOT bare-patched (no S.Apply*; a bare
refresh would repaint identical actionable state): LocalSkipDouble
Triple/Four/Gahwa branches, LocalTakweesh, LocalKawesh, LocalOvercall
— deferred for a pending-state design (see audit report).
Untouched: LocalBid/LocalPlay (v3.2.14), LocalAKA/LocalDeclareMeld/
LocalSWA/LocalSWAResp/SkipDouble-PHASE_DOUBLE (already refresh),
F2/F3/F4, SendTurn/_OnTurn/ApplyTurn, safeOnPlayObserved, raid-lobby,
bid-card, duplicate-play.
Tests: new BU section (30 checks) — non-host deferred for all 7,
host paths still take their host-step (no non-host deferred), local
mutation applied, UI Gahwa gate pin, v3.2.14 markers intact pin.
Harness 1460/0 (1430 + 30). H1 11/0, H7 9/0.
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com

