Phase F1 - Authentication: - OAuth callback handling with token management - Auth guards for protected routes - Account linking composable - Profile page updates Phase F2 - Deck Management: - Collection page with card filtering and display - Decks page with CRUD operations - Deck builder with drag-drop support - Collection and deck Pinia stores Phase F3 - Phaser Integration: - Game bridge composable for Vue-Phaser communication - Game page with Phaser canvas mounting - Socket.io event types for real-time gameplay - Game store with match state management - Phaser scene scaffolding and type definitions Also includes: - New UI components (ConfirmDialog, EmptyState, FilterBar, etc.) - Toast notification system - Game config composable for dynamic rule loading - Comprehensive test coverage for new features
581 lines
24 KiB
JSON
581 lines
24 KiB
JSON
{
|
|
"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)"
|
|
]
|
|
}
|