{ "meta": { "phaseId": "PHASE_F3", "name": "Phaser Integration", "version": "1.0.0", "created": "2026-01-31", "lastUpdated": "2026-01-31", "totalTasks": 12, "completedTasks": 12, "status": "COMPLETED", "description": "Game rendering foundation - Phaser setup, board layout, card objects, Vue-Phaser communication bridge. This phase establishes the core infrastructure for rendering TCG matches.", "designReference": "src/styles/DESIGN_REFERENCE.md" }, "stylingPrinciples": [ "Game canvas fills viewport on game page (no scrollbars during match)", "Card objects use type-colored borders matching design system", "All interactive elements have clear hover/focus states", "Touch targets minimum 44px for mobile play", "Smooth 60fps animations - use Phaser tweens, not CSS", "Loading states with progress indicator during asset loading", "Responsive scaling maintains playable experience on all devices" ], "dependencies": { "phases": ["PHASE_F0", "PHASE_F1", "PHASE_F2"], "backend": [ "POST /api/games - Create new game (for testing)", "GET /api/games/{id} - Get game info", "WebSocket game:state events - For state rendering tests" ], "external": [ "Phaser 3 library", "Card image assets (placeholder/CDN)" ] }, "architectureNotes": { "vuePhaser": { "principle": "Phaser handles RENDERING ONLY. All game logic lives on backend.", "pattern": "Vue component mounts Phaser game, communicates via typed EventEmitter bridge", "state": "Pinia game store is source of truth. Phaser reads from store, never modifies.", "cleanup": "Phaser game instance destroyed on component unmount to prevent memory leaks" }, "eventBridge": { "vueToPhaser": [ "game:state_updated - New state from server, Phaser should re-render", "card:highlight - Highlight specific card (for tutorials, hints)", "animation:request - Request Phaser to play specific animation", "resize - Viewport resized, Phaser should rescale" ], "phaserToVue": [ "card:clicked - User clicked a card (cardId, zone)", "zone:clicked - User clicked a zone (zoneType, slotIndex)", "animation:complete - Animation finished, Vue can proceed", "ready - Phaser scene is ready and loaded" ] }, "sceneFlow": { "PreloadScene": "Load all required assets, show progress bar", "MatchScene": "Main gameplay rendering, handles all card/board display" }, "fileStructure": { "src/game/": "All Phaser code lives here", "src/game/config.ts": "Phaser game configuration", "src/game/scenes/": "Scene classes", "src/game/objects/": "Game objects (Card, Board, Zone)", "src/game/bridge.ts": "Vue-Phaser event bridge", "src/game/assets/": "Asset loading utilities", "src/game/layout.ts": "Board layout calculations", "src/game/scale.ts": "Responsive scaling logic" } }, "tasks": [ { "id": "F3-001", "name": "Install and configure Phaser", "description": "Add Phaser 3 to the project with proper TypeScript configuration", "category": "setup", "priority": 1, "completed": true, "tested": true, "dependencies": [], "files": [ {"path": "package.json", "status": "modify"}, {"path": "src/game/config.ts", "status": "create"}, {"path": "src/game/index.ts", "status": "create"}, {"path": "tsconfig.json", "status": "modify"} ], "details": [ "Install phaser: npm install phaser", "Create src/game/config.ts with Phaser.Types.Core.GameConfig", "Configure WebGL renderer with canvas fallback", "Set transparent: true to allow CSS background styling", "Configure physics: 'arcade' (minimal, for tween support)", "Set parent element ID for mounting ('phaser-game')", "Configure scale mode: Phaser.Scale.RESIZE for responsive", "Export createGame(container: HTMLElement) factory function", "Add DOM config to allow DOM elements in Phaser if needed" ], "acceptance": [ "Phaser installed and importable", "Config creates valid Phaser game instance", "TypeScript types work correctly", "No console errors on import" ], "estimatedHours": 1 }, { "id": "F3-002", "name": "Create PhaserGame Vue component", "description": "Vue component that mounts and manages Phaser game lifecycle", "category": "components", "priority": 2, "completed": true, "tested": true, "dependencies": ["F3-001"], "files": [ {"path": "src/components/game/PhaserGame.vue", "status": "create"}, {"path": "src/components/game/PhaserGame.spec.ts", "status": "create"} ], "details": [ "Template: single div with ref='container' for Phaser mounting", "Props: none initially (scenes loaded via config)", "Emits: ready, error", "onMounted: create Phaser game instance with createGame(container)", "onUnmounted: call game.destroy(true) to cleanup", "Expose game instance via defineExpose for parent access", "Handle creation errors with error emit and console.error", "Use CSS: width: 100%, height: 100%, position: relative", "Add ResizeObserver to detect container size changes", "Emit 'ready' when Phaser emits READY event" ], "styling": [ "Container: w-full h-full relative overflow-hidden", "Canvas will be absolutely positioned by Phaser", "Background: bg-background (matches design system)" ], "acceptance": [ "Component renders empty Phaser canvas", "Game instance properly destroyed on unmount", "No memory leaks (verified via dev tools)", "ready event fires when Phaser initializes", "Test verifies mount/unmount lifecycle" ], "estimatedHours": 2 }, { "id": "F3-003", "name": "Create Vue-Phaser event bridge", "description": "Typed bidirectional communication system between Vue and Phaser", "category": "composables", "priority": 3, "completed": true, "tested": true, "dependencies": ["F3-002"], "files": [ {"path": "src/game/bridge.ts", "status": "create"}, {"path": "src/game/bridge.spec.ts", "status": "create"}, {"path": "src/composables/useGameBridge.ts", "status": "create"}, {"path": "src/composables/useGameBridge.spec.ts", "status": "create"} ], "details": [ "Create GameBridge class extending Phaser.Events.EventEmitter", "Define typed events interface: GameBridgeEvents", "Vue->Phaser events: state_updated, card_highlight, animation_request, resize", "Phaser->Vue events: card_clicked, zone_clicked, animation_complete, ready, error", "Export singleton: gameBridge = new GameBridge()", "Create useGameBridge() composable for Vue components", "Composable provides: emit(event, data), on(event, handler), off(event, handler)", "Composable auto-cleans up listeners on component unmount (onUnmounted)", "Phaser scenes access gameBridge directly via import", "All event data types explicitly defined in bridge.ts" ], "acceptance": [ "Events flow Vue -> Phaser correctly", "Events flow Phaser -> Vue correctly", "TypeScript enforces correct event payloads", "Listeners auto-removed on Vue component unmount", "Tests verify bidirectional communication" ], "estimatedHours": 3 }, { "id": "F3-004", "name": "Create PreloadScene", "description": "Phaser scene for loading game assets with progress display", "category": "game", "priority": 4, "completed": true, "tested": true, "dependencies": ["F3-001"], "files": [ {"path": "src/game/scenes/PreloadScene.ts", "status": "create"}, {"path": "src/game/assets/manifest.ts", "status": "create"}, {"path": "src/game/assets/loader.ts", "status": "create"} ], "details": [ "Extend Phaser.Scene with key 'PreloadScene'", "preload(): Load assets defined in manifest.ts", "Assets to load: card back image, board background, UI sprites, type icons", "Create loading progress bar using Phaser graphics", "Listen to 'progress' event to update progress bar", "Listen to 'complete' event to transition to MatchScene", "manifest.ts: Export ASSET_MANIFEST with paths and keys", "loader.ts: Helper functions for dynamic card image loading", "Card images loaded lazily in MatchScene (not all upfront)", "Handle load errors gracefully with fallback placeholder" ], "acceptance": [ "Progress bar displays during loading", "All core assets load successfully", "Scene transitions to MatchScene after load", "Load errors handled without crash", "Progress reaches 100% before transition" ], "estimatedHours": 3, "notes": "Card images may use placeholders initially if CDN not configured" }, { "id": "F3-005", "name": "Create MatchScene foundation", "description": "Main game scene that renders the board and responds to state changes", "category": "game", "priority": 5, "completed": true, "tested": true, "dependencies": ["F3-003", "F3-004"], "files": [ {"path": "src/game/scenes/MatchScene.ts", "status": "create"}, {"path": "src/game/scenes/index.ts", "status": "create"} ], "details": [ "Extend Phaser.Scene with key 'MatchScene'", "create(): Set up board layout, subscribe to bridge events", "Subscribe to gameBridge 'state_updated' event", "Implement renderState(gameState: GameState) method", "Track game objects in Maps: cards, zones, etc.", "update(): Minimal - most updates are event-driven", "Handle resize events from bridge to rescale board", "Emit 'ready' to bridge when scene is fully initialized", "Implement clearBoard() for state reset", "scenes/index.ts: Export array of scene classes for game config" ], "acceptance": [ "Scene creates and displays empty board area", "Scene responds to state_updated events", "Resize events properly handled", "ready event emitted after initialization", "Scene can be destroyed and recreated cleanly" ], "estimatedHours": 4 }, { "id": "F3-006", "name": "Create board layout system", "description": "Calculate positions for all game zones based on screen size", "category": "game", "priority": 6, "completed": true, "tested": true, "dependencies": ["F3-005"], "files": [ {"path": "src/game/layout.ts", "status": "create"}, {"path": "src/game/layout.spec.ts", "status": "create"}, {"path": "src/game/objects/Board.ts", "status": "create"} ], "details": [ "Define BoardLayout interface with zone positions", "Zones: myActive, myBench (5 slots), myDeck, myDiscard, myPrizes (6)", "Mirror zones for opponent: oppActive, oppBench, oppDeck, oppDiscard, oppPrizes", "Hand zone: bottom of screen, fan layout", "calculateLayout(width: number, height: number): BoardLayout", "Layout adapts to portrait (mobile) vs landscape (desktop)", "Each zone has: x, y, width, height, angle (for fanning)", "Board.ts: Game object that renders zone backgrounds/outlines", "Highlight zones when they're valid drop/play targets", "Zone positions exported as constants for consistent reference" ], "styling": [ "Zone outlines: subtle border, slightly lighter than background", "Active zones: prominent position, larger card area", "Bench: horizontal row of 5 slots with spacing", "Prizes: 2x3 grid or stacked display", "Opponent zones: mirrored at top of screen (inverted orientation)" ], "acceptance": [ "Layout calculation produces valid positions", "All zones have distinct, non-overlapping areas", "Layout works for both portrait and landscape", "Board renders zone outlines correctly", "Tests verify layout calculations" ], "estimatedHours": 4 }, { "id": "F3-007", "name": "Create Card game object", "description": "Phaser game object representing a card that can be displayed and interacted with", "category": "game", "priority": 7, "completed": true, "tested": true, "dependencies": ["F3-005"], "files": [ {"path": "src/game/objects/Card.ts", "status": "create"}, {"path": "src/game/objects/CardBack.ts", "status": "create"}, {"path": "src/game/objects/DamageCounter.ts", "status": "create"} ], "details": [ "Card extends Phaser.GameObjects.Container", "Contains: card image sprite, type border, optional damage counter", "Constructor params: scene, x, y, cardData (from game state)", "setCard(cardData): Update displayed card", "setFaceDown(isFaceDown): Toggle card back vs face", "CardBack: Simple game object for face-down cards", "DamageCounter: Displays damage on card (red circle with number)", "Interactive: setInteractive(), on('pointerdown'), on('pointerover'), etc.", "Click emits event to bridge: card_clicked with cardId and zone", "Support multiple sizes: hand (medium), board (large), thumbnail (small)", "Lazy load card images - use placeholder until image loads" ], "styling": [ "Card border: 3px colored border matching card type", "Hover: scale to 1.05, slight shadow, raise z-index", "Selected: pulsing glow effect (tween)", "Disabled: grayscale + reduced alpha", "Damage counter: red circle, white text, positioned bottom-right" ], "acceptance": [ "Card displays correctly with image and border", "Face-down cards show card back", "Click events fire and reach bridge", "Hover effects work on desktop", "Damage counter displays when card has damage" ], "estimatedHours": 5 }, { "id": "F3-008", "name": "Create zone game objects", "description": "Game objects for each board zone that contain and arrange cards", "category": "game", "priority": 8, "completed": true, "tested": true, "dependencies": ["F3-006", "F3-007"], "files": [ {"path": "src/game/objects/Zone.ts", "status": "create"}, {"path": "src/game/objects/ActiveZone.ts", "status": "create"}, {"path": "src/game/objects/BenchZone.ts", "status": "create"}, {"path": "src/game/objects/HandZone.ts", "status": "create"}, {"path": "src/game/objects/PileZone.ts", "status": "create"}, {"path": "src/game/objects/PrizeZone.ts", "status": "create"} ], "details": [ "Zone: Base class extending Phaser.GameObjects.Container", "Zone tracks contained Card objects", "ActiveZone: Single card slot, larger display", "BenchZone: Row of 5 card slots, evenly spaced", "HandZone: Fan of cards at bottom, cards slightly overlap", "PileZone: For deck/discard - shows top card and count", "PrizeZone: 6 face-down cards in 2x3 grid", "setCards(cards[]): Update zone contents, animate changes", "highlight(enabled: boolean): Visual indicator for valid targets", "Each zone type handles its own card arrangement logic", "Zones are interactive for targeting (emit zone_clicked)" ], "acceptance": [ "All zone types render correctly", "Zones arrange cards appropriately", "Zone highlights toggle correctly", "Zone click events reach bridge", "Card updates animate smoothly" ], "estimatedHours": 6 }, { "id": "F3-009", "name": "Implement responsive canvas scaling", "description": "Make game canvas scale properly to any screen size", "category": "game", "priority": 9, "completed": true, "tested": true, "dependencies": ["F3-006"], "files": [ {"path": "src/game/scale.ts", "status": "create"}, {"path": "src/game/scale.spec.ts", "status": "create"}, {"path": "src/game/config.ts", "status": "modify"} ], "details": [ "Use Phaser.Scale.RESIZE mode (or FIT for fixed aspect)", "Define design resolution: 1920x1080 (landscape base)", "calculateScale(width, height): Get scale factor and offsets", "Handle orientation changes on mobile", "ResizeManager class to coordinate resize events", "Emit resize event to bridge when size changes", "Debounce resize handling (100ms) to prevent excessive recalc", "Update BoardLayout on resize", "Ensure touch areas scale appropriately (min 44px equivalent)" ], "acceptance": [ "Canvas scales to fill container", "Aspect ratio maintained (letterbox if needed)", "Resize events trigger layout recalculation", "Touch targets remain usable on mobile", "Tests verify scale calculations" ], "estimatedHours": 3 }, { "id": "F3-010", "name": "Implement state rendering", "description": "Render game state from Pinia store to Phaser scene", "category": "game", "priority": 10, "completed": true, "tested": true, "dependencies": ["F3-008"], "files": [ {"path": "src/game/sync/StateRenderer.ts", "status": "create"}, {"path": "src/game/sync/index.ts", "status": "create"} ], "details": [ "StateRenderer class coordinates state -> scene updates", "render(state: GameState): Main entry point", "Compare previous state to new state to minimize updates", "Update my zones: active, bench, hand, deck, discard, prizes", "Update opponent zones: active, bench, deck (count), discard (count), prizes", "Handle visibility: opponent hand shows card backs only", "updateCard(card, zone): Position card in correct zone", "createCard(card): Create new Card game object", "removeCard(cardId): Destroy Card game object", "Track cards by ID in Map for efficient updates" ], "acceptance": [ "State renders correctly to scene", "All zones populated from state", "Opponent hidden zones show appropriate info", "State updates are efficient (diff-based)", "Cards created/removed as state changes" ], "estimatedHours": 5 }, { "id": "F3-011", "name": "Create GamePage with Phaser integration", "description": "Vue page that hosts the Phaser game during matches", "category": "pages", "priority": 11, "completed": true, "tested": true, "dependencies": ["F3-002", "F3-003", "F3-010"], "files": [ {"path": "src/pages/GamePage.vue", "status": "create"}, {"path": "src/pages/GamePage.spec.ts", "status": "create"}, {"path": "src/router/index.ts", "status": "modify"} ], "details": [ "Full viewport page (no nav) using game layout", "Mount PhaserGame component filling container", "Route: /game/:id with id param", "On mount: connect to socket, join game room", "Subscribe to game:state events -> update store", "Watch game store state -> emit to bridge", "Handle game:error events with toast/overlay", "Handle disconnect/reconnect (overlay during reconnect)", "Exit button -> confirm, leave game, navigate away", "Route guard: redirect if not authenticated" ], "styling": [ "Container: fixed inset-0, bg-background", "No scrolling, overflow-hidden", "Exit button: absolute top-right, icon-only", "Reconnecting overlay: centered with spinner" ], "acceptance": [ "Page renders Phaser game full viewport", "WebSocket connection established", "Game state flows to Phaser", "Can exit game with confirmation", "Auth guard works correctly" ], "estimatedHours": 4 }, { "id": "F3-012", "name": "Add game types and API types", "description": "TypeScript types for game state, events, and Phaser integration", "category": "api", "priority": 12, "completed": true, "tested": true, "dependencies": [], "files": [ {"path": "src/types/game.ts", "status": "create"}, {"path": "src/types/phaser.ts", "status": "create"}, {"path": "src/types/index.ts", "status": "modify"} ], "details": [ "game.ts: GameState, Player, Card, Zone, Phase types (match backend)", "Card types: id, definitionId, name, hp, currentHp, type, attacks, etc.", "Zone types: ZoneType enum, ZoneState interface", "Phase types: TurnPhase enum (draw, main, attack, end)", "phaser.ts: GameBridgeEvents interface for typed bridge events", "phaser.ts: CardClickEvent, ZoneClickEvent payloads", "phaser.ts: Scene data interfaces for scene transitions", "Ensure types align with backend/app/schemas/game.py", "Export all from types/index.ts" ], "acceptance": [ "All game types defined and exported", "Types match backend API contracts", "Bridge events fully typed", "No any types in game code" ], "estimatedHours": 2, "notes": "This task can be done early and in parallel with others" } ], "testingApproach": { "unitTests": [ "Bridge event emission and reception", "Layout calculations for various screen sizes", "Scale calculations", "State renderer diffing logic" ], "componentTests": [ "PhaserGame mount/unmount lifecycle", "GamePage integration with mocked store/socket" ], "manualTests": [ "Visual inspection of board layout", "Card rendering at different sizes", "Touch interactions on mobile", "Resize behavior", "Scene transitions" ], "note": "Phaser rendering is hard to unit test - focus on logic tests and manual visual verification" }, "assetRequirements": { "required": [ {"name": "card_back.png", "size": "500x700", "description": "Generic card back for face-down cards"}, {"name": "board_background.png", "size": "1920x1080", "description": "Optional board background texture"} ], "optional": [ {"name": "type_icons.png", "description": "Sprite atlas of type icons"}, {"name": "ui_sprites.png", "description": "UI elements (buttons, indicators)"} ], "cardImages": { "source": "CDN or local /public/cards/ directory", "pattern": "{setId}/{cardNumber}.png or {cardDefinitionId}.png", "fallback": "placeholder_card.png for missing images" } }, "riskMitigation": [ { "risk": "Phaser learning curve", "mitigation": "Start with F3-001 and F3-002 to get basic rendering working quickly. Reference Phaser docs and examples." }, { "risk": "Vue-Phaser state sync complexity", "mitigation": "Keep bridge events minimal and explicit. Phaser only reads state, never modifies." }, { "risk": "Memory leaks from game objects", "mitigation": "Always call destroy() on game objects when removing. Test with Chrome DevTools memory profiler." }, { "risk": "Mobile performance", "mitigation": "Use object pooling for cards if performance issues arise. Lazy load card images." } ], "notes": [ "F3-012 (types) can be worked on early and in parallel", "Card images may use placeholders until CDN is configured", "This phase focuses on RENDERING infrastructure - actual gameplay interactions come in F4", "All Phaser code should be in src/game/ to keep it isolated from Vue code", "The game store already exists from F0 - this phase adds the Phaser visualization layer", "Consider using Phaser's built-in tween system for all animations (smoother than CSS)" ] }