Conversation
Collapse the 5-command lobby grammar to 3 (createdLobby, joinedLobby, closedLobby) so users have a single action to create or join. The second join auto-starts the game in the same state-machine transition, and open lobbies drop off /open_lobbies 10 minutes after creation. - Grammar: drop toggledReady, startedGame, leftLobby, askedForCard; drop maxPlayers from createdLobby (always 2 players). - State machine: joinedLobby validates open status + not-full, inserts the player, and fires the startGame query in the same transition on real insert. closedLobby verifies host + alone + status=open, then deletes the lobby and its lobby_players rows. Lobby IDs are now deterministic (blockHeight + addr slug) instead of Date.now(). - DB: drop lobbies.max_players and lobby_players.is_ready via new migration 3_simplify_lobbies. Rewrite lobby-queries.sql with new CountLobbyPlayers, DeleteLobbyPlayers, DeleteLobby; regenerate pgtyped bindings. - API: /open_lobbies filters created_at > NOW() - INTERVAL '10 minutes' and drops max_players from SELECT. - Frontend: remove ready/start/leave UI. LobbyListScreen has a single Create/Join flow; LobbyScreen shows Cancel only to the host while alone and auto-navigates on status=in_progress. effectstreamBridge exports closeLobby instead of toggleReady/startGame/leaveLobby. - Tests: api.test.ts adds /open_lobbies shape, TTL (9min keep, 11min hide), and no-max_players assertions; mock pool extended accordingly. - e2e: runLobbyFlow simplified to create → join → wait for in_progress. - Docs: README and CLAUDE.md reflect the 3-command set. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Eight incremental smoke tests isolate each layer of the e2e pipeline (imports, fs config, indexer subscription, lobby via batcher, init_deck, applyMask with secrets, full per-game setup, one-turn play) so failures localize quickly without running the full ~20 min game. test-witnesses.ts is a self-contained witness implementation for the Go Fish contract. It replaces the default @go-fish/midnight-contract witnesses in the e2e path: explicit per-session WitnessState, throws on missing secrets instead of silently falling back to random Math.random static keys, and captures each player's shuffle seed during player_secret_key so the deck shuffle is deterministic per (game, player). deno.json adds the import-map entries needed by the smokes and the shared e2e helpers (rxjs, @paimaexample/concise, midnight-js providers, compact-js, local @go-fish/data-types and midnight-contract aliases). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… double-count Three contract changes resolve BACKEND_ISSUES #3 (game stalls in TurnStart with empty hand) and #4 (winner ledger never written): 1. GoFish.compact:addScore — replace the 7-book cap with an early-win at score ≥ 4 (majority of 7). Also fixes a read-after-write double-count bug: Compact's queryLedgerState returns the post-write value on re-read, so the old `lookup → insert → lookup → +1` pattern was triggering game-over at 3 books instead of 4. Now computes post-increment scores from the pre-read locally. 2. game.compact:askForCard — relax rule 5 when the asker's hand is empty. Routes empty-hand turns through respondToAsk's existing go-fish branch (no new circuit needed). Matches real Go Fish rules. 3. game.compact — add top-level getWinner wrapper so the managed contract exposes the V4 winner ledger reader. Client: e2e helpers drop the empty-hand bail, game-round test adds hard assertions for finalPhase==GameOver and winner∈{1,2}. Validated: 164/164 contract tests pass, 27/27 node tests pass, ec_mul guard detection clean, 3 consecutive sim runs all end at exactly 4 books with correct winner. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…arden witnesses Phase 0 (EVM lobby alignment): - Remove maxPlayers from GoFishGameState type and in-memory state (DB column dropped per EVM_UPDATE.md; game is always 2 players) Phase 1 (critical fixes): - Fix callAfterGoFish: remove stale drewRequestedCard param (4→3 args). Contract v3 decrypts the drawn card internally (game.compact:451-510). Updated across all 6 files in the call chain (GoFishContractService, MidnightService, MidnightOnChainService, midnightBridge, GameScreen, GameScene). - Delete callGoFish / goFish: no goFish circuit exists in the compiled contract. respondToAsk handles the draw internally. Turn flow is now: askForCard → respondToAsk → (if WaitForDrawCheck) afterGoFish. - Allow empty-hand asks: contract rule-5 relaxation permits asking for any rank when hand is empty. GameScreen now shows an "Ask for a card" button instead of the old passive "Drawing will happen automatically" message that stalled the game. Phase 2 (book scoring — without this, games never end): - Add callCheckAndScoreBook to GoFishContractService (batcher delegation) - Add onChainCheckAndScoreBook to MidnightOnChainService - Add checkAndScoreBook routing to MidnightService - Add autoScoreBooks() to GameScreen: after respondToAsk and afterGoFish, scans the player's hand for ranks with ≥3 cards and submits checkAndScoreBook for each. The contract's addScore checks ≥4 books and sets phase=GameOver + winner. Phase 3 (winner display): - Update renderGameOverPanel to use ≥4 threshold for winner determination (matches the contract's early-win rule). Added TODO for reading getWinner from the contract once direct indexer access is available. Phase 5 (witness hardening): - Remove silent Math.random() fallback from witnesses.ts getSecretKey and getShuffleSeed. Both now throw immediately on a miss with a descriptive error naming the game+player that's missing. Delete the static `keys` object that powered the fallback (20-bit random values that produced wrong ec_mul results). See BACKEND_ISSUES.md #1. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Introduces zk-time-estimator.ts: circuit→k table (captured from `compact compile +0.30.0`), REFERENCE_MS sourced from ZKTIME.md, and calibrateFromProof that folds each observed proof into a running-mean scaleFactor persisted to localStorage. Wires calibration end-to-end: midnightBrowserProofProvider now exposes getLastProveDurationMs(); callDelegated resets it pre-call and feeds the measured duration into calibrateFromProof after every proof on a CIRCUIT_K-known circuit. GlobalLoader gains a countdown suffix — callDelegated passes the per-circuit expectedProof() time during proving and a flat 20s during awaiting-confirmation. Overtime is rendered as +Ns in amber. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Subscribe to go-fish-contract ledger state via parallelMidnight sync protocol and PrimitiveTypeMidnightGeneric; event_midnight handler logs the decoded payload as a first observability hook. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Rework dealCards so both players can submit simultaneously without stale-read proof conflicts. dealCards now consumes disjoint fixed deck indices (P1→0..3, P2→4..7) instead of read-incrementing the shared gameTopCardIndex counter, only touches its own hasDealt flag, and no longer transitions phase. A new exported startGame(gameId, playerId) circuit owns the Setup → TurnStart transition — self-dedups via assert(phase == Setup) so repeated calls are safe. - Deck.compact: pre-seed gameTopCardIndex to 8 in init_deck so post-setup draws start at the first undealt index without dealing ever reading the counter. - game.compact: inline the decrypt+store pipeline into dealCard with an explicit cardIndex parameter; drop getTopCardForOpponent; add the startGame export. ec_mul guard rule preserved — partial_decryption is still called unconditionally from the unrolled dealCard loop. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Score panel, game log, and turn bar now paint titles + shimmering "Loading…" text the moment the HUD mounts, instead of sitting as empty backing boxes until the first game-state poll settles. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When a ZK proof or tx send runs past its estimated duration, the bottom-left
loader now shows an orange subtitle at +2.0s ("Taking more than expected, please
wait…") and swaps to a longer reassurance message at +10.0s so users on slow
hardware know the app is still working.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Active Games sidebar was rendering a deterministic 4-card mock hand (seeded from the lobby id) whenever no real hand was cached yet. Users saw card previews that didn't match the actual game state. Also hardens the session→state fallback path to skip rendering until playerId resolves to a real (1|2) value. - Remove mockMiniHand entirely; miniHandFor now returns an empty array when there's no authoritative data. - Only render the .mini-hand strip when there's at least one real card to show (or a +N overflow chip) — otherwise omit it entirely. - Guard buildActiveGamesFromCacheAndSessions against the playerId=0 sentinel so we don't compute `state.scores[-1]` during the post-join indexing window. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- LobbyListScreen.joinLobby: clear pendingJoinLobbyId BEFORE dispatching navigate. dispatchEvent is synchronous; UIManager's listener called show() → innerHTML='' → render(), which bailed on the still-set pending flag and left the sidebar blank. - GameSession: add refreshSnapshot() public method that emits a stateChange with empty changes. GameScene.attach calls it after hydrate so loader/turn-indicator render immediately instead of waiting for a real on-chain change that may never come. Also emitted when setupPhase flips to 'done' so rejoins of already-set-up games don't strand the view with setupPhase='idle' snapshot. - Setup-flow canvas log entries: deduplicated "waiting for opponent" lines plus concrete progress markers (mask applied / deal confirmed / start submitted) so the user sees meaningful progress during the 30–120s setup window. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cards taken by the opponent or scored into a book previously vanished instantly from the hand — only a camera shake and toast notification signalled the loss. Now the departing cards arc up and forward with a slight spin and shrink out over ~900ms so the player actually sees the card go. - animateCardLeave: new gsap tween (arc + spin + scale-to-zero) in the 800ms– 1000ms band — the "fast enough to feel responsive, slow enough to be seen" range. - CardHand.detachCards: split matching cards out of the hand without disposing them so the caller can reparent and animate independently. - ThreeApp.animateCardLoss: detach by rank+suit, scene.attach to preserve world transform across the upcoming setCards clear(), animate, dispose on complete. - GameScene.onStateChange: diff previous vs current myHand on handChanged and call animateCardLoss before setPlayerHand so the remaining cards re-close the fan immediately instead of waiting for the animation. - Idle demo REMOVE branch: 50/50 between fly-to-deck and the new leave animation so the menu scene showcases both. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Setup can block the UI for 30–90s while proofs generate (applyMask + dealCards), but the 3D canvas previously sat static the whole time — users had no signal that the app was still working. The deck now visibly shuffles through a rotating sequence of 5 motions until setup completes. - New ShuffleAnimations module with 5 variants (riffle, overhand, cascade, table spread, Hindu) and a non-repeating orchestrator. Returns a stop fn that kills in-flight tweens and settles every card back to its rest pose in ≤350ms so the subsequent deal animation inherits a clean stack. - Respects prefers-reduced-motion: single slow cascade, no variant cycling. - Deck3D exposes getCardMeshes() for the orchestrator to snapshot. - ThreeApp.startDeckShuffle/stopDeckShuffle — start is kill-and-restart so repeated calls re-snapshot after setDeckCount rebuilds the card meshes. - ThreeApp.setDeckCount auto-restarts the shuffle when one is active so the new card meshes replace the now-disposed snapshot. - GameScene: start shuffle unconditionally in mount() (the host's pre-P2 window has null contract state — a phase='dealing' gate would never fire); stop it once snapshot.setupPhase flips to 'done' or we detect attach to an already-in-progress game; stop on scene clear. - GameScene.mount now also clears the idle-demo hand + resets deck count so a freshly-created lobby doesn't inherit random cards from the menu scene. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Users could previously click Create Lobby, Join, Resume, or Dismiss while a
proof was running or a tx was posting, triggering either a racing second tx
or an orphaned operation (the loader then pointed at a game the user had
just left). Every game-switch / game-mutation entry point is now guarded.
- GlobalLoader: new isTxInFlight() (true for 'proving' or 'sending', not
for passive 'waiting') and onTxInFlightChange(cb) subscription API that
fires on every transition including those hidden by mute.
- txGuard.ts: ensureNotBusy() shows a centered orange toast ("Blockchain
operation in progress — please wait.") and returns false. guardedClick()
wraps a handler for drop-in guarding.
- main.ts: subscribes at bootstrap to toggle body.tx-in-flight so every
.tx-guarded element visibly dims (0.55 opacity, not-allowed cursor).
- LobbyListScreen: guards Create (confirm button), Join (tx path), Resume,
Join-with-Rejoin (foreground swap), and Dismiss. All the same buttons
also get the .tx-guarded class for visible disabled state.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…PC sync Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Update all @paimaexample dependencies from 0.11.1/0.10.24 to 0.11.3 and add Google Tag Manager (GTM-KJX5XHXL) to the frontend index.html.
…11.3-and-gtm chore: upgrade @paimaexample/* to 0.11.3 and add GTM
Added admin_secret_ky circuit stub
update to effectstream 0.100.21 and bun migration
…nd build, gateway routing)
Follow-up fixes found while deploying the effectstream 0.100.21 + bun migration
to mainnet. Each is required for go-fish to actually run on the new contract.
- node: add missing `wit_split_card_index` witness to actionWitnesses. The
compiled contract requires it; without it `initializeActionContract` throws
and the node crash-loops once sync reaches live game-action events. Mirrors
the frontend midnightBridge witness (index = suit*7 + rank, returned as
bigints).
- evm: fix deploy.mainnet.ts — it still imported the deleted GoFishLobby module
and referenced result.paimaL2Contract. Point it at effectstreaml2-module on
the arbitrum network so the mainnet deploy works.
- deployed_addresses.json + frontend VITE_PAIMA_L2_CONTRACT_ADDRESS: record the
redeployed mainnet effectstreaml2 contract 0x148bC957bFbCd344A50ec9cC08654c6e2A9656c2.
- frontend/vite.config.ts: nodePolyfills `overrides: { fs: memfs, 'node:fs': memfs }`
so server-only modules pulled into the bundle (get-wallet-info via
@effectstream/wallets) resolve named fs exports instead of the empty mock.
Matches block-kart. Without it `build:mainnet` fails.
- config.mainnet.ts: route EVM reads through ARBITRUM_ONE_RPC (the local
evm-gateway cache) instead of arbitrum's default public RPC, matching the
other migrated games.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ixes fix: complete 0.100.21 mainnet deploy (witness, deploy script, frontend build, gateway routing)
Follow-up to #8. The node constructs the Midnight contract in two places — actionWitnesses (midnight-actions.ts, fixed in #8) and queryWitnesses (midnight-query.ts, missed). Both must declare every witness the compiled contract requires, or `new Contract(...)` throws and the node crash-loops. initializeQueryContract was still dying on the missing wit_split_card_index. Added as a throwing stub matching the other query witnesses (the query contract only reads ledger state, never runs circuits). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fix: add wit_split_card_index to queryWitnesses (follow-up to #8)
After the witness fixes (#8, #9) the node still crash-looped — but on a different, silent exit(1) during live Midnight event processing. Traced via a process.exit/unhandledRejection probe to: TypeError: Cannot close a writable stream that is closed or errored at close (@seriousme/opifex/dist/socket/socket.js:33) <- this.writer.close() at close (.../mqttConn/mqttConn.js:171) at #receive (.../mqttConn/mqttConn.js:131) <- MQTT client teardown -> unhandledRejection -> @effectstream/log process-handlers.ts:48 -> process.exit(1) opifex's socket close() wraps `this.writer.close()` in a synchronous try/catch ("swallow any errors on close"), but `WritableStreamDefaultWriter.close()` returns a Promise that rejects *asynchronously* when the stream is already closed. The sync try/catch can't catch it; under bun the floating rejection is fatal (deno tolerated it, which is why this only bites post-bun-migration). Patch opifex in patch.sh (same mechanism we already use for hardhat/fetch-blob) to add `.catch(() => {})` so the benign async close rejection is swallowed too. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fix: swallow async opifex MQTT socket close rejection (node crash loop)
update effectstream to 0.100.23
Patch @seriousme/opifex socket close() and write() with .catch()
…address-format Patch @midnight-ntwrk/wallet-sdk-address-format 3.x for the frontend bundle.
…Size-reset Fix: Frontend case locked `isHandReady`
update to effectstream 0.100.26
Update/effectstream 0.100.28
Bump effectstream top 0.101.1
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.