{"id":50787004,"url":"https://github.com/maskdotdev/ygo-ai-bench","last_synced_at":"2026-06-12T08:33:03.697Z","repository":{"id":356130248,"uuid":"1225162537","full_name":"maskdotdev/ygo-ai-bench","owner":"maskdotdev","description":"Yu-Gi-Oh! deck builder and playtest engine with card search, validation, simulation, and browser-agent automation hooks.","archived":false,"fork":false,"pushed_at":"2026-06-11T01:31:30.000Z","size":34929,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-11T03:14:16.740Z","etag":null,"topics":["ai-benchmark","deck-builder","game-engine","typescript","yugioh"],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/maskdotdev.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-30T02:25:22.000Z","updated_at":"2026-06-11T01:32:24.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/maskdotdev/ygo-ai-bench","commit_stats":null,"previous_names":["maskdotdev/ygo-ai-bench"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/maskdotdev/ygo-ai-bench","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maskdotdev%2Fygo-ai-bench","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maskdotdev%2Fygo-ai-bench/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maskdotdev%2Fygo-ai-bench/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maskdotdev%2Fygo-ai-bench/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/maskdotdev","download_url":"https://codeload.github.com/maskdotdev/ygo-ai-bench/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maskdotdev%2Fygo-ai-bench/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34236551,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-12T02:00:06.859Z","response_time":109,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai-benchmark","deck-builder","game-engine","typescript","yugioh"],"created_at":"2026-06-12T08:33:02.597Z","updated_at":"2026-06-12T08:33:03.686Z","avatar_url":"https://github.com/maskdotdev.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Duel Deck Studio — Yu-Gi-Oh! Deck Builder\n\nA polished browser app for building Yu-Gi-Oh! decks with real card data and images from the public YGOPRODeck API. The deck builder remains static, while the playtest arena now runs as a React/TanStack Router surface.\n\n## Features\n\n- Live card search across name, effect text, type, race, attribute, and archetype.\n- Card thumbnails and detail inspector with stats, effect text, prices, and banlist status.\n- Main / Extra / Side Deck zones with drag-and-drop, quantity controls, smart add, and move actions.\n- Real-time deck validation:\n  - Main Deck: 40–60 cards.\n  - Extra Deck: up to 15 cards.\n  - Side Deck: up to 15 cards.\n  - Maximum copies across Main + Extra + Side.\n  - TCG / OCG / GOAT Forbidden, Limited, and Semi-Limited status from the API.\n  - Extra Deck monsters routed/validated correctly.\n  - Token and Skill cards hidden by default.\n- IndexedDB card-data cache to reduce API traffic after the first load.\n- Local deck save/load.\n- Standard `.ydk` import/export.\n- Responsive dark UI with keyboard-friendly controls.\n\n## How to run\n\nFor the static deck builder, open `index.html` in a browser, or serve the folder locally:\n\n```bash\ncd yugioh-deck-builder\npython3 -m http.server 8080\n```\n\nThen open `http://localhost:8080`.\n\nFor the React playtest arena, use Bun:\n\n```bash\nbun install\nbun run dev\n```\n\nThen open the local Vite URL and visit `/playtest.html`.\n\n## API notes\n\nThe app uses the YGOPRODeck Card Information endpoint at runtime. The API docs ask developers to cache downloaded data and avoid continually hotlinking images in production. This prototype caches card JSON in IndexedDB; a production deployment should re-host card images or proxy them through your own image cache.\n\n## Agent bridge\n\nAfter the page loads, the app exposes `window.duelDeckAgent` for browser agents and automation. The bridge provides structured access to card search, current deck state, deck mutation, validation, analysis, and YDK import/export without scraping the UI.\n\nUseful calls:\n\n```js\nwindow.duelDeckAgent.status()\nwindow.duelDeckAgent.searchCards('Blue-Eyes special summon', { limit: 20, full: true })\nwindow.duelDeckAgent.getDeck({ includeCards: true })\nwindow.duelDeckAgent.addCard('89631139', 'main', 3)\nwindow.duelDeckAgent.validateDeck()\nwindow.duelDeckAgent.analyzeDeck()\nwindow.duelDeckAgent.simulateHands({ trials: 20, handSize: 5, seed: 42 })\nwindow.duelDeckAgent.playtest.legalActionGroups()\nwindow.duelDeckAgent.playtest.runScripted([{ type: 'normalSummon', labelIncludes: 'Magician' }])\nwindow.duelDeckAgent.exportYdk()\nwindow.duelDeckAgent.importYdk('#main\\n89631139\\n#extra\\n!side\\n')\n```\n\n`simulateHands()` performs a non-mutating opening-hand dry run from the current Main Deck. It draws 5 cards by default, classifies likely starters, extenders, searchers, disruption, removal, draw power, and bricks, then returns per-hand strategy lines plus aggregate consistency rates. Pass `handIds` to evaluate a specific known hand instead of drawing randomly.\n\n## TypeScript playtest engine\n\nThe framework-agnostic playtest engine lives under `src`. The `/playtest.html` arena consumes it through a React/TanStack Router app styled with Tailwind.\n\n```ts\nimport { chooseHighestPriority, parseYdk, runPlaytest, snapshot, startPlaytest } from './src/playtest';\n\nconst ydk = parseYdk(deckText);\nconst session = startPlaytest({ deck: ydk.main, extraDeck: ydk.extra, seed: 42, handSize: 5 });\nconst view = snapshot(session);\nconst result = runPlaytest(session, chooseHighestPriority, 10);\n```\n\n`snapshot(session)` and `applyAction(session, action)` return `legalActions` plus `legalActionGroups`. UI and browser agents should render or choose from those engine-provided actions directly; grouping is presentation metadata, not a separate legality system.\n\nThe full duel engine exposes the same pattern through `getDuelLegalActions(session, player)`, `groupDuelLegalActions(actions)`, and the convenience `getGroupedDuelLegalActions(session, player)`. Grouped full-duel actions preserve `windowId` and `windowKind`, so UI code can render prompt, chain-response, trigger-bucket, battle, and open-game windows without rebuilding timing rules.\n\nFull-duel automation can reuse the fixture selector path with `selectDuelActionBySelector(actions, selector, cards)` or apply a sequence with `runScriptedDuelResponses(session, selectors)`. The scripted runner returns `failedStep`, `failure`, `divergencePlayer`, `divergenceWindowId`, `divergenceWindowKind`, `divergenceGroupKey`, and `divergenceGroupLabel` when the legal-action window diverges.\n\nRun checks with:\n\n```bash\nbun run check\n```\n\nOr run individual checks with:\n\n```bash\nbun run check:loc\nbun run scan:lua-parity\nbun run typecheck\nbun run test\nbun run build\n```\n\n`bun run check` includes the combined Lua parity scanner and the official chain-limit predicate-shape scanner. To inspect missing EDOPro Lua APIs, constants, or unclassified chain-limit predicates against a local Project Ignis card-script checkout, clone scripts into the ignored upstream workspace and run the scanners directly:\n\n```bash\ngit clone --depth 1 https://github.com/ProjectIgnis/CardScripts .upstream/ignis/script\nbun run scan:lua-api -- --limit 50\nbun run scan:lua-constants -- --fail-on-missing\nbun run scan:lua-chain-limits -- --limit 80 --fail-on-unclassified\nbun run scan:lua-parity\n```\n\nThese scanners are parity guardrails. Missing APIs or constants should become implementation work or fixture-backed parity backlog, not permanent exclusions from the engine target.\nThe chain-limit scanner classifies official `Duel.SetChainLimit` and `Duel.SetChainLimitTillChainEnd` predicate shapes so restore coverage can be compared against the current Project Ignis corpus.\n\n`bun run build` emits the React playtest page and `dist/playtest-engine.js`, which exposes `window.duelDeckPlaytest` in the browser. If that bundle is loaded, the existing `window.duelDeckAgent.playtest` bridge can start, inspect, step, and auto-run playtest sessions from the current deck.\n\nFor browser automation, `window.duelDeckAgent.playtest.runScripted(steps, sessionId)` accepts fixture-style action selectors (`type`, `uid`, `id`, `effectId`, `labelIncludes`, `occurrence`) and returns `failedStep`, `failure`, `divergenceGroupKey`, and `divergenceGroupLabel` when the current legal-action group diverges from the script.\n\n## Duel snapshot persistence\n\nThe full duel engine exposes `serializeDuel(session)` and `restoreDuel(snapshot)` for deterministic test fixtures, browser handoff, and long-running playtest state. Snapshots contain serializable duel state only; callback functions are intentionally stripped.\n\nCurrent restore behavior:\n\n- Static continuous effects persist automatically when they have no callback-driven activation, cost, target, or operation.\n- Lua card effects should be restored with `restoreDuelWithLuaScripts(snapshot, source, cardReader)`. The helper reloads required card scripts, registers their `initial_effect` functions, keeps only Lua registry keys that existed in the snapshot, and reports `restoreComplete`, `restoredRegistryKeys`, `missingRegistryKeys`, and Lua chain-limit predicate diagnostics. Browser reconnect code should treat `restoreComplete === false` as unsafe for legal-action display because missing Lua callbacks can expose illegal responses.\n- Manual TypeScript effects with callbacks must provide a stable `registryKey` and be restored with a `DuelEffectRestoreRegistry` passed to `restoreDuel(snapshot, cardReader, registry)`.\n- Effects without a static shape or registry key are omitted from snapshots by design, because replaying arbitrary closures would not be browser-safe or deterministic.\n\nMinimal Lua restore guard:\n\n```ts\nimport { getLuaRestoreLegalActionGroups, getLuaRestoreLegalActions, restoreDuelWithLuaScripts, serializeDuel } from './src/engine';\n\nconst snapshot = serializeDuel(session);\nconst restored = restoreDuelWithLuaScripts(snapshot, scriptSource, cardReader);\n\nif (!restored.restoreComplete) {\n  // Do not expose legal actions from this session until the missing scripts or callbacks are resolved.\n  console.warn(restored.missingRegistryKeys, restored.missingChainLimitRegistryKeys);\n}\n\nconst legalActions = getLuaRestoreLegalActions(restored, 0);\nconst legalActionGroups = getLuaRestoreLegalActionGroups(restored, 0);\n```\n\nMinimal manual registry example:\n\n```ts\nimport { restoreDuel, serializeDuel, type DuelEffectRestoreRegistry } from './src/engine';\n\nconst snapshot = serializeDuel(session);\nconst registry: DuelEffectRestoreRegistry = {\n  'manual:draw-once': (saved) =\u003e ({\n    ...saved,\n    operation: ({ session }) =\u003e {\n      session.state.log.push({ type: 'effect', message: 'restored manual effect resolved' });\n    },\n  }),\n};\n\nconst restored = restoreDuel(snapshot, cardReader, registry);\n```\n\n## Parity fixture metadata\n\nScripted duel fixtures should label committed expectation blocks with `source: 'edopro'` when they are meant to prove parity:\n\n```ts\nafter: {\n  source: 'edopro',\n  note: 'EDOPro keeps optional if triggers available after mandatory when triggers enter the chain',\n  legalActionCounts: { 0: 1, 1: 0 },\n  legalActionGroupCounts: { 0: 1, 1: 0 },\n  legalActions: [{ type: 'activateTrigger', player: 0, effectId: 'fixture-optional-if', count: 1 }],\n  legalActionGroups: [\n    {\n      player: 0,\n      label: 'Trigger Activations',\n      windowKind: 'triggerBucket',\n      actions: [{ type: 'activateTrigger', player: 0, effectId: 'fixture-optional-if', count: 1 }],\n    },\n  ],\n  absentLegalActionGroups: [\n    {\n      player: 0,\n      label: 'Trigger Declines',\n      windowKind: 'triggerBucket',\n      actions: [{ type: 'declineTrigger', player: 0, effectId: 'fixture-mandatory-when' }],\n    },\n  ],\n}\n```\n\nUse `source: 'edopro'` only for expectations backed by observed EDOPro behavior. `source: 'parity-backlog'` is reserved for scanner diagnostics or temporary local investigation; committed parity fixtures are currently gated to zero backlog blocks. Every sourced expectation block should include a `note` that names the EDOPro behavior being preserved so the fixture stays attached to implementation work. For UI-facing timing behavior, assert raw `legalActions`, aggregate `legalActionCounts`, grouped `legalActionGroups`, and aggregate `legalActionGroupCounts`; use `absentLegalActions` and `absentLegalActionGroups` when an illegal response must not be exposed.\n\n## Included decks\n\n- `dark-magical-blast-master-duel-day1.ydk` — 40-card Master Duel Day 1 Dark Magician upgrade path for two Dark Magical Blast structure decks plus Dragoon/Verte staples.\n- `dark-magical-blast-tcg-branded-dm.ydk` — TCG-valid Branded Dark Magician variant for the app's TCG validator.\n\n## Files\n\n- `index.html` — app shell and accessible markup.\n- `styles.css` — responsive, polished UI.\n- `app.js` — card loading, deck state, validation, import/export, drag/drop.\n- `playtest.html` — React/TanStack Router playtest arena shell.\n- `src/playtest-app` — Tailwind-styled playtest UI.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmaskdotdev%2Fygo-ai-bench","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmaskdotdev%2Fygo-ai-bench","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmaskdotdev%2Fygo-ai-bench/lists"}