feat: add persistent arena cards and market#39
Merged
Conversation
xmujx
added a commit
that referenced
this pull request
Jun 3, 2026
xmujx
added a commit
that referenced
this pull request
Jun 3, 2026
This was referenced Jun 4, 2026
#40) * feat(arena/tier): Bronze/Silver/Gold tier matchmaking + withdraw Adds G-balance tiers to Arena matchmaking, submission withdraw, and a leaderboard G + tier display, on top of the G economy / card market. Contract (ArenaEngine): - enum Tier + _tierFor (canonical, on-chain) reading GTreasury.gBalance - owner-tunable thresholds: setTierThresholds + tierThresholds() + event, DEFAULT_* constants as fallback (retune without an upgrade) - batched tierStates(ids) -> (tiers, balances) for one-RPC leaderboard hydration - per-tier pools, submit-time tier lock, withdrawSubmission (reverts once matched) - runMatchmaking(Tier) overload, setMatchmakingPeriod, activeMatchOf lock - new tier storage appended to preserve existing slot layout Frontend: - Router V3->V2->V1 ladder; hydrate tier + G via tierStates - gBalance/tier/tierFilter store fields; G column, B/S/G badges, filter pills Tests: ArenaTier 18/18; full arena/card/treasury suites green. * fix(arena): prevent double-matching across tier + legacy bucket Both matchmaking paths now skip ghosts already locked into a match (activeMatchOf != 0), and a tier match also removes its ghosts from the legacy ELO bucket. This closes two issues with the transitional dual-pool submit: a ghost could be paired twice (once per path) into concurrent matches, and the bucket never drained for tier-matched ghosts (eventually hitting the per-bucket cap and reverting submit). Legacy bucket matchmaking behavior for un-matched ghosts is unchanged. + 2 tests (bucket drains on tier match; matched ghosts skipped by tier). * refactor(arena): retire legacy ELO bucket, tier is the sole matchmaking path Fully removes the legacy ELO-bucket matchmaking (bucketGhosts, _addToBucket, _removeFromBucket, runMatchmaking(uint16), bucketSize/bucketOf, lastMatchmakingAt, MAX_BUCKET_SIZE/MATCHMAKING_PERIOD, GhostSubmitted/MatchmakingRan events). Tier (G-based) matchmaking is now the only pairing path. Why: the dual matchmaking system was both the root of the review's double-match report AND pushed the merged ArenaEngine over the 24,576-byte EIP-170 limit (26,347 -> 24,496, now deployable). Retiring the bucket fixes both at once and removes the never-drained pool entirely instead of guarding it. - submit() routes to the tier pool only; withdraw/_setElo no longer touch buckets - getGhost still returns an ELO band (elo/200) for display via _bucketIdFor (pure) - ArenaEngine.t.sol: bucket-only tests dropped; combat/settle/ELO tests now create matches via runMatchmaking(Tier.Silver) (all fixtures are funded 500 G = Silver) - frontend: drop the dead lastMatchmakingAt poll + bucket ABI entries * chore(arena): align event name with spec; drop dead event - Rename GhostSubmittedTier -> GhostSubmitted to match the #33 spec signature (event GhostSubmitted(uint256 indexed agentId, Tier tier, uint16 elo, uint256 gAtSubmit)). The legacy bucket GhostSubmitted is gone, so the name is free. - Remove the unused GTreasurySet event (its emitter was dropped when this PR switched to the card-market branch's setGTreasury). - Tidy a few stale storage-layout comments.
xmujx
added a commit
that referenced
this pull request
Jun 4, 2026
…37) (#51) * feat(arena/mcp): Arena Toolkit Phase 1 — 16 MCP tools + OWNER_KEYS + /arena skill Phase 1 (fully wired, works now): - 4 verb tools: arena_sell, arena_move, arena_freeze, arena_roll - 2 keeper tools: arena_run_matchmaking, arena_force_settle (OWNER_KEYS gated) - OWNER_KEYS env parsing in both stdio and HTTP entry points - Agent runner selfTools + system prompt updated with new verbs Phase 2 (scaffolded, awaiting #32 GTreasury/CardLedger + #33 Tier): - 10 tool registrations with full zod schemas and descriptions - chain.ts: ABI stubs for GTreasury + CardLedger + Tier - chain.ts: method stubs with typed signatures (throw until contracts deployed) - Wiring checklist: resolve contracts from Router.getAddressesV3() in ready() Claude Code skill: - .claude/commands/arena.md — /arena slash command with workflow guide + strategy Closes Phase 1 of #35. Phase 2 unblocks when #32 and #33 merge. * docs: add Phase 2 tools to /arena skill List all 21 arena tools in the skill reference, grouped by category. Phase 2 tools (G currency, market, tier) marked with issue refs. Added market and tier workflow sections. * fix: align Phase 2 stubs with PR #39 and #40 actual implementations * test: add E2E script for arena MCP tools (24/24 pass on fresh chain) * feat(arena/mcp): add missing tools + arena gameplay guide New tools: - arena_simulate_match: full turn-by-turn combat replay - arena_preview_elo: preview ELO delta without committing - arena_get_card: single card details (#32) - arena_set_matchmaking_period: owner-only tier cooldown (#33) Docs: - docs/arena-guide.md: complete agent gameplay guide covering card flow, 12 units, combat mechanics, ability timing, 3 archetypes, positioning strategy, tier system, secondary market, and full tool reference - Updated /arena skill with new tools * docs: arena guide in skill.md + CLAUDE.md entry link - skill.md: add condensed Arena section (card flow, combat, units, strategy, tool table, tier system) for agent system prompt - CLAUDE.md: add Arena entry with link to docs/arena-guide.md - docs/arena-guide.md kept as the complete reference - .claude/commands/arena.md: add simulate_match, preview_elo, get_card, set_matchmaking_period to tool table * test: add full arena E2E script (requires #39 contracts) * fix: replace ore refs with G, remove hardcoded prices in arena descriptions * fix: remove hardcoded tier thresholds, make owner-configurable * fix: remove stale arena_sell/freeze/roll tools and old ABI entries These were from the pre-persistent-card era and don't exist in the current ArenaEngine contract. * fix: sync MCP ABI with tier-based matchmaking contracts, remove duplicate code - ARENA_ENGINE_ABI: GhostSubmitted/runMatchmaking/MatchmadeInTier use uint8 tier (was uint16 bucket — different selector/topic hash, calls reverted silently) - G_TREASURY_ABI: use creditG (onlyOperator) instead of fundAgentG (onlyOwner) - Delete duplicate G_TREASURY_ABI, CARD_LEDGER_ABI const declarations - Delete Phase 2 stubs section (~150 lines of duplicate methods + throw stubs) - Delete 7 duplicate tool registrations in tools.ts - Wire fund_agent_g, arena_get_tier_info, arena_withdraw_submission, arena_set_matchmaking_period as working implementations - Net -306 lines, 0 TS errors * feat: add arena_deposit_g tool from PR #52 GTreasury.depositG(agentId) payable — lets agent owners deposit native testnet tokens into their Arena G balance. Adds ABI entry, chain method, and MCP tool. * docs: sync arena guide and skill.md with current tool names - Core loop: add arena_deposit_g, remove deleted tools (sell/move/freeze/roll) - Tool tables: arena_fund_g → fund_agent_g, bucket_id → tier, remove stale entries - skill.md: update card flow and tool roster * refactor: consolidate admin tools into gated section with [ADMIN] prefix Move set_oracle_agent, fund_agent_g, arena_set_matchmaking_period into a single admin block with isOwnerCall() guards and [ADMIN] description prefix. Update arena-guide.md and skill.md to match. * fix: remove duplicate arenaDepositG from merge * docs: add missing arena_cancel_listing and arena_get_card to skill.md
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.
摘要
GTreasury和CardLedger,支持 Arena G 余额、持久卡背包、二级市场 list / buy / cancel。inventory / 背包、bench / 阵容、marketplace / 二级市场。arena_buy现在只把卡买进背包;新增arena_place_card上阵、arena_remove_card下阵。arena_buy_listing默认补buyer_agent_id,arena_list_market不再错误补agent_id。Closes #32
怎么验证 E2E
本 PR 新增了一个可复现的本机链 E2E 脚本:
它会在 fresh Anvil 链上自己部署代理合约、创建 demo agents、fund G,并验证完整链路:
Step 1:启动本机链
Terminal 1:
这里使用
8555是为了不影响已有的8545本机链;如果你想用8545,后面的脚本参数也改成http://127.0.0.1:8545即可。Step 2:编译合约 artifacts
Terminal 2:
cd contracts forge build脚本会读取
contracts/out/**.json里的 ABI 和 bytecode,所以需要先 build。Step 3:运行 E2E 脚本
继续在 Terminal 2:
cd ../mcp-server node scripts/e2e-arena-card-flow.mjs http://127.0.0.1:8555如果你使用默认私钥以外的本机链账号,可以传
PRIVATE_KEY:Step 4:确认输出
成功时会看到:
E2E 覆盖的 Case
Case 1:买卡只进背包
步骤:
ArenaEngine.buy(seller, unitType)。CardLedger.getOwnedCards(seller)。ArenaEngine.getGhost(seller)和getGhostCards(seller)。预期:seller G 扣对应 unit cost;主世界 ore 不变;新
cardId出现在 seller inventory;bench 仍为空,cardIds仍全 0。Case 2:背包卡可以上阵
步骤:调用
ArenaEngine.placeCard(seller, cardId, 0),再查 seller bench 和 cardIds。预期:
bench[0] == card.unitType,cardIds[0] == cardId,卡 owner 仍是 seller。Case 3:上阵卡不能挂市场
步骤:卡在 bench 时调用
CardLedger.listCard(seller, cardId, price)。预期:revert
card on bench。Case 4:从阵容取下,不退款
步骤:记录 seller G,调用
ArenaEngine.removeCard(seller, 0),再查询 G、bench、cardIds、inventory。预期:seller G 不变;bench/cardIds 清空;inventory 仍包含
cardId。Case 5:背包卡挂市场、取消、重新挂
步骤:
listCard-> 查 active listings ->cancelListing-> 再查 listings/inventory -> 再次listCard。预期:listing 出现、取消后消失、卡仍归 seller、重新挂单成功。
Case 6:市场购买失败路径
步骤:分别验证 max price 低、余额不足、自买。
预期:revert
price too high、insufficient G、self buy。Case 7:市场成交
步骤:buyer 调
CardLedger.buyListed(buyer, cardId, 100),再查 G、card owner、seller/buyer inventory、active listings。预期:buyer G 减 100;seller G 加 100;
card.ownerAgent == buyer;seller inventory 移除该卡;buyer inventory 包含该卡;listing 关闭。Case 8:市场买来的卡可以上阵
步骤:buyer 从 marketplace 买到
cardId后,调用ArenaEngine.placeCard(buyer, cardId, 2)。预期:
bench[2] == card.unitType,cardIds[2] == cardId。Case 9:已挂市场的卡不能上阵
步骤:seller 买一张新卡进 inventory,挂到 marketplace,再调用
placeCard。预期:revert
card listed。Case 10:阵容满了仍然可以买卡进背包
步骤:buyer 放满 5 个 bench slot,再调用
ArenaEngine.buy(buyer, unitType)。预期:新卡进入 buyer inventory;bench 仍是原来的 5 张卡,不被改动。
Case 11:market bootstrap 只能执行一次
步骤:再次调用
ArenaEngine.bootstrapMarket(seedAgentId)。预期:revert
already seeded。本次实测记录
我已经按上面的步骤在 fresh Anvil 本机链上跑过:
结果:E2E 全链路通过。
说明:当前环境下 Foundry CLI 的
cast/forge script访问 localhost 会返回 502,但curl和 Node/ethers 能直连 Anvil。因此本机链 E2E 使用ethers部署并调用合约方法,验证的是同一套合约链上状态流。其他验证
npm run buildinmcp-server:通过。npm run buildinagent-runner:通过。arena_place_card自动补agent_id。arena_buy_listing自动补buyer_agent_id。arena_list_market不补agent_id。forge test --match-contract 'CardLedgerTest|BenchInvariantTest|ArenaEngineTest' -vv:44 passed。forge test -vv:74 passed / 2 failed。GameEngineTestincite 用例:test_InciteCooldown和test_InciteReducesHappiness,都是hex unclaimed,不在 Arena 卡市场范围内。