- Mark completed tasks in Phase F4 - Track live gameplay implementation progress Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1017 lines
35 KiB
JSON
1017 lines
35 KiB
JSON
{
|
|
"meta": {
|
|
"version": "1.1.0",
|
|
"created": "2026-01-30",
|
|
"lastUpdated": "2026-02-01",
|
|
"planType": "master",
|
|
"projectName": "Mantimon TCG - Frontend",
|
|
"description": "Vue 3 + Phaser 3 frontend for pocket.manticorum.com - real-time multiplayer TCG with campaign mode",
|
|
"totalPhases": 8,
|
|
"completedPhases": 5,
|
|
"status": "Phase F4 COMPLETE - Ready for Polish & UX (F5)"
|
|
},
|
|
|
|
"techStack": {
|
|
"framework": "Vue 3 (Composition API + <script setup>)",
|
|
"gameEngine": "Phaser 3",
|
|
"language": "TypeScript",
|
|
"stateManagement": "Pinia",
|
|
"styling": "Tailwind CSS",
|
|
"realtime": "Socket.io-client",
|
|
"buildTool": "Vite",
|
|
"testing": "Vitest + Vue Test Utils",
|
|
"e2e": "Playwright (future)"
|
|
},
|
|
|
|
"architectureDecisions": {
|
|
"vuePhaser": {
|
|
"pattern": "Phaser mounts as Vue component, communication via event bridge",
|
|
"rationale": "Keep Vue for UI/state, Phaser for game canvas rendering only",
|
|
"communication": "EventEmitter pattern - Vue emits intentions, Phaser emits completion"
|
|
},
|
|
"stateManagement": {
|
|
"pattern": "Pinia stores as single source of truth, Phaser reads from stores",
|
|
"stores": ["auth", "user", "game", "deck", "collection", "ui"]
|
|
},
|
|
"apiLayer": {
|
|
"pattern": "Composables for API calls, automatic token refresh",
|
|
"structure": "useApi() base, useAuth(), useDecks(), useGames() domain composables"
|
|
},
|
|
"socketIO": {
|
|
"pattern": "Singleton connection manager with Pinia integration",
|
|
"reconnection": "Auto-reconnect with exponential backoff, queue actions during disconnect"
|
|
},
|
|
"routing": {
|
|
"guards": "Auth guard redirects to login, game guard ensures valid game state",
|
|
"lazyLoading": "Route-level code splitting for Phaser scenes"
|
|
}
|
|
},
|
|
|
|
"sitePlan": {
|
|
"designApproach": "Mobile-first, desktop is first-class citizen",
|
|
"navigation": {
|
|
"desktop": "Left sidebar",
|
|
"mobile": "Bottom tabs",
|
|
"items": [
|
|
{"icon": "home", "label": "Home", "route": "/"},
|
|
{"icon": "cards", "label": "Collection", "route": "/collection"},
|
|
{"icon": "deck", "label": "Decks", "route": "/decks"},
|
|
{"icon": "play", "label": "Play", "route": "/play"},
|
|
{"icon": "user", "label": "Profile", "route": "/profile"}
|
|
]
|
|
},
|
|
"layouts": {
|
|
"minimal": {
|
|
"routes": ["/login", "/auth/callback"],
|
|
"description": "No navigation, centered content"
|
|
},
|
|
"game": {
|
|
"routes": ["/game/:id"],
|
|
"description": "Full viewport for Phaser, no nav"
|
|
},
|
|
"default": {
|
|
"routes": ["*"],
|
|
"description": "Sidebar (desktop) / bottom tabs (mobile)"
|
|
}
|
|
},
|
|
"routes": [
|
|
{"path": "/login", "name": "Login", "auth": "guest", "layout": "minimal", "notes": "OAuth buttons, redirect if logged in"},
|
|
{"path": "/auth/callback", "name": "AuthCallback", "auth": "none", "layout": "minimal", "notes": "Silent token handling"},
|
|
{"path": "/starter", "name": "StarterSelection", "auth": "required", "layout": "minimal", "notes": "Redirect here if no starter deck"},
|
|
{"path": "/", "name": "Dashboard", "auth": "required", "layout": "default", "notes": "Home with widgets (future)"},
|
|
{"path": "/collection", "name": "Collection", "auth": "required", "layout": "default", "notes": "Card grid with filters"},
|
|
{"path": "/decks", "name": "DeckList", "auth": "required", "layout": "default", "notes": "List + create button"},
|
|
{"path": "/decks/new", "name": "NewDeck", "auth": "required", "layout": "default", "notes": "Deck builder (empty)"},
|
|
{"path": "/decks/:id", "name": "EditDeck", "auth": "required", "layout": "default", "notes": "Deck builder (existing)"},
|
|
{"path": "/play", "name": "PlayMenu", "auth": "required", "layout": "default", "notes": "Game mode selection"},
|
|
{"path": "/game/:id", "name": "Game", "auth": "required", "layout": "game", "notes": "Phaser canvas, full viewport"},
|
|
{"path": "/profile", "name": "Profile", "auth": "required", "layout": "default", "notes": "Display name, avatar, linked accounts, logout"}
|
|
],
|
|
"authFlow": {
|
|
"loggedOut": "Redirect to /login",
|
|
"loggedInNoStarter": "Redirect to /starter",
|
|
"loggedIn": "Allow access to protected routes"
|
|
},
|
|
"futureRoutes": [
|
|
{"path": "/campaign", "backendDep": "Phase 5"},
|
|
{"path": "/campaign/club/:id", "backendDep": "Phase 5"},
|
|
{"path": "/matchmaking", "backendDep": "Phase 6"},
|
|
{"path": "/history", "backendDep": "Phase 6"},
|
|
{"path": "/packs", "backendDep": "Phase 5"}
|
|
],
|
|
"dashboardWidgets": {
|
|
"planned": [
|
|
"Active games (resume)",
|
|
"Quick play button",
|
|
"Recent matches",
|
|
"Collection stats",
|
|
"Daily rewards (Phase 5+)",
|
|
"News/announcements"
|
|
],
|
|
"v1": ["Quick play button", "Active games"]
|
|
},
|
|
"profilePage": {
|
|
"v1Features": [
|
|
"Avatar display",
|
|
"Display name (editable)",
|
|
"Linked accounts list",
|
|
"Logout button"
|
|
]
|
|
}
|
|
},
|
|
|
|
"phases": [
|
|
{
|
|
"id": "PHASE_F0",
|
|
"name": "Project Foundation",
|
|
"status": "COMPLETE",
|
|
"completedDate": "2026-01-30",
|
|
"description": "Scaffolding, tooling, core infrastructure, API client setup",
|
|
"estimatedDays": "3-5",
|
|
"dependencies": [],
|
|
"backendDependencies": ["PHASE_2"],
|
|
"deliverables": [
|
|
"Vite + Vue 3 + TypeScript project structure",
|
|
"Tailwind CSS configuration",
|
|
"Vue Router with lazy loading",
|
|
"Pinia store scaffolding",
|
|
"API client with auth header injection",
|
|
"Socket.IO client wrapper",
|
|
"Environment configuration (dev/staging/prod)",
|
|
"App shell with responsive layout",
|
|
"ESLint + Prettier configuration"
|
|
],
|
|
"tasks": [
|
|
{
|
|
"id": "F0-001",
|
|
"name": "Initialize Vite project",
|
|
"description": "Create Vue 3 + TypeScript project with Vite",
|
|
"files": ["package.json", "vite.config.ts", "tsconfig.json"],
|
|
"details": [
|
|
"npm create vite@latest . -- --template vue-ts",
|
|
"Configure path aliases (@/ for src/)",
|
|
"Set up TypeScript strict mode"
|
|
]
|
|
},
|
|
{
|
|
"id": "F0-002",
|
|
"name": "Install and configure Tailwind",
|
|
"description": "Set up Tailwind CSS with custom theme",
|
|
"files": ["tailwind.config.js", "src/assets/main.css"],
|
|
"details": [
|
|
"Install tailwindcss, postcss, autoprefixer",
|
|
"Configure content paths",
|
|
"Add Mantimon color palette (Pokemon-inspired)"
|
|
]
|
|
},
|
|
{
|
|
"id": "F0-003",
|
|
"name": "Set up Vue Router",
|
|
"description": "Configure routing with guards and lazy loading",
|
|
"files": ["src/router/index.ts", "src/router/guards.ts"],
|
|
"details": [
|
|
"Define route structure",
|
|
"Create auth guard placeholder",
|
|
"Configure lazy loading for heavy routes"
|
|
]
|
|
},
|
|
{
|
|
"id": "F0-004",
|
|
"name": "Set up Pinia stores",
|
|
"description": "Create store structure with persistence",
|
|
"files": ["src/stores/auth.ts", "src/stores/user.ts", "src/stores/ui.ts"],
|
|
"details": [
|
|
"Install pinia and pinia-plugin-persistedstate",
|
|
"Create auth store skeleton",
|
|
"Create user store skeleton",
|
|
"Create UI store (loading states, toasts)"
|
|
]
|
|
},
|
|
{
|
|
"id": "F0-005",
|
|
"name": "Create API client",
|
|
"description": "HTTP client with auth token injection and refresh",
|
|
"files": ["src/api/client.ts", "src/api/types.ts"],
|
|
"details": [
|
|
"Create fetch/axios wrapper",
|
|
"Inject Authorization header from auth store",
|
|
"Handle 401 responses with token refresh",
|
|
"Type API responses"
|
|
]
|
|
},
|
|
{
|
|
"id": "F0-006",
|
|
"name": "Create Socket.IO client",
|
|
"description": "WebSocket connection manager",
|
|
"files": ["src/socket/client.ts", "src/socket/types.ts"],
|
|
"details": [
|
|
"Install socket.io-client",
|
|
"Create connection manager singleton",
|
|
"Configure auth token in handshake",
|
|
"Set up reconnection with exponential backoff",
|
|
"Create typed event emitters"
|
|
]
|
|
},
|
|
{
|
|
"id": "F0-007",
|
|
"name": "Create app shell",
|
|
"description": "Basic layout with navigation",
|
|
"files": ["src/App.vue", "src/layouts/DefaultLayout.vue", "src/components/NavBar.vue"],
|
|
"details": [
|
|
"Responsive header/nav",
|
|
"Main content area with router-view",
|
|
"Loading overlay component",
|
|
"Toast notification area"
|
|
]
|
|
},
|
|
{
|
|
"id": "F0-008",
|
|
"name": "Environment configuration",
|
|
"description": "Configure environment variables",
|
|
"files": [".env.development", ".env.production", "src/config.ts"],
|
|
"details": [
|
|
"VITE_API_BASE_URL",
|
|
"VITE_WS_URL",
|
|
"VITE_OAUTH_REDIRECT_URI",
|
|
"Type-safe config access"
|
|
]
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": "PHASE_F1",
|
|
"name": "Authentication",
|
|
"status": "COMPLETE",
|
|
"completedDate": "2026-01-30",
|
|
"description": "OAuth login flow, token management, protected routes",
|
|
"estimatedDays": "2-3",
|
|
"dependencies": ["PHASE_F0"],
|
|
"backendDependencies": ["PHASE_2"],
|
|
"deliverables": [
|
|
"Login page with OAuth buttons",
|
|
"OAuth callback handling",
|
|
"Token storage and auto-refresh",
|
|
"Auth store implementation",
|
|
"Protected route guards",
|
|
"User profile display",
|
|
"Logout functionality"
|
|
],
|
|
"tasks": [
|
|
{
|
|
"id": "F1-001",
|
|
"name": "Create login page",
|
|
"description": "OAuth login buttons for Google and Discord",
|
|
"files": ["src/pages/LoginPage.vue"],
|
|
"details": [
|
|
"Google OAuth button with icon",
|
|
"Discord OAuth button with icon",
|
|
"Redirect to backend /api/auth/{provider}",
|
|
"Handle loading states"
|
|
]
|
|
},
|
|
{
|
|
"id": "F1-002",
|
|
"name": "Handle OAuth callback",
|
|
"description": "Parse tokens from URL fragment after OAuth redirect",
|
|
"files": ["src/pages/AuthCallbackPage.vue", "src/composables/useAuth.ts"],
|
|
"details": [
|
|
"Extract access_token, refresh_token from URL fragment",
|
|
"Store tokens in auth store",
|
|
"Redirect to intended destination or home",
|
|
"Handle OAuth errors"
|
|
]
|
|
},
|
|
{
|
|
"id": "F1-003",
|
|
"name": "Implement token refresh",
|
|
"description": "Auto-refresh tokens before expiry",
|
|
"files": ["src/stores/auth.ts", "src/api/client.ts"],
|
|
"details": [
|
|
"Track token expiry time",
|
|
"Refresh proactively before expiry",
|
|
"Handle refresh failures (logout)",
|
|
"Queue requests during refresh"
|
|
]
|
|
},
|
|
{
|
|
"id": "F1-004",
|
|
"name": "Implement auth guards",
|
|
"description": "Protect routes requiring authentication",
|
|
"files": ["src/router/guards.ts"],
|
|
"details": [
|
|
"requireAuth guard - redirect to login",
|
|
"requireGuest guard - redirect authenticated users away from login",
|
|
"Store intended destination for post-login redirect"
|
|
]
|
|
},
|
|
{
|
|
"id": "F1-005",
|
|
"name": "Create user profile display",
|
|
"description": "Show logged-in user info in nav",
|
|
"files": ["src/components/UserMenu.vue", "src/stores/user.ts"],
|
|
"details": [
|
|
"Fetch user from /api/users/me on login",
|
|
"Display avatar and name in nav",
|
|
"Dropdown with profile link and logout"
|
|
]
|
|
},
|
|
{
|
|
"id": "F1-006",
|
|
"name": "Implement logout",
|
|
"description": "Clear tokens and redirect to login",
|
|
"files": ["src/stores/auth.ts"],
|
|
"details": [
|
|
"Call /api/auth/logout to revoke refresh token",
|
|
"Clear local token storage",
|
|
"Disconnect WebSocket",
|
|
"Redirect to login page"
|
|
]
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": "PHASE_F2",
|
|
"name": "Deck Management",
|
|
"status": "COMPLETE",
|
|
"completedDate": "2026-01-31",
|
|
"auditStatus": "PASSED",
|
|
"auditDate": "2026-01-31",
|
|
"description": "Collection viewing, deck building, starter deck selection",
|
|
"estimatedDays": "5-7",
|
|
"dependencies": ["PHASE_F1"],
|
|
"backendDependencies": ["PHASE_3"],
|
|
"deliverables": [
|
|
"Starter deck selection flow",
|
|
"Collection grid view",
|
|
"Card display component",
|
|
"Deck list page",
|
|
"Deck builder with drag-and-drop",
|
|
"Real-time validation feedback",
|
|
"Card detail modal"
|
|
],
|
|
"tasks": [
|
|
{
|
|
"id": "F2-001",
|
|
"name": "Create card component",
|
|
"description": "Reusable card display component",
|
|
"files": ["src/components/cards/CardDisplay.vue", "src/components/cards/CardImage.vue"],
|
|
"details": [
|
|
"Display card image from CDN/local",
|
|
"Show card name, HP, type",
|
|
"Type-colored border/badge",
|
|
"Hover state with subtle animation",
|
|
"Support multiple sizes (thumbnail, medium, large)"
|
|
]
|
|
},
|
|
{
|
|
"id": "F2-002",
|
|
"name": "Create card detail modal",
|
|
"description": "Full card view with all details",
|
|
"files": ["src/components/cards/CardDetailModal.vue"],
|
|
"details": [
|
|
"Large card image",
|
|
"Full attack descriptions",
|
|
"Weakness/resistance/retreat",
|
|
"Owned quantity display",
|
|
"Close on backdrop click or Escape"
|
|
]
|
|
},
|
|
{
|
|
"id": "F2-003",
|
|
"name": "Implement starter deck selection",
|
|
"description": "First-time user flow to pick starter",
|
|
"files": ["src/pages/StarterSelectionPage.vue", "src/composables/useStarter.ts"],
|
|
"details": [
|
|
"Check starter status on app load",
|
|
"Display 5 starter deck options with preview",
|
|
"POST /api/users/me/starter-deck on selection",
|
|
"Redirect to main app after selection",
|
|
"Cannot proceed without selecting"
|
|
]
|
|
},
|
|
{
|
|
"id": "F2-004",
|
|
"name": "Create collection page",
|
|
"description": "Grid view of owned cards",
|
|
"files": ["src/pages/CollectionPage.vue", "src/stores/collection.ts"],
|
|
"details": [
|
|
"Fetch collection from /api/collections/me",
|
|
"Filterable grid (by type, rarity, set)",
|
|
"Search by card name",
|
|
"Show quantity owned",
|
|
"Click to open detail modal"
|
|
]
|
|
},
|
|
{
|
|
"id": "F2-005",
|
|
"name": "Create deck list page",
|
|
"description": "View all user decks",
|
|
"files": ["src/pages/DecksPage.vue", "src/stores/deck.ts"],
|
|
"details": [
|
|
"Fetch decks from /api/decks",
|
|
"Display deck cards with validation status",
|
|
"Create new deck button",
|
|
"Edit/delete deck actions",
|
|
"Show starter deck badge"
|
|
]
|
|
},
|
|
{
|
|
"id": "F2-006",
|
|
"name": "Create deck builder",
|
|
"description": "Add/remove cards from deck",
|
|
"files": ["src/pages/DeckBuilderPage.vue", "src/components/deck/DeckEditor.vue"],
|
|
"details": [
|
|
"Two-panel layout: collection picker + deck contents",
|
|
"Drag-and-drop or click to add/remove",
|
|
"Energy deck configuration",
|
|
"Live validation with error messages",
|
|
"Save/cancel buttons",
|
|
"Confirm discard unsaved changes"
|
|
]
|
|
},
|
|
{
|
|
"id": "F2-007",
|
|
"name": "Implement deck validation feedback",
|
|
"description": "Real-time validation as user edits",
|
|
"files": ["src/composables/useDeckValidation.ts"],
|
|
"details": [
|
|
"Debounced validation on changes",
|
|
"Call /api/decks/validate endpoint",
|
|
"Display errors inline",
|
|
"Disable save if invalid",
|
|
"Show card count progress (40/40)"
|
|
]
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": "PHASE_F3",
|
|
"name": "Phaser Integration",
|
|
"status": "COMPLETE",
|
|
"completedDate": "2026-01-31",
|
|
"description": "Game rendering foundation - Phaser setup, board layout, card objects",
|
|
"estimatedDays": "7-10",
|
|
"dependencies": ["PHASE_F2"],
|
|
"backendDependencies": ["PHASE_4"],
|
|
"deliverables": [
|
|
"Phaser mounted as Vue component",
|
|
"Vue-Phaser event bridge",
|
|
"Scene structure (Preload, Match)",
|
|
"Asset loading system",
|
|
"Game board layout with all zones",
|
|
"Card game objects with interactions",
|
|
"Responsive canvas scaling"
|
|
],
|
|
"tasks": [
|
|
{
|
|
"id": "F3-001",
|
|
"name": "Install and configure Phaser",
|
|
"description": "Add Phaser 3 to project",
|
|
"files": ["package.json", "src/game/config.ts"],
|
|
"details": [
|
|
"Install phaser",
|
|
"Create Phaser game configuration",
|
|
"Configure WebGL with canvas fallback",
|
|
"Set up asset base path"
|
|
]
|
|
},
|
|
{
|
|
"id": "F3-002",
|
|
"name": "Create Phaser Vue component",
|
|
"description": "Mount Phaser game in Vue component",
|
|
"files": ["src/components/game/PhaserGame.vue"],
|
|
"details": [
|
|
"Create/destroy Phaser game on mount/unmount",
|
|
"Pass game instance to parent via ref",
|
|
"Handle resize events",
|
|
"Emit ready event when game initialized"
|
|
]
|
|
},
|
|
{
|
|
"id": "F3-003",
|
|
"name": "Create Vue-Phaser event bridge",
|
|
"description": "Bidirectional communication system",
|
|
"files": ["src/game/bridge.ts", "src/composables/useGameBridge.ts"],
|
|
"details": [
|
|
"Vue -> Phaser: game.events.emit('card:play', data)",
|
|
"Phaser -> Vue: game.events.on('animation:complete', cb)",
|
|
"Type-safe event definitions",
|
|
"Automatic cleanup on unmount"
|
|
]
|
|
},
|
|
{
|
|
"id": "F3-004",
|
|
"name": "Create scene structure",
|
|
"description": "Phaser scenes for game flow",
|
|
"files": ["src/game/scenes/PreloadScene.ts", "src/game/scenes/MatchScene.ts"],
|
|
"details": [
|
|
"PreloadScene: Load all assets with progress bar",
|
|
"MatchScene: Main game rendering",
|
|
"Scene transitions",
|
|
"Scene data passing"
|
|
]
|
|
},
|
|
{
|
|
"id": "F3-005",
|
|
"name": "Implement asset loading",
|
|
"description": "Load card images and UI assets",
|
|
"files": ["src/game/assets/loader.ts", "src/game/assets/manifest.ts"],
|
|
"details": [
|
|
"Card image loading (lazy load as needed)",
|
|
"UI sprite atlas",
|
|
"Board background",
|
|
"Type icons",
|
|
"Loading progress display"
|
|
]
|
|
},
|
|
{
|
|
"id": "F3-006",
|
|
"name": "Create board layout",
|
|
"description": "Position all game zones",
|
|
"files": ["src/game/objects/Board.ts", "src/game/layout.ts"],
|
|
"details": [
|
|
"Player zones: active, bench (5 slots), deck, discard, prizes (6)",
|
|
"Opponent zones: mirrored layout",
|
|
"Hand area (bottom)",
|
|
"Zone highlighting for valid targets",
|
|
"Responsive positioning"
|
|
]
|
|
},
|
|
{
|
|
"id": "F3-007",
|
|
"name": "Create card game objects",
|
|
"description": "Interactive card sprites",
|
|
"files": ["src/game/objects/Card.ts", "src/game/objects/CardBack.ts"],
|
|
"details": [
|
|
"Card face with image",
|
|
"Card back for hidden cards",
|
|
"Hover effects (scale, glow)",
|
|
"Click/tap handling",
|
|
"Drag support for hand cards",
|
|
"Damage counters display"
|
|
]
|
|
},
|
|
{
|
|
"id": "F3-008",
|
|
"name": "Implement responsive scaling",
|
|
"description": "Canvas adapts to screen size",
|
|
"files": ["src/game/scale.ts"],
|
|
"details": [
|
|
"Maintain aspect ratio",
|
|
"Scale to fit container",
|
|
"Handle window resize",
|
|
"Mobile-friendly touch areas"
|
|
]
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": "PHASE_F4",
|
|
"name": "Live Gameplay",
|
|
"status": "COMPLETE",
|
|
"completedDate": "2026-02-01",
|
|
"description": "WebSocket integration, game state sync, action handling, complete game flow",
|
|
"estimatedDays": "10-14",
|
|
"dependencies": ["PHASE_F3"],
|
|
"backendDependencies": ["PHASE_4"],
|
|
"deliverables": [
|
|
"Game creation flow",
|
|
"WebSocket connection with auth",
|
|
"Game state rendering",
|
|
"Hand and card interactions",
|
|
"Attack selection UI",
|
|
"Turn phase management",
|
|
"Action dispatch to server",
|
|
"Opponent state display",
|
|
"Game over handling"
|
|
],
|
|
"tasks": [
|
|
{
|
|
"id": "F4-001",
|
|
"name": "Create game lobby page",
|
|
"description": "Start or join a game",
|
|
"files": ["src/pages/GameLobbyPage.vue"],
|
|
"details": [
|
|
"Create new game with deck selection",
|
|
"Show active games (for rejoining)",
|
|
"Simple invite flow (share game ID for now)",
|
|
"POST /api/games to create"
|
|
]
|
|
},
|
|
{
|
|
"id": "F4-002",
|
|
"name": "Implement WebSocket game connection",
|
|
"description": "Connect to game via Socket.IO",
|
|
"files": ["src/socket/gameSocket.ts", "src/stores/game.ts"],
|
|
"details": [
|
|
"Connect with JWT auth",
|
|
"Emit game:join on connect",
|
|
"Handle game:state events",
|
|
"Handle game:error events",
|
|
"Store game state in Pinia"
|
|
]
|
|
},
|
|
{
|
|
"id": "F4-003",
|
|
"name": "Render game state to Phaser",
|
|
"description": "Sync Pinia state to Phaser objects",
|
|
"files": ["src/game/sync/StateRenderer.ts"],
|
|
"details": [
|
|
"Watch game store for changes",
|
|
"Update card positions",
|
|
"Show/hide cards based on visibility",
|
|
"Update HP, status, damage counters",
|
|
"Highlight active Pokemon"
|
|
]
|
|
},
|
|
{
|
|
"id": "F4-004",
|
|
"name": "Implement hand interactions",
|
|
"description": "Play cards from hand",
|
|
"files": ["src/game/interactions/HandManager.ts"],
|
|
"details": [
|
|
"Fan cards in hand area",
|
|
"Drag to play Pokemon to bench",
|
|
"Click to play Trainer cards",
|
|
"Attach energy to Pokemon",
|
|
"Visual feedback for valid/invalid plays"
|
|
]
|
|
},
|
|
{
|
|
"id": "F4-005",
|
|
"name": "Implement attack selection",
|
|
"description": "UI for choosing attacks",
|
|
"files": ["src/components/game/AttackMenu.vue", "src/game/ui/AttackOverlay.ts"],
|
|
"details": [
|
|
"Show available attacks for active Pokemon",
|
|
"Display energy cost, damage, effect text",
|
|
"Disable attacks without enough energy",
|
|
"Target selection for attacks that require it",
|
|
"Confirm attack action"
|
|
]
|
|
},
|
|
{
|
|
"id": "F4-006",
|
|
"name": "Implement turn phase UI",
|
|
"description": "Show current phase and valid actions",
|
|
"files": ["src/components/game/TurnIndicator.vue", "src/components/game/PhaseActions.vue"],
|
|
"details": [
|
|
"Display current phase (Draw, Main, Attack)",
|
|
"Show whose turn it is",
|
|
"End Turn button",
|
|
"Retreat button",
|
|
"Phase-appropriate action hints"
|
|
]
|
|
},
|
|
{
|
|
"id": "F4-007",
|
|
"name": "Implement action dispatch",
|
|
"description": "Send actions to server",
|
|
"files": ["src/composables/useGameActions.ts"],
|
|
"details": [
|
|
"Emit game:action for each action type",
|
|
"Handle action results",
|
|
"Optimistic UI updates (optional)",
|
|
"Error handling and retry",
|
|
"Action confirmation for important moves"
|
|
]
|
|
},
|
|
{
|
|
"id": "F4-008",
|
|
"name": "Display opponent state",
|
|
"description": "Render opponent's visible information",
|
|
"files": ["src/game/objects/OpponentArea.ts"],
|
|
"details": [
|
|
"Show opponent active and bench",
|
|
"Display card backs for hand (count only)",
|
|
"Show deck and discard counts",
|
|
"Prize card display (face down)",
|
|
"Opponent name and avatar"
|
|
]
|
|
},
|
|
{
|
|
"id": "F4-009",
|
|
"name": "Implement forced action handling",
|
|
"description": "Handle required actions (select prize, new active)",
|
|
"files": ["src/components/game/ForcedActionModal.vue"],
|
|
"details": [
|
|
"Prize card selection after KO",
|
|
"New active Pokemon selection when active KO'd",
|
|
"Discard selection for certain effects",
|
|
"Block other actions until resolved"
|
|
]
|
|
},
|
|
{
|
|
"id": "F4-010",
|
|
"name": "Implement game over screen",
|
|
"description": "Display results and return to menu",
|
|
"files": ["src/components/game/GameOverModal.vue"],
|
|
"details": [
|
|
"Victory/Defeat display",
|
|
"Show win reason (prizes, deck out, etc.)",
|
|
"Game statistics (turns, cards played)",
|
|
"Return to lobby button",
|
|
"Rematch option (future)"
|
|
]
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": "PHASE_F5",
|
|
"name": "Polish & UX",
|
|
"status": "NOT_STARTED",
|
|
"description": "Reconnection handling, animations, turn timer, error states",
|
|
"estimatedDays": "5-7",
|
|
"dependencies": ["PHASE_F4"],
|
|
"backendDependencies": ["PHASE_4"],
|
|
"technicalDebt": [
|
|
{
|
|
"id": "F5-DEBT-001",
|
|
"description": "Replace 'as any' with proper type validation in useGameSocket",
|
|
"files": ["src/composables/useGameSocket.ts:258", "src/composables/useGameSocket.ts:316"],
|
|
"recommendation": "Use Zod schema validation for WebSocket messages",
|
|
"priority": "medium",
|
|
"source": "Code audit 2026-01-31"
|
|
},
|
|
{
|
|
"id": "F5-DEBT-002",
|
|
"description": "Add .catch() to fire-and-forget promises",
|
|
"files": ["src/composables/useGames.ts:173"],
|
|
"recommendation": "Add error handler to background fetchActiveGames call",
|
|
"priority": "low",
|
|
"source": "Code audit 2026-01-31"
|
|
},
|
|
{
|
|
"id": "F5-DEBT-003",
|
|
"description": "Audit Phaser scene imports for direct store usage",
|
|
"files": ["src/game/scenes/"],
|
|
"recommendation": "Verify all Phaser files use event bridge instead of direct store imports",
|
|
"priority": "low",
|
|
"source": "Code audit 2026-01-31"
|
|
}
|
|
],
|
|
"deliverables": [
|
|
"Reconnection with state recovery",
|
|
"Turn timer display",
|
|
"Opponent connection status",
|
|
"Card play animations",
|
|
"Attack animations",
|
|
"Loading and error states",
|
|
"Sound effects (optional)"
|
|
],
|
|
"tasks": [
|
|
{
|
|
"id": "F5-001",
|
|
"name": "Implement reconnection flow",
|
|
"description": "Handle disconnects gracefully",
|
|
"files": ["src/socket/reconnection.ts", "src/components/game/ReconnectingOverlay.vue"],
|
|
"details": [
|
|
"Detect disconnect",
|
|
"Show reconnecting overlay",
|
|
"Auto-reconnect with backoff",
|
|
"Restore game state on reconnect",
|
|
"Handle failed reconnection"
|
|
]
|
|
},
|
|
{
|
|
"id": "F5-002",
|
|
"name": "Implement turn timer display",
|
|
"description": "Visual countdown for turn time",
|
|
"files": ["src/components/game/TurnTimer.vue"],
|
|
"details": [
|
|
"Countdown display from game state",
|
|
"Warning color at thresholds",
|
|
"Pulse animation when low",
|
|
"Handle timeout notification"
|
|
]
|
|
},
|
|
{
|
|
"id": "F5-003",
|
|
"name": "Show opponent connection status",
|
|
"description": "Indicate if opponent is connected",
|
|
"files": ["src/components/game/OpponentStatus.vue"],
|
|
"details": [
|
|
"Online/offline indicator",
|
|
"Handle game:opponent_connected events",
|
|
"Show 'waiting for opponent' if disconnected"
|
|
]
|
|
},
|
|
{
|
|
"id": "F5-004",
|
|
"name": "Add card play animations",
|
|
"description": "Animate cards moving between zones",
|
|
"files": ["src/game/animations/CardAnimations.ts"],
|
|
"details": [
|
|
"Draw card animation (deck to hand)",
|
|
"Play to bench animation",
|
|
"Evolve animation",
|
|
"Attach energy animation",
|
|
"Discard animation"
|
|
]
|
|
},
|
|
{
|
|
"id": "F5-005",
|
|
"name": "Add attack animations",
|
|
"description": "Visual feedback for attacks",
|
|
"files": ["src/game/animations/AttackAnimations.ts"],
|
|
"details": [
|
|
"Attack motion (active toward opponent)",
|
|
"Damage numbers",
|
|
"KO animation (fade out)",
|
|
"Status effect indicators",
|
|
"Type-specific visual effects (optional)"
|
|
]
|
|
},
|
|
{
|
|
"id": "F5-006",
|
|
"name": "Add loading and error states",
|
|
"description": "Feedback for async operations",
|
|
"files": ["src/components/ui/LoadingSpinner.vue", "src/components/ui/Toast.vue"],
|
|
"details": [
|
|
"Skeleton loaders for lists",
|
|
"Button loading states",
|
|
"Error toast notifications",
|
|
"Retry prompts for failed requests"
|
|
]
|
|
},
|
|
{
|
|
"id": "F5-007",
|
|
"name": "Add sound effects (optional)",
|
|
"description": "Audio feedback for game events",
|
|
"files": ["src/game/audio/SoundManager.ts"],
|
|
"details": [
|
|
"Card play sound",
|
|
"Attack sound",
|
|
"Turn change chime",
|
|
"Victory/defeat music",
|
|
"Volume controls",
|
|
"Mute option"
|
|
]
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": "PHASE_F6",
|
|
"name": "Campaign Mode",
|
|
"status": "NOT_STARTED",
|
|
"description": "Single-player campaign UI - clubs, NPCs, progression, rewards",
|
|
"estimatedDays": "7-10",
|
|
"dependencies": ["PHASE_F5"],
|
|
"backendDependencies": ["PHASE_5"],
|
|
"blocked": true,
|
|
"blockedReason": "Requires backend Phase 5 (Campaign Mode)",
|
|
"deliverables": [
|
|
"Campaign world map",
|
|
"Club selection",
|
|
"NPC opponent display",
|
|
"Medal/progression tracking",
|
|
"Reward notifications",
|
|
"Difficulty indicators"
|
|
]
|
|
},
|
|
{
|
|
"id": "PHASE_F7",
|
|
"name": "Multiplayer & Matchmaking",
|
|
"status": "NOT_STARTED",
|
|
"description": "PvP matchmaking queue, invite links, match history",
|
|
"estimatedDays": "5-7",
|
|
"dependencies": ["PHASE_F5"],
|
|
"backendDependencies": ["PHASE_6"],
|
|
"blocked": true,
|
|
"blockedReason": "Requires backend Phase 6 (Multiplayer)",
|
|
"deliverables": [
|
|
"Matchmaking queue UI",
|
|
"Queue status and cancel",
|
|
"Invite link generation",
|
|
"Join via invite link",
|
|
"Match history page",
|
|
"Player stats display"
|
|
]
|
|
},
|
|
{
|
|
"id": "PHASE_F8",
|
|
"name": "Pack Opening & Rewards",
|
|
"status": "NOT_STARTED",
|
|
"description": "Animated pack opening experience",
|
|
"estimatedDays": "5-7",
|
|
"dependencies": ["PHASE_F6"],
|
|
"backendDependencies": ["PHASE_5"],
|
|
"blocked": true,
|
|
"blockedReason": "Requires backend Phase 5 reward system",
|
|
"deliverables": [
|
|
"Pack opening Phaser scene",
|
|
"Card reveal animations",
|
|
"Rarity celebration effects",
|
|
"New card indicators in collection"
|
|
]
|
|
}
|
|
],
|
|
|
|
"criticalPath": ["PHASE_F0", "PHASE_F1", "PHASE_F3", "PHASE_F4"],
|
|
|
|
"parallelDevelopment": {
|
|
"note": "F0-F5 can proceed while backend completes Phase 5-6",
|
|
"tracks": [
|
|
{
|
|
"name": "Frontend Core",
|
|
"phases": ["PHASE_F0", "PHASE_F1", "PHASE_F2", "PHASE_F3", "PHASE_F4", "PHASE_F5"]
|
|
},
|
|
{
|
|
"name": "Backend Campaign",
|
|
"phases": ["PHASE_5"]
|
|
},
|
|
{
|
|
"name": "Backend Multiplayer",
|
|
"phases": ["PHASE_6"]
|
|
}
|
|
],
|
|
"joinPoints": [
|
|
{
|
|
"frontend": "PHASE_F6",
|
|
"backend": "PHASE_5",
|
|
"description": "Campaign UI requires campaign backend"
|
|
},
|
|
{
|
|
"frontend": "PHASE_F7",
|
|
"backend": "PHASE_6",
|
|
"description": "Matchmaking UI requires multiplayer backend"
|
|
}
|
|
]
|
|
},
|
|
|
|
"designSystem": {
|
|
"colors": {
|
|
"note": "Pokemon type-inspired palette",
|
|
"types": {
|
|
"grass": "#78C850",
|
|
"fire": "#F08030",
|
|
"water": "#6890F0",
|
|
"lightning": "#F8D030",
|
|
"psychic": "#F85888",
|
|
"fighting": "#C03028",
|
|
"darkness": "#705848",
|
|
"metal": "#B8B8D0",
|
|
"fairy": "#EE99AC",
|
|
"dragon": "#7038F8",
|
|
"colorless": "#A8A878"
|
|
},
|
|
"ui": {
|
|
"primary": "#3B82F6",
|
|
"secondary": "#6366F1",
|
|
"success": "#22C55E",
|
|
"warning": "#F59E0B",
|
|
"error": "#EF4444",
|
|
"background": "#1F2937",
|
|
"surface": "#374151",
|
|
"text": "#F9FAFB"
|
|
}
|
|
},
|
|
"typography": {
|
|
"headings": "Press Start 2P or similar pixel font for retro feel",
|
|
"body": "Inter or system font stack"
|
|
}
|
|
},
|
|
|
|
"testingStrategy": {
|
|
"unit": {
|
|
"tool": "Vitest",
|
|
"scope": "Composables, stores, utilities",
|
|
"location": "src/**/*.test.ts"
|
|
},
|
|
"component": {
|
|
"tool": "Vitest + Vue Test Utils",
|
|
"scope": "Vue components in isolation",
|
|
"location": "src/**/*.test.ts"
|
|
},
|
|
"e2e": {
|
|
"tool": "Playwright",
|
|
"scope": "Full user flows",
|
|
"location": "e2e/",
|
|
"deferred": "After F4 complete"
|
|
}
|
|
},
|
|
|
|
"risks": [
|
|
{
|
|
"risk": "Phaser learning curve",
|
|
"mitigation": "Start F3 with simple prototypes, iterate",
|
|
"priority": "high"
|
|
},
|
|
{
|
|
"risk": "Vue-Phaser state sync complexity",
|
|
"mitigation": "Establish clear patterns in F3, document bridge API",
|
|
"priority": "high"
|
|
},
|
|
{
|
|
"risk": "Mobile touch interactions",
|
|
"mitigation": "Design for touch from start, test on real devices",
|
|
"priority": "medium"
|
|
},
|
|
{
|
|
"risk": "Asset loading performance",
|
|
"mitigation": "Lazy load cards, use sprite atlases, implement caching",
|
|
"priority": "medium"
|
|
}
|
|
]
|
|
}
|