promotional bannermobile promotional banner

Loot & Baloot

Loot & Baloot — Play Saudi Baloot (بلوت) inside World of Warcraft. A four-player trick-taking card game over the party addon channel. Host with friends or play solo against bots, with full Hokm / Sun rules, melds, escalations, and SWA claims.

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)).
    isReplay is hoisted above the phase guard and reused by the existing
    downstream if isReplay and S.s.isHost check (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
  • 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 from if not S.s.isHost then deferredRefresh() end
    to an unconditional deferredRefresh() 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 5 v3.2.15 blocker1 host 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.14 else deferredRefresh() tail to:
    LocalDouble, LocalTriple, LocalFour, LocalGahwa, LocalPreempt,
    LocalPreemptPass
    and an announce-only if 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