From 019986ceef07f3551657731c9ad7bb7b3313cb17 Mon Sep 17 00:00:00 2001 From: "Z. Cliffe Schreuders" Date: Sat, 15 Nov 2025 23:58:19 +0000 Subject: [PATCH] docs: Add comprehensive room layout system redesign plan MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Created detailed implementation plan for redesigning the room layout system to support variable room sizes and four-direction connections. Core Concepts: - Grid unit system (5×4 tiles base, excluding 2-tile visual top) - Valid room heights: 6, 10, 14, 18, 22, 26... (formula: 2 + 4N) - Breadth-first room positioning from starting room - Deterministic door placement with alignment for asymmetric connections - Comprehensive scenario validation Documents Created: - OVERVIEW.md: High-level goals and changes - TERMINOLOGY.md: Definitions and concepts - GRID_SYSTEM.md: Grid unit system specification - POSITIONING_ALGORITHM.md: Room positioning logic - DOOR_PLACEMENT.md: Door placement rules and algorithms - WALL_SYSTEM.md: Wall collision system updates - VALIDATION.md: Scenario validation system - IMPLEMENTATION_STEPS.md: Step-by-step implementation guide - TODO_LIST.md: Detailed task checklist - README.md: Quick start and overview Review & Critical Fixes: - review1/CRITICAL_REVIEW.md: Identified 4 critical issues - review1/RECOMMENDATIONS.md: Solutions for all issues - UPDATED_FILES_SUMMARY.md: Integration of review feedback Critical Issues Identified & Resolved: 1. Grid height calculation (now: 6, 10, 14, 18...) 2. Door alignment for asymmetric connections (solution documented) 3. Code duplication (shared module approach specified) 4. Disconnected rooms (validation added) Implementation Strategy: - Incremental approach with feature flag - Phase 1: Constants and helpers - Phase 2a: North/South positioning - Phase 2b: East/West support - Phase 3: Door placement with critical fixes - Phase 4: Validation - Phase 5-6: Testing and documentation Estimated time: 18-26 hours Confidence: 9/10 (all critical issues addressed) Ready for implementation. --- .../new_room_layout/DOOR_PLACEMENT.md | 393 ++++++++++ planning_notes/new_room_layout/GRID_SYSTEM.md | 241 ++++++ .../new_room_layout/IMPLEMENTATION_STEPS.md | 714 ++++++++++++++++++ planning_notes/new_room_layout/OVERVIEW.md | 74 ++ .../new_room_layout/POSITIONING_ALGORITHM.md | 469 ++++++++++++ planning_notes/new_room_layout/README.md | 261 +++++++ planning_notes/new_room_layout/TERMINOLOGY.md | 165 ++++ planning_notes/new_room_layout/TODO_LIST.md | 661 ++++++++++++++++ .../new_room_layout/UPDATED_FILES_SUMMARY.md | 166 ++++ planning_notes/new_room_layout/VALIDATION.md | 503 ++++++++++++ planning_notes/new_room_layout/WALL_SYSTEM.md | 304 ++++++++ .../review1/CRITICAL_REVIEW.md | 511 +++++++++++++ .../review1/RECOMMENDATIONS.md | 653 ++++++++++++++++ 13 files changed, 5115 insertions(+) create mode 100644 planning_notes/new_room_layout/DOOR_PLACEMENT.md create mode 100644 planning_notes/new_room_layout/GRID_SYSTEM.md create mode 100644 planning_notes/new_room_layout/IMPLEMENTATION_STEPS.md create mode 100644 planning_notes/new_room_layout/OVERVIEW.md create mode 100644 planning_notes/new_room_layout/POSITIONING_ALGORITHM.md create mode 100644 planning_notes/new_room_layout/README.md create mode 100644 planning_notes/new_room_layout/TERMINOLOGY.md create mode 100644 planning_notes/new_room_layout/TODO_LIST.md create mode 100644 planning_notes/new_room_layout/UPDATED_FILES_SUMMARY.md create mode 100644 planning_notes/new_room_layout/VALIDATION.md create mode 100644 planning_notes/new_room_layout/WALL_SYSTEM.md create mode 100644 planning_notes/new_room_layout/review1/CRITICAL_REVIEW.md create mode 100644 planning_notes/new_room_layout/review1/RECOMMENDATIONS.md diff --git a/planning_notes/new_room_layout/DOOR_PLACEMENT.md b/planning_notes/new_room_layout/DOOR_PLACEMENT.md new file mode 100644 index 0000000..8a41ef9 --- /dev/null +++ b/planning_notes/new_room_layout/DOOR_PLACEMENT.md @@ -0,0 +1,393 @@ +# Door Placement System + +## Overview + +Doors must be placed such that they: +1. Align perfectly between connecting rooms +2. Follow visual conventions (corners for N/S, edges for E/W) +3. Use deterministic positioning for consistent layouts +4. Support multiple doors per room edge + +## Door Types + +### North/South Doors +- **Sprite**: `door_32.png` (sprite sheet: `door_sheet_32.png`) +- **Size**: 1 tile wide × 2 tiles tall +- **Placement**: In corners of the room +- **Visual**: Represents passage through wall with visible door frame + +### East/West Doors +- **Sprite**: `door_side_sheet_32.png` +- **Size**: 1 tile wide × 1 tile tall +- **Placement**: At edges, based on connection count +- **Visual**: Side view of door + +## Placement Rules + +### North Connections + +#### Single Door +- **Position**: Determined by grid coordinates using modulus for alternation +- **Options**: Northwest corner or Northeast corner +- **Inset**: 1.5 tiles from edge (half tile for wall, 1 tile for door) + +```javascript +function placeNorthDoorSingle(roomId, roomPosition, roomDimensions, gridCoords) { + const roomWidthPx = roomDimensions.widthPx; + + // Deterministic left/right placement based on grid position + // Use sum of grid coordinates for deterministic alternation + const useRightSide = (gridCoords.x + gridCoords.y) % 2 === 1; + + let doorX; + if (useRightSide) { + // Northeast corner + doorX = roomPosition.x + roomWidthPx - (TILE_SIZE * 1.5); + } else { + // Northwest corner + doorX = roomPosition.x + (TILE_SIZE * 1.5); + } + + // Door Y is 1 tile from top + const doorY = roomPosition.y + TILE_SIZE; + + return { x: doorX, y: doorY }; +} +``` + +#### Multiple Doors +- **Position**: Evenly spaced across room width +- **Spacing**: Maintains 1.5 tile inset from edges +- **Count**: Supports 2+ doors + +```javascript +function placeNorthDoorsMultiple(roomId, roomPosition, roomDimensions, connectedRooms) { + const roomWidthPx = roomDimensions.widthPx; + const doorPositions = []; + + // Available width after edge insets + const edgeInset = TILE_SIZE * 1.5; + const availableWidth = roomWidthPx - (edgeInset * 2); + + // Space between doors + const doorCount = connectedRooms.length; + const doorSpacing = availableWidth / (doorCount - 1); + + connectedRooms.forEach((connectedRoom, index) => { + const doorX = roomPosition.x + edgeInset + (doorSpacing * index); + const doorY = roomPosition.y + TILE_SIZE; + + doorPositions.push({ + connectedRoom, + x: doorX, + y: doorY + }); + }); + + return doorPositions; +} +``` + +### South Connections + +Same as North, but door Y position is at bottom: + +```javascript +function placeSouthDoorSingle(roomId, roomPosition, roomDimensions, gridCoords) { + const roomWidthPx = roomDimensions.widthPx; + const roomHeightPx = roomDimensions.heightPx; + + // Same deterministic placement as north + const useRightSide = (gridCoords.x + gridCoords.y) % 2 === 1; + + let doorX; + if (useRightSide) { + doorX = roomPosition.x + roomWidthPx - (TILE_SIZE * 1.5); + } else { + doorX = roomPosition.x + (TILE_SIZE * 1.5); + } + + // Door Y is at bottom (room height - 1 tile) + const doorY = roomPosition.y + roomHeightPx - TILE_SIZE; + + return { x: doorX, y: doorY }; +} +``` + +### East Connections + +#### Single Door +- **Position**: North corner of east edge +- **Inset**: 2 tiles from top (below visual wall) + +```javascript +function placeEastDoorSingle(roomId, roomPosition, roomDimensions) { + const roomWidthPx = roomDimensions.widthPx; + + // Place at north corner of east edge + const doorX = roomPosition.x + roomWidthPx - TILE_SIZE; + const doorY = roomPosition.y + (TILE_SIZE * 2); // Below visual wall + + return { x: doorX, y: doorY }; +} +``` + +#### Multiple Doors +- **First Door**: North corner (2 tiles from top) +- **Second Door**: 3 tiles up from south edge (avoids overlap) +- **More Doors**: Evenly spaced between first and second + +```javascript +function placeEastDoorsMultiple(roomId, roomPosition, roomDimensions, connectedRooms) { + const roomWidthPx = roomDimensions.widthPx; + const roomHeightPx = roomDimensions.heightPx; + const doorPositions = []; + + const doorCount = connectedRooms.length; + + if (doorCount === 1) { + return [placeEastDoorSingle(roomId, roomPosition, roomDimensions)]; + } + + connectedRooms.forEach((connectedRoom, index) => { + const doorX = roomPosition.x + roomWidthPx - TILE_SIZE; + + let doorY; + if (index === 0) { + // First door: north corner + doorY = roomPosition.y + (TILE_SIZE * 2); + } else if (index === doorCount - 1) { + // Last door: 3 tiles up from south + doorY = roomPosition.y + roomHeightPx - (TILE_SIZE * 3); + } else { + // Middle doors: evenly spaced + const firstDoorY = roomPosition.y + (TILE_SIZE * 2); + const lastDoorY = roomPosition.y + roomHeightPx - (TILE_SIZE * 3); + const spacing = (lastDoorY - firstDoorY) / (doorCount - 1); + doorY = firstDoorY + (spacing * index); + } + + doorPositions.push({ + connectedRoom, + x: doorX, + y: doorY + }); + }); + + return doorPositions; +} +``` + +### West Connections + +Mirror of East connections: + +```javascript +function placeWestDoorSingle(roomId, roomPosition, roomDimensions) { + // Place at north corner of west edge + const doorX = roomPosition.x + TILE_SIZE; + const doorY = roomPosition.y + (TILE_SIZE * 2); + + return { x: doorX, y: doorY }; +} +``` + +## Door Alignment Verification + +Critical: Doors between two connecting rooms must align exactly. + +```javascript +function verifyDoorAlignment(room1Id, room2Id, door1Pos, door2Pos, direction) { + const tolerance = 1; // 1px tolerance for floating point errors + + const deltaX = Math.abs(door1Pos.x - door2Pos.x); + const deltaY = Math.abs(door1Pos.y - door2Pos.y); + + if (deltaX > tolerance || deltaY > tolerance) { + console.error(`❌ Door misalignment between ${room1Id} and ${room2Id}`); + console.error(` ${room1Id} door: (${door1Pos.x}, ${door1Pos.y})`); + console.error(` ${room2Id} door: (${door2Pos.x}, ${door2Pos.y})`); + console.error(` Delta: (${deltaX}, ${deltaY}) [tolerance: ${tolerance}]`); + return false; + } + + console.log(`✅ Door alignment verified: ${room1Id} ↔ ${room2Id}`); + return true; +} +``` + +## Complete Door Placement Algorithm + +```javascript +function calculateDoorPositions(roomId, roomPosition, roomDimensions, connections, allPositions, allDimensions) { + const doors = []; + const gridCoords = worldToGrid(roomPosition.x, roomPosition.y); + + // Process each direction + ['north', 'south', 'east', 'west'].forEach(direction => { + if (!connections[direction]) return; + + const connected = connections[direction]; + const connectedRooms = Array.isArray(connected) ? connected : [connected]; + + let doorPositions; + + // Calculate door positions based on direction and count + if (direction === 'north') { + doorPositions = connectedRooms.length === 1 + ? [{ connectedRoom: connectedRooms[0], ...placeNorthDoorSingle(roomId, roomPosition, roomDimensions, gridCoords) }] + : placeNorthDoorsMultiple(roomId, roomPosition, roomDimensions, connectedRooms); + } else if (direction === 'south') { + doorPositions = connectedRooms.length === 1 + ? [{ connectedRoom: connectedRooms[0], ...placeSouthDoorSingle(roomId, roomPosition, roomDimensions, gridCoords) }] + : placeSouthDoorsMultiple(roomId, roomPosition, roomDimensions, connectedRooms); + } else if (direction === 'east') { + doorPositions = placeEastDoorsMultiple(roomId, roomPosition, roomDimensions, connectedRooms); + } else if (direction === 'west') { + doorPositions = placeWestDoorsMultiple(roomId, roomPosition, roomDimensions, connectedRooms); + } + + // Add to doors list + doorPositions.forEach(doorPos => { + doors.push({ + roomId, + connectedRoom: doorPos.connectedRoom, + direction, + x: doorPos.x, + y: doorPos.y + }); + }); + }); + + return doors; +} +``` + +## Door Sprite Creation + +Unchanged from current implementation, but now uses calculated positions: + +```javascript +function createDoorSprite(doorInfo, gameInstance) { + const { roomId, connectedRoom, direction, x, y } = doorInfo; + + // Create door sprite + const doorSprite = gameInstance.add.sprite(x, y, getDoorTexture(direction)); + doorSprite.setOrigin(0.5, 0.5); + doorSprite.setDepth(y + 0.45); + + // Set up door properties + doorSprite.doorProperties = { + roomId, + connectedRoom, + direction, + worldX: x, + worldY: y, + open: false, + locked: getLockedState(connectedRoom), + lockType: getLockType(connectedRoom), + // ... other properties + }; + + // Set up collision and interaction + setupDoorPhysics(doorSprite, gameInstance); + setupDoorInteraction(doorSprite, gameInstance); + + return doorSprite; +} + +function getDoorTexture(direction) { + if (direction === 'north' || direction === 'south') { + return 'door_32'; + } else { + return 'door_side_sheet_32'; + } +} +``` + +## Special Cases + +### Connecting Rooms of Different Sizes + +When a small room connects to a large room: + +``` + [Small - 1 grid unit] + [Large - 4 grid units] +``` + +The small room's door will be in its corner (deterministic placement). +The large room's door will align with the small room's door position. + +**Implementation**: Both rooms calculate their door positions independently, but because positioning is deterministic and based on the same grid alignment, doors will align. + +### Hallway Connectors + +Hallways are just narrow rooms: + +``` + [Room1][Room2] + [---Hallway--] + [---Room0---] +``` + +- Hallway is 4 grid units wide × 1 grid unit tall +- Has 2 north doors (connecting to Room1 and Room2) +- Has 1 south door (connecting to Room0) +- Door placement follows standard rules + +### Corner vs Center Placement + +For aesthetic variety, doors alternate left/right based on grid coordinates: + +``` +Vertical stack of rooms: + [Room3] <- Door on left (grid sum = odd) + [Room2] <- Door on right (grid sum = even) + [Room1] <- Door on left (grid sum = odd) + [Room0] <- Starting room +``` + +This creates a more interesting zigzag pattern rather than all doors being on the same side. + +## Testing Door Placement + +```javascript +function testDoorPlacement() { + // Test case: Two rooms connected north-south + const room1 = { + id: 'room1', + position: { x: 0, y: 0 }, + dimensions: { widthPx: 320, heightPx: 256 } + }; + + const room2 = { + id: 'room2', + position: { x: 0, y: -192 }, // Stacking height = 192px + dimensions: { widthPx: 320, heightPx: 256 } + }; + + // Calculate door positions + const room1Door = calculateDoorPositions('room1', room1.position, room1.dimensions, + { north: 'room2' }, {}, {}); + + const room2Door = calculateDoorPositions('room2', room2.position, room2.dimensions, + { south: 'room1' }, {}, {}); + + // Verify alignment + verifyDoorAlignment('room1', 'room2', + room1Door[0], room2Door[0], 'north'); + + // Expected: Doors align exactly at same (x, y) world position +} +``` + +## Migration from Current System + +Current system has special logic for detecting which side to place doors based on the connecting room's connections. This is replaced with: + +1. **Grid-based deterministic placement**: Uses `(gridX + gridY) % 2` for left/right +2. **Simpler logic**: No need to check connecting room's connections +3. **More flexible**: Works with any room size combinations + +The current code in `js/systems/doors.js` lines 86-159 will be replaced with the new door placement functions. diff --git a/planning_notes/new_room_layout/GRID_SYSTEM.md b/planning_notes/new_room_layout/GRID_SYSTEM.md new file mode 100644 index 0000000..b821017 --- /dev/null +++ b/planning_notes/new_room_layout/GRID_SYSTEM.md @@ -0,0 +1,241 @@ +# Grid Unit System + +## Definition + +The **grid unit** is the fundamental building block for room sizing and positioning. + +### Base Grid Unit +- **Width**: 5 tiles = 160px +- **Height**: 4 tiles = 128px (stacking height, excludes top 2 visual wall tiles) +- **Total Height**: 6 tiles = 192px (including top 2 visual wall tiles) + +## Room Size Specification + +### In Tiled +Rooms are created in Tiled using standard 32px tiles: +- Total room dimensions include all tiles (walls + floor + visual top) +- Example: Standard room is 10 tiles wide × 8 tiles tall + +### In Code +Rooms are tracked using grid units for positioning: +- **Grid Width**: `Math.floor(tileWidth / 5)` +- **Grid Height**: `Math.floor((tileHeight - 2) / 4)` (excludes visual top) +- Example: Standard room (10×8 tiles) = 2×1.5 grid units + - But for alignment, we treat as 2×2 grid units (10 tiles wide × (2 + 4 + 2) tiles tall) + +### Calculation Formula + +```javascript +const TILE_SIZE = 32; // pixels +const GRID_UNIT_WIDTH_TILES = 5; +const GRID_UNIT_HEIGHT_TILES = 4; // stackable area only +const VISUAL_TOP_TILES = 2; + +// Convert tile dimensions to grid units +function tilesToGridUnits(tileWidth, tileHeight) { + const gridWidth = Math.floor(tileWidth / GRID_UNIT_WIDTH_TILES); + const stackingHeight = tileHeight - VISUAL_TOP_TILES; + const gridHeight = Math.floor(stackingHeight / GRID_UNIT_HEIGHT_TILES); + + return { gridWidth, gridHeight }; +} + +// Convert grid units to pixel dimensions +function gridUnitsToPixels(gridWidth, gridHeight) { + const pixelWidth = gridWidth * GRID_UNIT_WIDTH_TILES * TILE_SIZE; + const stackingHeight = gridHeight * GRID_UNIT_HEIGHT_TILES * TILE_SIZE; + const totalHeight = stackingHeight + (VISUAL_TOP_TILES * TILE_SIZE); + + return { + width: pixelWidth, + height: totalHeight, + stackingHeight: stackingHeight + }; +} +``` + +## Valid Room Sizes + +All rooms must be exact multiples of grid units in both dimensions. + +### Standard Sizes + +**IMPORTANT**: Total room height must equal: `2 + (gridHeight × 4)` tiles +- 2 tiles for visual top wall +- gridHeight × 4 tiles for stackable area + +**Valid Heights**: 6, 10, 14, 18, 22, 26... (formula: 2 + 4N where N ≥ 1) + +| Room Type | Tiles (W×H) | Grid Units | Pixels (W×H) | Formula Check | +|-----------|-------------|------------|--------------|---------------| +| Closet | 5×6 | 1×1 | 160×192 | 2 + (1×4) = 6 ✓ | +| Standard | 10×10 | 2×2 | 320×320 | 2 + (2×4) = 10 ✓ | +| Wide Hall | 20×6 | 4×1 | 640×192 | 2 + (1×4) = 6 ✓ | +| Tall Hall | 10×6 | 2×1 | 320×192 | 2 + (1×4) = 6 ✓ | +| Tall Room | 10×14 | 2×3 | 320×448 | 2 + (3×4) = 14 ✓ | +| Large Room | 15×10 | 3×2 | 480×320 | 2 + (2×4) = 10 ✓ | + +### Important Notes + +1. **Total Height Calculation**: + - Grid units count stackable area only (4 tiles per grid unit) + - Add 2 tiles for visual top wall + - **Formula**: totalHeight = 2 + (gridHeight × 4) + - **Valid heights ONLY**: 6, 10, 14, 18, 22, 26... (increments of 4 after initial 2) + +2. **Minimum Floor Space**: + - After removing walls (1 tile each side) + - Minimum: 3 tiles wide × 2 tiles tall + - Closet (5×6): 3×2 floor area + +3. **Door Space**: + - North/South doors: 1 tile wide, need 1.5 tile inset + - East/West doors: 1 tile tall, placed at edges + - Minimum width: 5 tiles (supports 1 door per side) + - Multiple E/W doors: minimum 8 tiles height recommended + +4. **Invalid Room Sizes**: + - Width not multiple of 5: ❌ Invalid + - Height not matching formula: ❌ Invalid (e.g., 8, 9, 11, 12, 13 are all invalid) + - Height less than 6: ❌ Too small + +## Grid Coordinate System + +### Purpose +Used for deterministic door placement and overlap detection. + +### Coordinates + +``` +Grid Origin (0, 0) = Starting Room Top-Left + + -2 -1 0 1 2 3 (grid X) +-2 [ ][ ][ ][ ][ ][ ] +-1 [ ][ ][ ][ ][ ][ ] + 0 [ ][ ][R0][R0][ ][ ] <- Starting room at (0,0), size 2×2 + 1 [ ][ ][R0][R0][ ][ ] + 2 [ ][ ][ ][ ][ ][ ] + 3 [ ][ ][ ][ ][ ][ ] + +(grid Y) +``` + +### Conversion + +```javascript +// World position to grid coordinates +function worldToGrid(worldX, worldY) { + const gridX = Math.floor(worldX / (GRID_UNIT_WIDTH_TILES * TILE_SIZE)); + const gridY = Math.floor(worldY / (GRID_UNIT_HEIGHT_TILES * TILE_SIZE)); + return { gridX, gridY }; +} + +// Grid coordinates to world position +function gridToWorld(gridX, gridY) { + const worldX = gridX * GRID_UNIT_WIDTH_TILES * TILE_SIZE; + const worldY = gridY * GRID_UNIT_HEIGHT_TILES * TILE_SIZE; + return { worldX, worldY }; +} +``` + +## Overlap Detection + +### Algorithm + +```javascript +function checkRoomOverlap(room1, room2) { + // Get grid positions and sizes + const r1 = { + gridX: room1.gridX, + gridY: room1.gridY, + gridWidth: room1.gridWidth, + gridHeight: room1.gridHeight + }; + + const r2 = { + gridX: room2.gridX, + gridY: room2.gridY, + gridWidth: room2.gridWidth, + gridHeight: room2.gridHeight + }; + + // Check for overlap using AABB (Axis-Aligned Bounding Box) + const noOverlap = ( + r1.gridX + r1.gridWidth <= r2.gridX || // r1 is left of r2 + r2.gridX + r2.gridWidth <= r1.gridX || // r2 is left of r1 + r1.gridY + r1.gridHeight <= r2.gridY || // r1 is above r2 + r2.gridY + r2.gridHeight <= r1.gridY // r2 is above r1 + ); + + return !noOverlap; +} +``` + +### Usage +- Check all room pairs after positioning +- Log errors for any overlaps found +- Continue loading despite overlaps (for debugging) + +## Alignment Requirements + +### Room Positions +All room positions must align to grid boundaries: + +```javascript +function validateRoomAlignment(worldX, worldY) { + const gridUnitWidthPx = GRID_UNIT_WIDTH_TILES * TILE_SIZE; + const gridUnitHeightPx = GRID_UNIT_HEIGHT_TILES * TILE_SIZE; + + const alignedX = (worldX % gridUnitWidthPx) === 0; + const alignedY = (worldY % gridUnitHeightPx) === 0; + + if (!alignedX || !alignedY) { + console.error(`Room not aligned to grid: (${worldX}, ${worldY})`); + console.error(`Expected multiples of (${gridUnitWidthPx}, ${gridUnitHeightPx})`); + } + + return alignedX && alignedY; +} +``` + +### Door Alignment +Doors between rooms must align perfectly: +- Both rooms calculate door position independently +- Positions must match exactly (within 1px tolerance for floating point) +- Misalignment indicates positioning error + +## Migration Notes + +### Existing Rooms +Current standard rooms (320×320px) are 10×10 tiles: +- **New interpretation**: 2×2 grid units with extra tiles +- **Actual size needed**: 10×8 tiles (2×2 grid units) +- **Action**: Update room JSONs to 10×8 tiles + +### Backward Compatibility +The system should gracefully handle non-aligned rooms: +- Calculate nearest grid position +- Log warning about alignment +- Continue with nearest valid position +- **Note**: Not needed per requirements, but good for debugging + +## Testing + +### Unit Tests +```javascript +// Test grid unit calculations +assert(tilesToGridUnits(5, 6) === {gridWidth: 1, gridHeight: 1}); +assert(tilesToGridUnits(10, 8) === {gridWidth: 2, gridHeight: 1.5}); // rounds to 2×2 +assert(tilesToGridUnits(20, 6) === {gridWidth: 4, gridHeight: 1}); + +// Test grid alignment +assert(validateRoomAlignment(0, 0) === true); +assert(validateRoomAlignment(160, 128) === true); +assert(validateRoomAlignment(100, 100) === false); +``` + +### Integration Tests +- Create scenario with various room sizes +- Verify all rooms align to grid +- Verify no overlaps +- Verify door alignment diff --git a/planning_notes/new_room_layout/IMPLEMENTATION_STEPS.md b/planning_notes/new_room_layout/IMPLEMENTATION_STEPS.md new file mode 100644 index 0000000..e22e61e --- /dev/null +++ b/planning_notes/new_room_layout/IMPLEMENTATION_STEPS.md @@ -0,0 +1,714 @@ +# Implementation Steps + +## Phase 1: Constants and Helper Functions + +### Step 1.1: Add Grid Unit Constants + +**File**: `js/utils/constants.js` + +Add new constants for grid units: + +```javascript +// Existing constants +export const TILE_SIZE = 32; +export const DOOR_ALIGN_OVERLAP = 64; + +// NEW: Grid unit system constants +export const GRID_UNIT_WIDTH_TILES = 5; // 5 tiles wide +export const GRID_UNIT_HEIGHT_TILES = 4; // 4 tiles tall (stacking area) +export const VISUAL_TOP_TILES = 2; // Top 2 rows are visual wall + +// Calculated grid unit sizes in pixels +export const GRID_UNIT_WIDTH_PX = GRID_UNIT_WIDTH_TILES * TILE_SIZE; // 160px +export const GRID_UNIT_HEIGHT_PX = GRID_UNIT_HEIGHT_TILES * TILE_SIZE; // 128px +``` + +**Testing**: Verify constants are exported and accessible + +--- + +### Step 1.2: Create Grid Conversion Functions + +**File**: `js/core/rooms.js` (add near top, before calculateRoomPositions) + +```javascript +/** + * Convert tile dimensions to grid units + * + * Grid units are the base stacking size: 5 tiles wide × 4 tiles tall + * (excluding top 2 visual wall tiles) + * + * @param {number} widthTiles - Room width in tiles + * @param {number} heightTiles - Room height in tiles (including visual wall) + * @returns {{gridWidth: number, gridHeight: number}} + */ +function tilesToGridUnits(widthTiles, heightTiles) { + const gridWidth = Math.floor(widthTiles / GRID_UNIT_WIDTH_TILES); + + // Subtract visual top wall tiles before calculating grid height + const stackingHeightTiles = heightTiles - VISUAL_TOP_TILES; + const gridHeight = Math.floor(stackingHeightTiles / GRID_UNIT_HEIGHT_TILES); + + return { gridWidth, gridHeight }; +} + +/** + * Convert grid coordinates to world position + * + * Grid coordinates are positions in grid unit space. + * This converts them to pixel world coordinates. + * + * @param {number} gridX - Grid X coordinate + * @param {number} gridY - Grid Y coordinate + * @returns {{x: number, y: number}} + */ +function gridToWorld(gridX, gridY) { + return { + x: gridX * GRID_UNIT_WIDTH_PX, + y: gridY * GRID_UNIT_HEIGHT_PX + }; +} + +/** + * Convert world position to grid coordinates + * + * @param {number} worldX - World X position in pixels + * @param {number} worldY - World Y position in pixels + * @returns {{gridX: number, gridY: number}} + */ +function worldToGrid(worldX, worldY) { + return { + gridX: Math.floor(worldX / GRID_UNIT_WIDTH_PX), + gridY: Math.floor(worldY / GRID_UNIT_HEIGHT_PX) + }; +} + +/** + * Align a world position to the nearest grid boundary + * + * Ensures rooms are positioned at grid unit boundaries + * + * @param {number} worldX - World X position + * @param {number} worldY - World Y position + * @returns {{x: number, y: number}} + */ +function alignToGrid(worldX, worldY) { + return { + x: Math.round(worldX / GRID_UNIT_WIDTH_PX) * GRID_UNIT_WIDTH_PX, + y: Math.round(worldY / GRID_UNIT_HEIGHT_PX) * GRID_UNIT_HEIGHT_PX + }; +} +``` + +**Testing**: Create unit tests for conversion functions + +--- + +### Step 1.3: Create Room Dimension Extraction Function + +**File**: `js/core/rooms.js` (add before calculateRoomPositions) + +```javascript +/** + * Extract room dimensions from Tiled JSON data + * + * Reads the tilemap to get room size and calculates: + * - Tile dimensions + * - Pixel dimensions + * - Grid units + * - Stacking height (for positioning calculations) + * + * @param {string} roomId - Room identifier + * @param {Object} roomData - Room data from scenario + * @param {Phaser.Game} gameInstance - Game instance for accessing tilemaps + * @returns {Object} Dimension data + */ +function getRoomDimensions(roomId, roomData, gameInstance) { + const map = gameInstance.cache.tilemap.get(roomData.type); + + let widthTiles, heightTiles; + + // Try different ways to access tilemap data + if (map.json) { + widthTiles = map.json.width; + heightTiles = map.json.height; + } else if (map.data) { + widthTiles = map.data.width; + heightTiles = map.data.height; + } else { + // Fallback to standard room size + console.warn(`Could not read dimensions for ${roomId}, using default 10×8`); + widthTiles = 10; + heightTiles = 8; + } + + // Calculate grid units + const { gridWidth, gridHeight } = tilesToGridUnits(widthTiles, heightTiles); + + // Calculate pixel dimensions + const widthPx = widthTiles * TILE_SIZE; + const heightPx = heightTiles * TILE_SIZE; + const stackingHeightPx = (heightTiles - VISUAL_TOP_TILES) * TILE_SIZE; + + return { + widthTiles, + heightTiles, + widthPx, + heightPx, + stackingHeightPx, + gridWidth, + gridHeight + }; +} +``` + +**Testing**: Verify dimensions extracted correctly for test room + +--- + +## Phase 2: Room Positioning Algorithm + +### Step 2.1: Create Single Room Positioning Functions + +**File**: `js/core/rooms.js` (add before calculateRoomPositions) + +Implement these functions as described in POSITIONING_ALGORITHM.md: + +```javascript +function positionNorthSingle(currentRoom, connectedRoom, currentPos, dimensions) { /* ... */ } +function positionSouthSingle(currentRoom, connectedRoom, currentPos, dimensions) { /* ... */ } +function positionEastSingle(currentRoom, connectedRoom, currentPos, dimensions) { /* ... */ } +function positionWestSingle(currentRoom, connectedRoom, currentPos, dimensions) { /* ... */ } +``` + +**Reference**: See POSITIONING_ALGORITHM.md for complete implementations + +**Testing**: +- Test each direction individually +- Verify grid alignment +- Verify centering logic + +--- + +### Step 2.2: Create Multiple Room Positioning Functions + +**File**: `js/core/rooms.js` + +```javascript +function positionNorthMultiple(currentRoom, connectedRooms, currentPos, dimensions) { /* ... */ } +function positionSouthMultiple(currentRoom, connectedRooms, currentPos, dimensions) { /* ... */ } +function positionEastMultiple(currentRoom, connectedRooms, currentPos, dimensions) { /* ... */ } +function positionWestMultiple(currentRoom, connectedRooms, currentPos, dimensions) { /* ... */ } +``` + +**Reference**: See POSITIONING_ALGORITHM.md + +**Testing**: +- Test with 2 rooms +- Test with 3+ rooms +- Verify even spacing + +--- + +### Step 2.3: Create Router Functions + +**File**: `js/core/rooms.js` + +```javascript +function positionSingleRoom(direction, currentRoom, connectedRoom, currentPos, dimensions) { + switch (direction) { + case 'north': return positionNorthSingle(currentRoom, connectedRoom, currentPos, dimensions); + case 'south': return positionSouthSingle(currentRoom, connectedRoom, currentPos, dimensions); + case 'east': return positionEastSingle(currentRoom, connectedRoom, currentPos, dimensions); + case 'west': return positionWestSingle(currentRoom, connectedRoom, currentPos, dimensions); + default: + console.error(`Unknown direction: ${direction}`); + return currentPos; + } +} + +function positionMultipleRooms(direction, currentRoom, connectedRooms, currentPos, dimensions) { + switch (direction) { + case 'north': return positionNorthMultiple(currentRoom, connectedRooms, currentPos, dimensions); + case 'south': return positionSouthMultiple(currentRoom, connectedRooms, currentPos, dimensions); + case 'east': return positionEastMultiple(currentRoom, connectedRooms, currentPos, dimensions); + case 'west': return positionWestMultiple(currentRoom, connectedRooms, currentPos, dimensions); + default: + console.error(`Unknown direction: ${direction}`); + return {}; + } +} +``` + +**Testing**: Verify routing works for all directions + +--- + +### Step 2.4: Rewrite calculateRoomPositions Function + +**File**: `js/core/rooms.js` (lines 644-786) + +**Current function**: Replace the entire function with new algorithm + +**New implementation**: See POSITIONING_ALGORITHM.md for complete algorithm + +**Key changes**: +1. Extract all room dimensions first +2. Support all 4 directions (north, south, east, west) +3. Use breadth-first processing +4. Call new positioning functions +5. Align all positions to grid + +**Testing**: +- Test with existing scenarios +- Verify starting room at (0, 0) +- Verify breadth-first ordering +- Verify all rooms positioned + +--- + +## Phase 3: Door Placement + +### Step 3.1: Create Door Placement Functions + +**File**: `js/systems/doors.js` (add before createDoorSpritesForRoom) + +Implement door placement functions as described in DOOR_PLACEMENT.md: + +```javascript +function placeNorthDoorSingle(roomId, roomPosition, roomDimensions, gridCoords) { /* ... */ } +function placeNorthDoorsMultiple(roomId, roomPosition, roomDimensions, connectedRooms) { /* ... */ } +function placeSouthDoorSingle(roomId, roomPosition, roomDimensions, gridCoords) { /* ... */ } +function placeSouthDoorsMultiple(roomId, roomPosition, roomDimensions, connectedRooms) { /* ... */ } +function placeEastDoorSingle(roomId, roomPosition, roomDimensions) { /* ... */ } +function placeEastDoorsMultiple(roomId, roomPosition, roomDimensions, connectedRooms) { /* ... */ } +function placeWestDoorSingle(roomId, roomPosition, roomDimensions) { /* ... */ } +function placeWestDoorsMultiple(roomId, roomPosition, roomDimensions, connectedRooms) { /* ... */ } +``` + +**Reference**: See DOOR_PLACEMENT.md for complete implementations + +**Testing**: +- Test each direction +- Test single vs multiple doors +- Verify deterministic placement + +--- + +### Step 3.2: Update createDoorSpritesForRoom Function + +**File**: `js/systems/doors.js` (lines 47-308) + +**Current**: Has hardcoded door positioning logic + +**Changes**: +1. Get room dimensions using `getRoomDimensions()` +2. Calculate grid coordinates using `worldToGrid()` +3. For each connection direction: + - Determine single vs multiple connections + - Call appropriate door placement function + - Create door sprite at returned position +4. Remove old positioning logic (lines 86-187) + +**Testing**: +- Verify doors created at correct positions +- Verify door sprites have correct properties +- Verify collision zones work + +--- + +### Step 3.3: Update removeTilesUnderDoor Function + +**File**: `js/systems/collision.js` (lines 154-335) + +**Current**: Duplicates door positioning logic + +**Changes**: +1. Import door placement functions from doors.js +2. Use same door positioning functions as door sprites +3. Remove duplicate positioning logic (lines 197-283) +4. Ensure door position calculation matches exactly + +**Alternative**: Create shared `calculateDoorPositions()` function used by both systems + +**Testing**: +- Verify wall tiles removed at correct locations +- Verify removed tiles match door sprite positions +- No visual gaps or overlaps + +--- + +## Phase 4: Validation + +### Step 4.1: Create Validation Functions + +**File**: `js/core/rooms.js` or new file `js/core/validation.js` + +Implement validation functions from VALIDATION.md: + +```javascript +function validateRoomSize(roomId, dimensions) { /* ... */ } +function validateGridAlignment(roomId, position) { /* ... */ } +function detectRoomOverlaps(positions, dimensions) { /* ... */ } +function validateConnections(gameScenario) { /* ... */ } +function validateDoorAlignment(allDoors) { /* ... */ } +function validateStartingRoom(gameScenario) { /* ... */ } +function validateScenario(gameScenario, positions, dimensions, allDoors) { /* ... */ } +``` + +**Reference**: See VALIDATION.md + +**Testing**: Create test scenarios with known errors + +--- + +### Step 4.2: Integrate Validation into initializeRooms + +**File**: `js/core/rooms.js` (function initializeRooms, lines 548-576) + +**Add after** `calculateRoomPositions()`: + +```javascript +export function initializeRooms(gameInstance) { + // ... existing code ... + + // Calculate room positions for lazy loading + window.roomPositions = calculateRoomPositions(gameInstance); + console.log('Room positions calculated for lazy loading'); + + // NEW: Extract dimensions for validation + const dimensions = {}; + Object.entries(gameScenario.rooms).forEach(([roomId, roomData]) => { + dimensions[roomId] = getRoomDimensions(roomId, roomData, gameInstance); + }); + + // NEW: Calculate all door positions (for validation) + const allDoors = []; + Object.entries(gameScenario.rooms).forEach(([roomId, roomData]) => { + if (roomData.connections) { + const doors = calculateDoorPositionsForRoom( + roomId, + window.roomPositions[roomId], + dimensions[roomId], + roomData.connections, + window.roomPositions, + dimensions + ); + allDoors.push(...doors); + } + }); + + // NEW: Validate scenario + const validationResults = validateScenario( + gameScenario, + window.roomPositions, + dimensions, + allDoors + ); + + // Store validation results for debugging + window.scenarioValidation = validationResults; + + // Continue initialization even if validation fails + // (log errors but attempt to continue per requirements) + + // ... rest of existing code ... +} +``` + +**Testing**: Verify validation runs on scenario load + +--- + +## Phase 5: Testing and Refinement + +### Step 5.1: Test with Existing Scenarios + +Test each existing scenario: + +1. `scenario1.json` (biometric_breach) +2. `cybok_heist.json` +3. `scenario2.json` +4. `ceo_exfil.json` + +**Verify**: +- All rooms positioned correctly +- No overlaps +- All doors align +- Player can navigate between rooms +- No visual gaps or clipping + +--- + +### Step 5.2: Create Test Scenarios + +Create new test scenarios to verify features: + +**Test 1: Different Room Sizes** +```json +{ + "startRoom": "small", + "rooms": { + "small": { + "type": "room_closet", // 5×6 tiles + "connections": { "north": "medium" } + }, + "medium": { + "type": "room_office", // 10×8 tiles + "connections": { "north": "large", "south": "small" } + }, + "large": { + "type": "room_wide_hall", // 20×6 tiles (hypothetical) + "connections": { "south": "medium" } + } + } +} +``` + +**Test 2: East/West Connections** +```json +{ + "startRoom": "center", + "rooms": { + "center": { + "type": "room_office", + "connections": { + "east": "east_room", + "west": "west_room" + } + }, + "east_room": { + "type": "room_office", + "connections": { "west": "center" } + }, + "west_room": { + "type": "room_office", + "connections": { "east": "center" } + } + } +} +``` + +**Test 3: Multiple Connections** +```json +{ + "startRoom": "base", + "rooms": { + "base": { + "type": "room_office", + "connections": { + "north": ["north1", "north2", "north3"] + } + }, + "north1": { + "type": "room_office", + "connections": { "south": "base" } + }, + "north2": { + "type": "room_office", + "connections": { "south": "base" } + }, + "north3": { + "type": "room_office", + "connections": { "south": "base" } + } + } +} +``` + +**Test 4: Complex Layout** +```json +{ + "startRoom": "reception", + "rooms": { + "reception": { + "type": "room_reception", + "connections": { "north": "hallway" } + }, + "hallway": { + "type": "room_wide_hall", + "connections": { + "north": ["office1", "office2"], + "south": "reception" + } + }, + "office1": { + "type": "room_office", + "connections": { + "south": "hallway", + "east": "office2" + } + }, + "office2": { + "type": "room_office", + "connections": { + "south": "hallway", + "west": "office1" + } + } + } +} +``` + +--- + +### Step 5.3: Debug and Fix Issues + +**Common issues to watch for**: + +1. **Floating point errors**: Door positions off by 1-2px + - Fix: Use `Math.round()` for all position calculations + +2. **Grid misalignment**: Rooms not aligned to grid boundaries + - Fix: Use `alignToGrid()` function + +3. **Door misalignment**: Doors don't line up between rooms + - Fix: Ensure both rooms use same calculation logic + +4. **Visual gaps**: Rooms don't touch properly + - Fix: Check stacking height calculations + +5. **Overlap false positives**: Validation reports overlaps that don't exist + - Fix: Check overlap detection logic uses stacking height + +--- + +## Phase 6: Documentation and Cleanup + +### Step 6.1: Add Code Comments + +Add detailed comments to new functions explaining: +- Purpose of function +- How grid units work +- Why certain calculations are done +- Edge cases handled + +**Follow the style**: See existing comments in rooms.js for reference + +--- + +### Step 6.2: Update Console Logging + +Add helpful debug logging: + +```javascript +// Grid unit conversion +console.log(`Room ${roomId}: ${widthTiles}×${heightTiles} tiles = ${gridWidth}×${gridHeight} grid units`); + +// Room positioning +console.log(`Positioned ${roomId} at grid(${gridX}, ${gridY}) = world(${worldX}, ${worldY})`); + +// Door placement +console.log(`Door at (${doorX}, ${doorY}) for ${roomId} → ${connectedRoom} (${direction})`); + +// Validation +console.log(`✅ All ${doorCount} doors aligned correctly`); +console.log(`❌ Overlap detected: ${room1} and ${room2}`); +``` + +--- + +### Step 6.3: Create Debug Tools + +Add console commands for debugging: + +```javascript +// Show room bounds +window.showRoomBounds = function() { /* visualize room stacking areas */ }; + +// Show grid +window.showGrid = function() { /* draw grid unit overlay */ }; + +// Check scenario +window.checkScenario = function() { /* print validation results */ }; + +// List rooms +window.listRooms = function() { /* print all room positions and sizes */ }; +``` + +--- + +## Implementation Checklist + +Use this checklist to track progress: + +- [ ] Phase 1: Constants and Helpers + - [ ] Add grid unit constants + - [ ] Create grid conversion functions + - [ ] Create room dimension extraction + - [ ] Test helper functions + +- [ ] Phase 2: Room Positioning + - [ ] Implement single room positioning (4 directions) + - [ ] Implement multiple room positioning (4 directions) + - [ ] Create router functions + - [ ] Rewrite calculateRoomPositions + - [ ] Test with existing scenarios + +- [ ] Phase 3: Door Placement + - [ ] Implement door placement functions + - [ ] Update createDoorSpritesForRoom + - [ ] Update removeTilesUnderDoor + - [ ] Test door alignment + +- [ ] Phase 4: Validation + - [ ] Create validation functions + - [ ] Integrate into initializeRooms + - [ ] Test validation with error scenarios + +- [ ] Phase 5: Testing + - [ ] Test all existing scenarios + - [ ] Create and test new scenarios + - [ ] Debug and fix issues + +- [ ] Phase 6: Documentation + - [ ] Add code comments + - [ ] Update console logging + - [ ] Create debug tools + - [ ] Update user-facing documentation + +--- + +## Estimated Time + +- Phase 1: 2-3 hours +- Phase 2: 4-6 hours +- Phase 3: 3-4 hours +- Phase 4: 2-3 hours +- Phase 5: 4-6 hours +- Phase 6: 2-3 hours + +**Total**: 17-25 hours of implementation time + +--- + +## Risk Mitigation + +### Backup Current Code + +Before starting, create backup: +```bash +git checkout -b backup/before-room-layout-refactor +git commit -am "Backup before room layout refactor" +git checkout claude/review-room-layout-system-01Tk2U5qUChpAemwRVNFDR7t +``` + +### Incremental Implementation + +Implement and test each phase before moving to next: +1. Complete Phase 1 → Test → Commit +2. Complete Phase 2 → Test → Commit +3. etc. + +### Keep Old Code Available + +Comment out old code instead of deleting initially: +```javascript +// OLD IMPLEMENTATION - Remove after testing +// function oldCalculateRoomPositions() { ... } + +// NEW IMPLEMENTATION +function calculateRoomPositions() { ... } +``` + +This allows easy comparison if issues arise. diff --git a/planning_notes/new_room_layout/OVERVIEW.md b/planning_notes/new_room_layout/OVERVIEW.md new file mode 100644 index 0000000..46d98a3 --- /dev/null +++ b/planning_notes/new_room_layout/OVERVIEW.md @@ -0,0 +1,74 @@ +# Room Layout System Redesign - Overview + +## Current System Limitations + +The existing room positioning system has several constraints: + +1. **Fixed Room Size**: All rooms are 320x320px (10x10 tiles) +2. **Limited Connections**: Only supports north/south connections with up to 2 rooms +3. **Gap Issues**: When branching north to multiple rooms, awkward gaps appear between rooms +4. **Corner-Only Doors**: North doors must be in corners, creating alignment issues +5. **No East/West Support**: Cannot connect rooms horizontally + +## Goals of the Redesign + +1. **Flexible Room Sizes**: Support rooms in multiples of grid units (5x4 tiles base) +2. **Better Alignment**: Stack rooms against each other with no gaps +3. **4-Direction Support**: Enable north, south, east, and west connections +4. **Smarter Door Placement**: + - North/South doors still in corners for visual consistency + - East/West doors positioned based on connection count + - Deterministic door positioning using grid coordinates +5. **Overlap Detection**: Validate scenarios to prevent positioning conflicts +6. **Hallway Support**: Allow explicit hallway connectors in scenarios + +## Key Concepts + +### Grid Units +- **Base grid unit**: 5 tiles wide × 4 tiles tall (160px × 128px at 32px/tile) +- **Stacking size**: Excludes top 2 rows which overlap visually with rooms to the north +- All rooms must be sized in multiples of grid units (both X and Y) + +### Valid Room Sizes +- **Closet**: 5×4 tiles (1×1 grid units) - smallest room +- **Standard Room**: 10×8 tiles (2×2 grid units) - offices, reception, etc. +- **Hallways**: 10×4 or 20×4 tiles (2×1 or 4×1 grid units) +- **Large Rooms**: Any multiple of grid units (e.g., 10×16, 20×8, etc.) + +### Room Structure +``` +WWWWWWWWWW <- Top 2 rows: Visual wall (overlaps room to north) +WWWWWWWWWW +WFFFFFFFFW <- Stacking area begins (floor + side walls) +WFFFFFFFFW +WFFFFFFFFW +WFFFFFFFFW +WFFFFFFFFW <- Bottom row: Can overlap room to south +WFFFFFFFFW (treated as floor when overlapping) +``` + +## What Stays the Same + +1. **32px Tiles**: Core tile size unchanged +2. **Top-Down Orthogonal View**: Zelda-like perspective maintained +3. **Visual Overlapping**: North rooms still overlap south rooms visually +4. **Collision System**: Wall collision boxes at boundaries (except doors) +5. **Sprite-Based Doors**: Continue using door sprites with physics +6. **Lazy Loading**: Rooms still load on-demand + +## What Changes + +1. **Room Positioning Algorithm**: Complete rewrite to support grid units +2. **Door Placement Logic**: Enhanced for 4 directions and multiple sizes +3. **Connection Format**: Unchanged but interpreted differently +4. **Validation System**: New overlap detection on scenario load +5. **Wall Management**: Adapt to variable room sizes + +## Success Criteria + +- [ ] Rooms of different sizes align without gaps +- [ ] Doors always align perfectly when two rooms connect +- [ ] East/West connections work correctly +- [ ] Scenarios with overlapping rooms are detected and logged +- [ ] Existing scenarios (using 10×8 rooms) continue to work +- [ ] Door placement is deterministic and visually pleasing diff --git a/planning_notes/new_room_layout/POSITIONING_ALGORITHM.md b/planning_notes/new_room_layout/POSITIONING_ALGORITHM.md new file mode 100644 index 0000000..ae511e3 --- /dev/null +++ b/planning_notes/new_room_layout/POSITIONING_ALGORITHM.md @@ -0,0 +1,469 @@ +# Room Positioning Algorithm + +## Overview + +The positioning algorithm works outward from the starting room, processing rooms level-by-level in a breadth-first manner. This ensures deterministic positioning regardless of scenario structure. + +## High-Level Flow + +``` +1. Extract room dimensions from Tiled JSON +2. Place starting room at origin (0, 0) +3. Initialize queue with starting room +4. While queue not empty: + a. Pop current room from queue + b. For each connection direction (north, south, east, west): + - Calculate positions for connected rooms + - Add newly positioned rooms to queue +5. Validate all positions (check for overlaps) +6. Return position map +``` + +## Room Dimension Extraction + +### From Tiled JSON + +```javascript +function getRoomDimensions(roomId, roomData, gameInstance) { + const map = gameInstance.cache.tilemap.get(roomData.type); + + let widthTiles, heightTiles; + if (map.json) { + widthTiles = map.json.width; + heightTiles = map.json.height; + } else if (map.data) { + widthTiles = map.data.width; + heightTiles = map.data.height; + } else { + // Fallback to standard size + console.warn(`Could not read dimensions for ${roomId}, using default`); + widthTiles = 10; + heightTiles = 8; + } + + // Calculate grid units + const gridWidth = Math.floor(widthTiles / GRID_UNIT_WIDTH_TILES); + const stackingHeightTiles = heightTiles - VISUAL_TOP_TILES; + const gridHeight = Math.floor(stackingHeightTiles / GRID_UNIT_HEIGHT_TILES); + + return { + widthTiles, + heightTiles, + widthPx: widthTiles * TILE_SIZE, + heightPx: heightTiles * TILE_SIZE, + stackingHeightPx: stackingHeightTiles * TILE_SIZE, + gridWidth, + gridHeight + }; +} +``` + +## Positioning Constants + +```javascript +// For visual overlap between rooms +const VISUAL_OVERLAP_PX = 64; // 2 tiles (top wall overlaps) + +// Grid unit sizes in pixels +const GRID_UNIT_WIDTH_PX = GRID_UNIT_WIDTH_TILES * TILE_SIZE; // 160px +const GRID_UNIT_HEIGHT_PX = GRID_UNIT_HEIGHT_TILES * TILE_SIZE; // 128px +``` + +## Connection Processing + +### General Approach + +For each direction, we need to: +1. Determine how many rooms connect in that direction +2. Calculate total width/height needed for connected rooms +3. Position rooms to align properly +4. Ensure doors will align + +### North Connections + +**Single Room**: +``` + [Connected Room] + [Current Room] +``` + +```javascript +function positionNorthSingle(currentRoom, connectedRoom, currentPos, dimensions) { + const connectedDim = dimensions[connectedRoom]; + + // Center the connected room above current room + // Account for visual overlap (top 2 tiles of current room) + const x = currentPos.x + (currentDim.widthPx - connectedDim.widthPx) / 2; + const y = currentPos.y - connectedDim.stackingHeightPx; + + // Align to grid + return { + x: Math.round(x / GRID_UNIT_WIDTH_PX) * GRID_UNIT_WIDTH_PX, + y: Math.round(y / GRID_UNIT_HEIGHT_PX) * GRID_UNIT_HEIGHT_PX + }; +} +``` + +**Multiple Rooms**: +``` + [Room1][Room2] + [Current Room] +``` + +```javascript +function positionNorthMultiple(currentRoom, connectedRooms, currentPos, dimensions) { + const currentDim = dimensions[currentRoom]; + const positions = {}; + + // Calculate total width of all connected rooms + const totalWidth = connectedRooms.reduce((sum, roomId) => { + return sum + dimensions[roomId].widthPx; + }, 0); + + // Determine starting X position (center the group) + const startX = currentPos.x + (currentDim.widthPx - totalWidth) / 2; + + // Position each room left to right + let currentX = startX; + connectedRooms.forEach(roomId => { + const connectedDim = dimensions[roomId]; + + // Y position is based on stacking height + const y = currentPos.y - connectedDim.stackingHeightPx; + + // Align to grid + positions[roomId] = { + x: Math.round(currentX / GRID_UNIT_WIDTH_PX) * GRID_UNIT_WIDTH_PX, + y: Math.round(y / GRID_UNIT_HEIGHT_PX) * GRID_UNIT_HEIGHT_PX + }; + + currentX += connectedDim.widthPx; + }); + + return positions; +} +``` + +### South Connections + +**Single Room**: +``` + [Current Room] + [Connected Room] +``` + +```javascript +function positionSouthSingle(currentRoom, connectedRoom, currentPos, dimensions) { + const currentDim = dimensions[currentRoom]; + const connectedDim = dimensions[connectedRoom]; + + // Center the connected room below current room + const x = currentPos.x + (currentDim.widthPx - connectedDim.widthPx) / 2; + const y = currentPos.y + currentDim.stackingHeightPx; + + // Align to grid + return { + x: Math.round(x / GRID_UNIT_WIDTH_PX) * GRID_UNIT_WIDTH_PX, + y: Math.round(y / GRID_UNIT_HEIGHT_PX) * GRID_UNIT_HEIGHT_PX + }; +} +``` + +**Multiple Rooms**: +``` + [Current Room] + [Room1][Room2] +``` + +```javascript +function positionSouthMultiple(currentRoom, connectedRooms, currentPos, dimensions) { + const currentDim = dimensions[currentRoom]; + const positions = {}; + + // Calculate total width + const totalWidth = connectedRooms.reduce((sum, roomId) => { + return sum + dimensions[roomId].widthPx; + }, 0); + + // Center the group + const startX = currentPos.x + (currentDim.widthPx - totalWidth) / 2; + + // Position each room + let currentX = startX; + connectedRooms.forEach(roomId => { + const connectedDim = dimensions[roomId]; + + const y = currentPos.y + currentDim.stackingHeightPx; + + positions[roomId] = { + x: Math.round(currentX / GRID_UNIT_WIDTH_PX) * GRID_UNIT_WIDTH_PX, + y: Math.round(y / GRID_UNIT_HEIGHT_PX) * GRID_UNIT_HEIGHT_PX + }; + + currentX += connectedDim.widthPx; + }); + + return positions; +} +``` + +### East Connections + +**Single Room**: +``` + [Current][Connected] +``` + +```javascript +function positionEastSingle(currentRoom, connectedRoom, currentPos, dimensions) { + const currentDim = dimensions[currentRoom]; + const connectedDim = dimensions[connectedRoom]; + + // Position to the right, aligned at top (north edge) + const x = currentPos.x + currentDim.widthPx; + const y = currentPos.y; // Align north edges + + // Align to grid + return { + x: Math.round(x / GRID_UNIT_WIDTH_PX) * GRID_UNIT_WIDTH_PX, + y: Math.round(y / GRID_UNIT_HEIGHT_PX) * GRID_UNIT_HEIGHT_PX + }; +} +``` + +**Multiple Rooms**: +``` + [Current][Room1] + [Room2] +``` + +```javascript +function positionEastMultiple(currentRoom, connectedRooms, currentPos, dimensions) { + const currentDim = dimensions[currentRoom]; + const positions = {}; + + const startX = currentPos.x + currentDim.widthPx; + + // Stack vertically, starting at current room's Y + let currentY = currentPos.y; + connectedRooms.forEach(roomId => { + const connectedDim = dimensions[roomId]; + + positions[roomId] = { + x: Math.round(startX / GRID_UNIT_WIDTH_PX) * GRID_UNIT_WIDTH_PX, + y: Math.round(currentY / GRID_UNIT_HEIGHT_PX) * GRID_UNIT_HEIGHT_PX + }; + + currentY += connectedDim.stackingHeightPx; + }); + + return positions; +} +``` + +### West Connections + +Mirror of East connections, but positions to the left. + +```javascript +function positionWestSingle(currentRoom, connectedRoom, currentPos, dimensions) { + const connectedDim = dimensions[connectedRoom]; + + // Position to the left, aligned at top + const x = currentPos.x - connectedDim.widthPx; + const y = currentPos.y; + + return { + x: Math.round(x / GRID_UNIT_WIDTH_PX) * GRID_UNIT_WIDTH_PX, + y: Math.round(y / GRID_UNIT_HEIGHT_PX) * GRID_UNIT_HEIGHT_PX + }; +} +``` + +## Complete Algorithm Implementation + +```javascript +function calculateRoomPositions(gameScenario, gameInstance) { + const positions = {}; + const dimensions = {}; + const processed = new Set(); + const queue = []; + + console.log('=== Room Positioning Algorithm ==='); + + // 1. Extract all room dimensions + Object.entries(gameScenario.rooms).forEach(([roomId, roomData]) => { + dimensions[roomId] = getRoomDimensions(roomId, roomData, gameInstance); + console.log(`Room ${roomId}: ${dimensions[roomId].widthTiles}×${dimensions[roomId].heightTiles} tiles ` + + `(${dimensions[roomId].gridWidth}×${dimensions[roomId].gridHeight} grid units)`); + }); + + // 2. Place starting room at origin + const startRoom = gameScenario.startRoom; + positions[startRoom] = { x: 0, y: 0 }; + processed.add(startRoom); + queue.push(startRoom); + + console.log(`Starting room: ${startRoom} at (0, 0)`); + + // 3. Process rooms breadth-first + while (queue.length > 0) { + const currentRoomId = queue.shift(); + const currentRoom = gameScenario.rooms[currentRoomId]; + const currentPos = positions[currentRoomId]; + const currentDim = dimensions[currentRoomId]; + + console.log(`\nProcessing: ${currentRoomId} at (${currentPos.x}, ${currentPos.y})`); + + // Process each connection direction + ['north', 'south', 'east', 'west'].forEach(direction => { + if (!currentRoom.connections[direction]) return; + + const connected = currentRoom.connections[direction]; + const connectedRooms = Array.isArray(connected) ? connected : [connected]; + + // Filter out already processed rooms + const unprocessedRooms = connectedRooms.filter(id => !processed.has(id)); + if (unprocessedRooms.length === 0) return; + + console.log(` ${direction}: ${unprocessedRooms.join(', ')}`); + + // Calculate positions based on direction and count + let newPositions; + if (unprocessedRooms.length === 1) { + // Single room connection + const roomId = unprocessedRooms[0]; + const pos = positionSingleRoom(direction, currentRoomId, roomId, + currentPos, dimensions); + newPositions = { [roomId]: pos }; + } else { + // Multiple room connections + newPositions = positionMultipleRooms(direction, currentRoomId, + unprocessedRooms, currentPos, dimensions); + } + + // Apply positions and add to queue + Object.entries(newPositions).forEach(([roomId, pos]) => { + positions[roomId] = pos; + processed.add(roomId); + queue.push(roomId); + console.log(` ${roomId} positioned at (${pos.x}, ${pos.y})`); + }); + }); + } + + // 4. Validate positions (check for overlaps) + validateRoomPositions(positions, dimensions); + + console.log('\n=== Final Room Positions ==='); + Object.entries(positions).forEach(([roomId, pos]) => { + const dim = dimensions[roomId]; + console.log(`${roomId}: (${pos.x}, ${pos.y}) [${dim.widthPx}×${dim.heightPx}px]`); + }); + + return positions; +} + +// Helper function to route to correct positioning function +function positionSingleRoom(direction, currentRoom, connectedRoom, currentPos, dimensions) { + switch (direction) { + case 'north': return positionNorthSingle(currentRoom, connectedRoom, currentPos, dimensions); + case 'south': return positionSouthSingle(currentRoom, connectedRoom, currentPos, dimensions); + case 'east': return positionEastSingle(currentRoom, connectedRoom, currentPos, dimensions); + case 'west': return positionWestSingle(currentRoom, connectedRoom, currentPos, dimensions); + } +} + +function positionMultipleRooms(direction, currentRoom, connectedRooms, currentPos, dimensions) { + switch (direction) { + case 'north': return positionNorthMultiple(currentRoom, connectedRooms, currentPos, dimensions); + case 'south': return positionSouthMultiple(currentRoom, connectedRooms, currentPos, dimensions); + case 'east': return positionEastMultiple(currentRoom, connectedRooms, currentPos, dimensions); + case 'west': return positionWestMultiple(currentRoom, connectedRooms, currentPos, dimensions); + } +} +``` + +## Validation + +```javascript +function validateRoomPositions(positions, dimensions) { + console.log('\n=== Validating Room Positions ==='); + + const roomIds = Object.keys(positions); + let overlapCount = 0; + + // Check each pair of rooms + for (let i = 0; i < roomIds.length; i++) { + for (let j = i + 1; j < roomIds.length; j++) { + const room1Id = roomIds[i]; + const room2Id = roomIds[j]; + + const r1 = { + x: positions[room1Id].x, + y: positions[room1Id].y, + width: dimensions[room1Id].widthPx, + height: dimensions[room1Id].stackingHeightPx + }; + + const r2 = { + x: positions[room2Id].x, + y: positions[room2Id].y, + width: dimensions[room2Id].widthPx, + height: dimensions[room2Id].stackingHeightPx + }; + + // AABB overlap test + const overlaps = !( + r1.x + r1.width <= r2.x || + r2.x + r2.width <= r1.x || + r1.y + r1.height <= r2.y || + r2.y + r2.height <= r1.y + ); + + if (overlaps) { + console.error(`❌ OVERLAP DETECTED: ${room1Id} and ${room2Id}`); + console.error(` ${room1Id}: (${r1.x}, ${r1.y}) ${r1.width}×${r1.height}`); + console.error(` ${room2Id}: (${r2.x}, ${r2.y}) ${r2.width}×${r2.height}`); + overlapCount++; + } + } + } + + if (overlapCount === 0) { + console.log('✅ No overlaps detected'); + } else { + console.error(`❌ Found ${overlapCount} room overlaps`); + } + + return overlapCount === 0; +} +``` + +## Edge Cases + +### Narrow Rooms Connecting to Wide Rooms +``` + [Room1 - 1 grid unit wide] + [Room0 - 4 grid units wide] +``` +- Room1 is centered above Room0 +- Works automatically with centering logic + +### Multiple Small Rooms on Large Room +``` + [R1][R2][R3] + [---Room0---] +``` +- Total width of R1+R2+R3 may be < Room0 width +- Centering ensures balanced layout + +### Hallway Connectors +``` + [Room1][Room2] + [---Hallway--] + [---Room0---] +``` +- Hallway explicitly defined in scenario +- Treated as regular room in positioning +- No special logic needed diff --git a/planning_notes/new_room_layout/README.md b/planning_notes/new_room_layout/README.md new file mode 100644 index 0000000..76b69a5 --- /dev/null +++ b/planning_notes/new_room_layout/README.md @@ -0,0 +1,261 @@ +# Room Layout System Redesign - Implementation Plan + +## Quick Start + +**Status**: ✅ Planning Complete - Ready for Implementation + +This directory contains the complete implementation plan for redesigning the room layout system to support: +- Variable room sizes (in multiples of grid units) +- Four-direction connections (north, south, east, west) +- Intelligent door placement and alignment +- Comprehensive scenario validation + +## Document Organization + +### Core Concepts (Read First) +1. **[OVERVIEW.md](OVERVIEW.md)** - High-level goals and changes +2. **[TERMINOLOGY.md](TERMINOLOGY.md)** - Definitions of tiles, grid units, etc. +3. **[GRID_SYSTEM.md](GRID_SYSTEM.md)** - Grid unit system explained + +### Technical Specifications +4. **[POSITIONING_ALGORITHM.md](POSITIONING_ALGORITHM.md)** - Room positioning logic +5. **[DOOR_PLACEMENT.md](DOOR_PLACEMENT.md)** - Door placement rules +6. **[WALL_SYSTEM.md](WALL_SYSTEM.md)** - Wall collision updates +7. **[VALIDATION.md](VALIDATION.md)** - Scenario validation system + +### Implementation Guides +8. **[IMPLEMENTATION_STEPS.md](IMPLEMENTATION_STEPS.md)** - Step-by-step implementation +9. **[TODO_LIST.md](TODO_LIST.md)** - Detailed task checklist + +### Review & Recommendations +10. **[review1/CRITICAL_REVIEW.md](review1/CRITICAL_REVIEW.md)** - Critical issues identified +11. **[review1/RECOMMENDATIONS.md](review1/RECOMMENDATIONS.md)** - Solutions and fixes +12. **[UPDATED_FILES_SUMMARY.md](UPDATED_FILES_SUMMARY.md)** - Review feedback integration + +## Critical Information + +### Valid Room Sizes + +**Width**: Must be multiple of 5 tiles (5, 10, 15, 20, 25...) + +**Height**: Must follow formula: `2 + (N × 4)` where N ≥ 1 +- **Valid heights**: 6, 10, 14, 18, 22, 26... +- **Invalid heights**: 7, 8, 9, 11, 12, 13... + +**Examples**: +- Closet: 5×6 tiles ✅ +- Standard: 10×10 tiles ✅ +- Wide Hall: 20×6 tiles ✅ +- Invalid: 10×8 tiles ❌ (8 is not valid height) + +### Critical Fixes Required + +Based on code review, these issues MUST be addressed: + +1. **Door Alignment for Asymmetric Connections** ⚠️ CRITICAL + - When single-connection room links to multi-connection room + - Doors must align by checking connected room's connection array + - See [review1/RECOMMENDATIONS.md](review1/RECOMMENDATIONS.md) for complete solution + +2. **Shared Door Positioning Module** ⚠️ CRITICAL + - Create `js/systems/door-positioning.js` + - Single source of truth for door calculations + - Eliminates code duplication between doors.js and collision.js + +3. **Grid Height Clarification** ✅ RESOLVED + - Valid heights documented in GRID_SYSTEM.md + - Validation function specified + +4. **Connectivity Validation** ⚠️ REQUIRED + - Detect rooms with no path from starting room + - Log warnings for disconnected rooms + +## Implementation Strategy + +### Recommended Approach: Incremental with Feature Flag + +**Phase 0**: Add feature flag (`USE_NEW_ROOM_LAYOUT = true`) +- Allows easy rollback +- Enables gradual testing + +**Phase 1**: Foundation (2-3 hours) +- Add constants and helper functions +- Test grid conversions + +**Phase 2a**: North/South Positioning (3-4 hours) +- Implement N/S room positioning only +- Test with all existing scenarios +- Get early validation + +**Phase 2b**: East/West Support (2-3 hours) +- Add E/W positioning +- Test with new scenarios + +**Phase 3**: Door Placement (3-4 hours) +- Create shared door positioning module +- Update door sprite creation +- Update wall tile removal +- **Critical**: Implement asymmetric connection handling + +**Phase 4**: Validation (2-3 hours) +- Add all validation functions +- Create ValidationReport class +- Test with invalid scenarios + +**Phase 5**: Testing & Refinement (4-6 hours) +- Test all existing scenarios +- Create comprehensive test scenarios +- Fix bugs + +**Phase 6**: Documentation (2-3 hours) +- Add code comments +- Create debug tools +- Write migration guide + +**Total Estimated Time**: 18-26 hours + +### Avoid "Big Bang" Approach + +❌ Don't implement everything at once +✅ Do implement and test incrementally +✅ Do commit after each phase +✅ Do test existing scenarios early + +## Key Design Decisions + +### Grid Unit Size: 5×4 tiles + +**Why 5 tiles wide?** +- 1 tile each side for walls +- 3 tiles minimum interior space +- Allows single door placement (needs 1.5 tile inset) + +**Why 4 tiles tall (stacking)?** +- Plus 2 visual top tiles = 6 tiles minimum total height +- Creates consistent vertical rhythm +- Aligns with standard room proportions + +### Deterministic Door Placement + +For rooms with single connections, door position (left vs right) is determined by: +```javascript +const useRightSide = (gridX + gridY) % 2 === 1; +``` + +This creates visual variety while being deterministic. + +**EXCEPT**: When connecting to room with multiple connections, must align with that room's door array. + +### Breadth-First Positioning + +Rooms positioned outward from starting room ensures: +- Deterministic layout +- All rooms reachable +- No forward references needed + +## Testing Strategy + +### Test Scenarios Required + +1. **Different Sizes**: Closet, standard, wide hall combinations +2. **East/West**: Horizontal connections +3. **Multiple Connections**: 3+ rooms in same direction +4. **Complex Layout**: Multi-directional with hallways +5. **Invalid Scenarios**: Wrong sizes, missing connections, overlaps + +### Existing Scenarios + +All must continue to work: +- scenario1.json (biometric_breach) +- cybok_heist.json +- scenario2.json +- ceo_exfil.json + +## Common Pitfalls to Avoid + +1. **Floating Point Errors** + - Always use `Math.round()` for pixel positions + - Align to grid boundaries + +2. **Door Misalignment** + - Use shared door positioning function + - Handle asymmetric connections correctly + - Validate alignment after positioning + +3. **Stacking vs Total Height Confusion** + - Use stacking height for positioning + - Use total height for rendering + - Document which is used where + +4. **Forgetting Visual Overlap** + - Top 2 tiles overlap room to north (correct) + - Don't treat as collision overlap + - Use in rendering depth calculations + +## Debug Tools + +Add these to console for debugging: + +```javascript +window.showRoomLayout() // Visualize room bounds and grid +window.checkScenario() // Print validation results +window.listRooms() // List all room positions +window.showWallCollisions() // Visualize collision boxes +``` + +## Files to Modify + +### Core Changes +- `js/utils/constants.js` - Add grid constants +- `js/core/rooms.js` - New positioning algorithm +- `js/systems/doors.js` - New door placement +- `js/systems/collision.js` - Update wall tile removal +- `js/systems/door-positioning.js` - NEW: Shared door module + +### New Files +- `js/core/validation.js` - Scenario validation (optional, can be in rooms.js) +- Test scenarios in `scenarios/test_*.json` + +### Tiled Room Files +May need to update existing room JSON files to valid heights: +- Check all room_*.json files +- Ensure heights are 6, 10, 14, 18, 22, 26... +- Update if needed + +## Success Criteria + +- [ ] All existing scenarios load without errors +- [ ] All test scenarios work correctly +- [ ] No validation errors for valid scenarios +- [ ] Validation catches all invalid scenarios +- [ ] All doors align perfectly (verified) +- [ ] No room overlaps detected +- [ ] Navigation works in all 4 directions +- [ ] Performance acceptable (< 1s load time) +- [ ] Code well documented +- [ ] Debug tools functional + +## Questions? + +Refer to specific documents for details: +- How grid units work? → GRID_SYSTEM.md +- How to position rooms? → POSITIONING_ALGORITHM.md +- How to place doors? → DOOR_PLACEMENT.md +- How to validate? → VALIDATION.md +- What's the critical bug? → review1/CRITICAL_REVIEW.md +- How to fix it? → review1/RECOMMENDATIONS.md + +## Next Steps + +1. ✅ Read OVERVIEW.md and TERMINOLOGY.md (understand concepts) +2. ✅ Read review1/CRITICAL_REVIEW.md (understand issues) +3. ✅ Read review1/RECOMMENDATIONS.md (understand solutions) +4. ⏭️ Start implementation following IMPLEMENTATION_STEPS.md +5. ⏭️ Use TODO_LIST.md to track progress +6. ⏭️ Test continuously, commit frequently + +--- + +**Last Updated**: 2025-11-15 +**Status**: Planning complete, ready for implementation +**Confidence**: 9/10 (all critical issues identified and solutions specified) diff --git a/planning_notes/new_room_layout/TERMINOLOGY.md b/planning_notes/new_room_layout/TERMINOLOGY.md new file mode 100644 index 0000000..84d0960 --- /dev/null +++ b/planning_notes/new_room_layout/TERMINOLOGY.md @@ -0,0 +1,165 @@ +# Terminology Guide + +## Tile vs Grid Unit + +### Tile (32px) +- **Definition**: The smallest visual unit in Tiled +- **Size**: 32px × 32px +- **Usage**: Building block for all room graphics +- **Example**: A door is 1 tile wide × 2 tiles tall + +### Grid Unit (5×4 tiles = 160×128px) +- **Definition**: The minimum stackable room size unit +- **Size**: 5 tiles wide × 4 tiles tall (excluding top 2 visual rows) +- **Usage**: Room sizes are specified as multiples of grid units +- **Example**: A standard room is 2×2 grid units (10×8 tiles) + +**Why 5×4?** +- 5 tiles wide: 1 for each side wall + 3 floor tiles minimum +- 4 tiles tall: Excludes the 2 top visual wall tiles, counts stackable area only + +## Room Dimensions + +### Total Size +- **Definition**: Complete room including all walls and visual elements +- **Measurement**: Includes top 2 visual wall rows +- **Example**: Standard room is 10 tiles wide × 8 tiles tall total + +### Stacking Size +- **Definition**: The area that cannot overlap with other rooms +- **Measurement**: Excludes top 2 visual wall rows +- **Example**: Standard room has 10×6 tiles stacking size (but we think in grid units: 2×1.5) +- **Note**: For positioning, we use grid units (2×2) since we always align to grid boundaries + +### Floor Area +- **Definition**: Interior walkable space (excluding walls) +- **Measurement**: Total size minus all walls +- **Example**: Standard room (10×8 tiles) has ~6×4 floor area + +## Directions + +### Connection Directions +- **North**: Rooms above current room +- **South**: Rooms below current room +- **East**: Rooms to the right of current room +- **West**: Rooms to the left of current room + +### Visual Direction +- **Top of Screen**: North (furthest from player viewpoint) +- **Bottom of Screen**: South (closest to player viewpoint) + +## Positioning + +### World Coordinates +- **Definition**: Absolute pixel positions in the game world +- **Origin**: (0, 0) typically at the starting room's top-left +- **Usage**: Final position where room sprites are rendered + +### Grid Coordinates +- **Definition**: Position in grid units from origin +- **Origin**: Starting room at grid position (0, 0) +- **Usage**: Used for deterministic door placement calculations +- **Conversion**: `worldX = gridX × 160`, `worldY = gridY × 128` + +### Room Position +- **Definition**: The top-left corner of a room's stacking area in world coordinates +- **Usage**: Where Phaser renders the room tilemap +- **Note**: This is the position used in `window.roomPositions[roomId]` + +## Connections + +### Single Connection +```json +"connections": { + "north": "office2" +} +``` +- One room connected in the specified direction + +### Multiple Connections +```json +"connections": { + "north": ["office2", "office3"] +} +``` +- Array of rooms connected in the same direction +- Rooms are positioned left-to-right (west-to-east) + +### Bidirectional Connections +```json +// In room1: +"connections": { "north": "room2" } + +// In room2: +"connections": { "south": "room1" } +``` +- Connections must be reciprocal for doors to work correctly + +## Doors + +### Door Position +- **North/South Doors**: Placed in corners of the room +- **East/West Doors**: Placed at edges based on connection count +- **Alignment**: Doors from two connecting rooms must align perfectly + +### Door Sprite +- **North/South**: 1 tile wide × 2 tiles tall (`door_32.png`) +- **East/West**: 1 tile wide × 1 tile tall (`door_side_sheet_32.png`) +- **Layered**: Two door sprites (one for each room) stack at same position + +### Door State +- **Closed**: Collision enabled, blocks passage +- **Open**: Sprite destroyed, collision removed, passage clear +- **Locked**: Requires unlock minigame before opening + +## Walls + +### Visual Wall +- **Definition**: The top 2 rows of tiles showing wall from orthogonal view +- **Behavior**: Overlaps the room to the north visually +- **Collision**: No collision (purely visual) + +### Collision Wall +- **Definition**: Invisible collision boxes at room boundaries +- **Placement**: At the border between wall tiles and floor tiles +- **Exception**: No collision at door positions + +### Wall Tiles +- **Side Walls**: Leftmost and rightmost columns of tiles +- **Top Wall**: Top 2 rows of tiles +- **Bottom Wall**: Bottom row of tiles (treated specially) + +## Validation + +### Overlap Detection +- **Definition**: Checking if two rooms' stacking areas occupy the same grid units +- **Timing**: Performed during scenario load +- **Action**: Log clear error but attempt to continue + +### Grid Alignment +- **Definition**: Ensuring all rooms are positioned at grid unit boundaries +- **Requirement**: Room positions must be multiples of grid unit size (160×128px) +- **Purpose**: Ensures consistent layout and door alignment + +## Special Cases + +### Hallway +- **Definition**: A connector room explicitly defined in scenario +- **Typical Size**: 2×1 or 4×1 grid units (long and narrow) +- **Purpose**: Connects multiple rooms without gaps +- **Example**: +``` +[Room1][Room2] +[--Hallway--] +[--Room0---] +``` + +### Closet +- **Definition**: Smallest room size (1×1 grid unit) +- **Size**: 5 tiles wide × 4 tiles tall +- **Usage**: Small storage rooms, utility spaces + +### Corner Alignment +- **Definition**: When a room's corner touches another room's corner +- **Current Behavior**: Creates a gap (undesired) +- **New Behavior**: Rooms stack flush against each other diff --git a/planning_notes/new_room_layout/TODO_LIST.md b/planning_notes/new_room_layout/TODO_LIST.md new file mode 100644 index 0000000..991a3ff --- /dev/null +++ b/planning_notes/new_room_layout/TODO_LIST.md @@ -0,0 +1,661 @@ +# Room Layout System - Detailed TODO List + +## Phase 1: Constants and Helper Functions ✓ + +### Task 1.1: Add Grid Unit Constants +- [ ] Open `js/utils/constants.js` +- [ ] Add `GRID_UNIT_WIDTH_TILES = 5` +- [ ] Add `GRID_UNIT_HEIGHT_TILES = 4` +- [ ] Add `VISUAL_TOP_TILES = 2` +- [ ] Add `GRID_UNIT_WIDTH_PX = GRID_UNIT_WIDTH_TILES * TILE_SIZE` +- [ ] Add `GRID_UNIT_HEIGHT_PX = GRID_UNIT_HEIGHT_TILES * TILE_SIZE` +- [ ] Export all new constants +- [ ] Test: Import in rooms.js and log values + +### Task 1.2: Create Grid Conversion Functions +- [ ] Open `js/core/rooms.js` +- [ ] Add `tilesToGridUnits(widthTiles, heightTiles)` function + - [ ] Calculate `gridWidth = floor(widthTiles / 5)` + - [ ] Calculate `stackingHeight = heightTiles - 2` + - [ ] Calculate `gridHeight = floor(stackingHeight / 4)` + - [ ] Return object with gridWidth and gridHeight + - [ ] Add comprehensive JSDoc comment +- [ ] Add `gridToWorld(gridX, gridY)` function + - [ ] Calculate worldX = gridX * GRID_UNIT_WIDTH_PX + - [ ] Calculate worldY = gridY * GRID_UNIT_HEIGHT_PX + - [ ] Return object with x and y + - [ ] Add JSDoc comment +- [ ] Add `worldToGrid(worldX, worldY)` function + - [ ] Calculate gridX = floor(worldX / GRID_UNIT_WIDTH_PX) + - [ ] Calculate gridY = floor(worldY / GRID_UNIT_HEIGHT_PX) + - [ ] Return object with gridX and gridY + - [ ] Add JSDoc comment +- [ ] Add `alignToGrid(worldX, worldY)` function + - [ ] Round X to nearest grid boundary + - [ ] Round Y to nearest grid boundary + - [ ] Return aligned position + - [ ] Add JSDoc comment +- [ ] Test: Create console tests for each function + - [ ] Test tilesToGridUnits(5, 6) → {1, 1} + - [ ] Test tilesToGridUnits(10, 8) → {2, ~2} + - [ ] Test gridToWorld(0, 0) → {0, 0} + - [ ] Test gridToWorld(1, 1) → {160, 128} + - [ ] Test worldToGrid(160, 128) → {1, 1} + - [ ] Test alignToGrid(150, 120) → {160, 128} + +### Task 1.3: Create Room Dimension Extraction +- [ ] Add `getRoomDimensions(roomId, roomData, gameInstance)` function + - [ ] Get tilemap from cache + - [ ] Extract width/height from tilemap (try .json, .data, fallback) + - [ ] Call tilesToGridUnits() to get grid dimensions + - [ ] Calculate pixel dimensions + - [ ] Calculate stacking height + - [ ] Return comprehensive dimension object + - [ ] Add detailed JSDoc comment + - [ ] Add logging for dimensions +- [ ] Test with existing room types: + - [ ] Test with room_office + - [ ] Test with room_reception + - [ ] Test with room_closet + - [ ] Verify all dimension calculations + +--- + +## Phase 2: Room Positioning Algorithm + +### Task 2.1: Implement North Positioning +- [ ] Add `positionNorthSingle(currentRoom, connectedRoom, currentPos, dimensions)` + - [ ] Get connected room dimensions + - [ ] Calculate X (centered above current room) + - [ ] Calculate Y (using stacking height) + - [ ] Align to grid using alignToGrid() + - [ ] Return position object + - [ ] Add JSDoc and inline comments +- [ ] Add `positionNorthMultiple(currentRoom, connectedRooms, currentPos, dimensions)` + - [ ] Calculate total width of all connected rooms + - [ ] Calculate starting X (centered group) + - [ ] Loop through connected rooms + - [ ] Position each left-to-right + - [ ] Align each to grid + - [ ] Return positions map + - [ ] Add JSDoc and inline comments +- [ ] Test north positioning: + - [ ] Test single room north connection + - [ ] Test 2 rooms north connection + - [ ] Test 3 rooms north connection + - [ ] Verify centering logic + - [ ] Verify grid alignment + +### Task 2.2: Implement South Positioning +- [ ] Add `positionSouthSingle(currentRoom, connectedRoom, currentPos, dimensions)` + - [ ] Get connected room dimensions + - [ ] Calculate X (centered below current room) + - [ ] Calculate Y (current Y + current stacking height) + - [ ] Align to grid + - [ ] Return position + - [ ] Add JSDoc and comments +- [ ] Add `positionSouthMultiple(currentRoom, connectedRooms, currentPos, dimensions)` + - [ ] Calculate total width + - [ ] Calculate starting X (centered) + - [ ] Position each room left-to-right + - [ ] Align to grid + - [ ] Return positions map + - [ ] Add JSDoc and comments +- [ ] Test south positioning: + - [ ] Test single room + - [ ] Test multiple rooms + - [ ] Verify positioning relative to current room + +### Task 2.3: Implement East Positioning +- [ ] Add `positionEastSingle(currentRoom, connectedRoom, currentPos, dimensions)` + - [ ] Calculate X (current X + current width) + - [ ] Calculate Y (aligned at north edge) + - [ ] Align to grid + - [ ] Return position + - [ ] Add JSDoc and comments +- [ ] Add `positionEastMultiple(currentRoom, connectedRooms, currentPos, dimensions)` + - [ ] Calculate X (current X + current width) + - [ ] Stack vertically starting at current Y + - [ ] Loop through rooms, position each + - [ ] Align to grid + - [ ] Return positions map + - [ ] Add JSDoc and comments +- [ ] Test east positioning: + - [ ] Test single east connection + - [ ] Test multiple east connections + - [ ] Verify vertical stacking + +### Task 2.4: Implement West Positioning +- [ ] Add `positionWestSingle(currentRoom, connectedRoom, currentPos, dimensions)` + - [ ] Get connected room width + - [ ] Calculate X (current X - connected width) + - [ ] Calculate Y (aligned at north edge) + - [ ] Align to grid + - [ ] Return position + - [ ] Add JSDoc and comments +- [ ] Add `positionWestMultiple(currentRoom, connectedRooms, currentPos, dimensions)` + - [ ] Similar to east but subtract widths + - [ ] Stack vertically + - [ ] Align to grid + - [ ] Return positions map + - [ ] Add JSDoc and comments +- [ ] Test west positioning: + - [ ] Test single west connection + - [ ] Test multiple west connections + +### Task 2.5: Create Router Functions +- [ ] Add `positionSingleRoom(direction, currentRoom, connectedRoom, currentPos, dimensions)` + - [ ] Switch on direction + - [ ] Call appropriate positioning function + - [ ] Return position + - [ ] Add error handling for unknown direction + - [ ] Add JSDoc +- [ ] Add `positionMultipleRooms(direction, currentRoom, connectedRooms, currentPos, dimensions)` + - [ ] Switch on direction + - [ ] Call appropriate positioning function + - [ ] Return positions map + - [ ] Add error handling + - [ ] Add JSDoc +- [ ] Test router functions: + - [ ] Test each direction routing + - [ ] Test error handling + +### Task 2.6: Rewrite calculateRoomPositions +- [ ] Locate existing function (lines 644-786 in rooms.js) +- [ ] Create backup copy (commented out) +- [ ] Rewrite function: + - [ ] Create positions, dimensions, processed, queue variables + - [ ] Log algorithm start + - [ ] Phase 1: Extract all room dimensions + - [ ] Loop through all rooms + - [ ] Call getRoomDimensions() for each + - [ ] Store in dimensions object + - [ ] Log each room's dimensions + - [ ] Phase 2: Place starting room + - [ ] Get starting room from gameScenario + - [ ] Set position to (0, 0) + - [ ] Add to processed set + - [ ] Add to queue + - [ ] Log starting room placement + - [ ] Phase 3: Process rooms breadth-first + - [ ] While queue not empty: + - [ ] Dequeue current room + - [ ] Get current room data and position + - [ ] Log current room processing + - [ ] Loop through ['north', 'south', 'east', 'west']: + - [ ] Skip if no connection in direction + - [ ] Get connected rooms (array or single) + - [ ] Filter out processed rooms + - [ ] Skip if no unprocessed rooms + - [ ] Log connection direction and rooms + - [ ] If single room: call positionSingleRoom() + - [ ] If multiple rooms: call positionMultipleRooms() + - [ ] Apply positions to position map + - [ ] Add rooms to processed set + - [ ] Add rooms to queue + - [ ] Log each positioned room + - [ ] Phase 4: Log final positions + - [ ] Return positions object +- [ ] Test with scenario1.json: + - [ ] Verify starting room at (0, 0) + - [ ] Verify all rooms positioned + - [ ] Verify no rooms missing + - [ ] Verify grid alignment +- [ ] Test with cybok_heist.json +- [ ] Test with scenario2.json + +--- + +## Phase 3: Door Placement + +### Task 3.1: Implement North Door Placement +- [ ] Open `js/systems/doors.js` +- [ ] Add `placeNorthDoorSingle(roomId, roomPosition, roomDimensions, gridCoords)` + - [ ] Get room width + - [ ] Calculate deterministic left/right using (gridX + gridY) % 2 + - [ ] If right: doorX = roomX + width - 1.5 tiles + - [ ] If left: doorX = roomX + 1.5 tiles + - [ ] doorY = roomY + 1 tile + - [ ] Return {x, y} + - [ ] Add JSDoc and comments explaining deterministic placement +- [ ] Add `placeNorthDoorsMultiple(roomId, roomPosition, roomDimensions, connectedRooms)` + - [ ] Calculate edge inset (1.5 tiles) + - [ ] Calculate available width + - [ ] Calculate door spacing + - [ ] Loop through connected rooms + - [ ] Calculate X for each door + - [ ] doorY = roomY + 1 tile + - [ ] Return array of door positions with connectedRoom + - [ ] Add JSDoc +- [ ] Test north door placement: + - [ ] Test single door alternation + - [ ] Test multiple door spacing + - [ ] Verify corner positions + +### Task 3.2: Implement South Door Placement +- [ ] Add `placeSouthDoorSingle(roomId, roomPosition, roomDimensions, gridCoords)` + - [ ] Same logic as north but Y at bottom + - [ ] doorY = roomY + roomHeight - 1 tile + - [ ] Return {x, y} + - [ ] Add JSDoc +- [ ] Add `placeSouthDoorsMultiple(roomId, roomPosition, roomDimensions, connectedRooms)` + - [ ] Same logic as north but Y at bottom + - [ ] Return array of positions + - [ ] Add JSDoc +- [ ] Test south door placement + +### Task 3.3: Implement East Door Placement +- [ ] Add `placeEastDoorSingle(roomId, roomPosition, roomDimensions)` + - [ ] doorX = roomX + roomWidth - 1 tile + - [ ] doorY = roomY + 2 tiles (below visual wall) + - [ ] Return {x, y} + - [ ] Add JSDoc +- [ ] Add `placeEastDoorsMultiple(roomId, roomPosition, roomDimensions, connectedRooms)` + - [ ] doorX = roomX + roomWidth - 1 tile (same for all) + - [ ] If single: return single position + - [ ] Loop through connected rooms: + - [ ] If first (index 0): Y = roomY + 2 tiles + - [ ] If last: Y = roomY + roomHeight - 3 tiles + - [ ] If middle: evenly space between first and last + - [ ] Return array of positions + - [ ] Add JSDoc explaining spacing logic +- [ ] Test east door placement: + - [ ] Test single east door + - [ ] Test 2 east doors + - [ ] Test 3+ east doors + +### Task 3.4: Implement West Door Placement +- [ ] Add `placeWestDoorSingle(roomId, roomPosition, roomDimensions)` + - [ ] doorX = roomX + 1 tile + - [ ] doorY = roomY + 2 tiles + - [ ] Return {x, y} + - [ ] Add JSDoc +- [ ] Add `placeWestDoorsMultiple(roomId, roomPosition, roomDimensions, connectedRooms)` + - [ ] Same logic as east but X on west edge + - [ ] Return array of positions + - [ ] Add JSDoc +- [ ] Test west door placement + +### Task 3.5: Create Door Calculation Router +- [ ] Add `calculateDoorPositionsForRoom(roomId, roomPosition, roomDimensions, connections, allPositions, allDimensions)` + - [ ] Calculate grid coords from room position + - [ ] Create doors array + - [ ] Loop through ['north', 'south', 'east', 'west']: + - [ ] Skip if no connection + - [ ] Get connected rooms (array or single) + - [ ] If north: call appropriate north function + - [ ] If south: call appropriate south function + - [ ] If east: call appropriate east function + - [ ] If west: call appropriate west function + - [ ] Add returned positions to doors array with metadata + - [ ] Return doors array + - [ ] Add comprehensive JSDoc +- [ ] Test door calculation for each direction + +### Task 3.6: Update createDoorSpritesForRoom +- [ ] Locate function (lines 47-308 in doors.js) +- [ ] Backup existing implementation (comment out) +- [ ] Rewrite function: + - [ ] Get room dimensions using getRoomDimensions() + - [ ] Import/access getRoomDimensions from rooms.js + - [ ] Calculate door positions using calculateDoorPositionsForRoom() + - [ ] Loop through door positions: + - [ ] Create door sprite at position + - [ ] Set correct texture (door_32 for N/S, door_side_sheet_32 for E/W) + - [ ] Set origin, depth, visibility + - [ ] Set door properties + - [ ] Set up collision and interaction + - [ ] Add to doorSprites array + - [ ] Return doorSprites +- [ ] Remove old positioning logic (lines 86-187) +- [ ] Test door sprite creation: + - [ ] Verify doors created at correct positions + - [ ] Verify correct textures + - [ ] Verify door properties set correctly + +### Task 3.7: Update removeTilesUnderDoor +- [ ] Open `js/systems/collision.js` +- [ ] Locate removeTilesUnderDoor function (lines 154-335) +- [ ] Import door placement functions from doors.js OR +- [ ] Import calculateDoorPositionsForRoom +- [ ] Rewrite door position calculation section: + - [ ] Replace duplicate positioning logic (lines 197-283) + - [ ] Call calculateDoorPositionsForRoom() instead + - [ ] Use returned door positions +- [ ] Test tile removal: + - [ ] Verify tiles removed at correct positions + - [ ] Verify matches door sprite positions exactly + - [ ] No gaps or overlaps + +--- + +## Phase 4: Validation + +### Task 4.1: Create Validation Helper Functions +- [ ] Decide: Add to rooms.js OR create new validation.js +- [ ] If new file: Create `js/core/validation.js` +- [ ] Add `validateRoomSize(roomId, dimensions)` + - [ ] Check width multiple of 5 + - [ ] Check (height - 2) multiple of 4 + - [ ] Log errors if invalid + - [ ] Return boolean + - [ ] Add JSDoc +- [ ] Add `validateGridAlignment(roomId, position)` + - [ ] Check X divisible by GRID_UNIT_WIDTH_PX + - [ ] Check Y divisible by GRID_UNIT_HEIGHT_PX + - [ ] Log errors with nearest valid position + - [ ] Return boolean + - [ ] Add JSDoc +- [ ] Add `checkOverlap(pos1, dim1, pos2, dim2)` + - [ ] Calculate room bounds using stacking height + - [ ] Use AABB overlap test + - [ ] Return null if no overlap + - [ ] Return overlap area if overlapping + - [ ] Add JSDoc +- [ ] Test helper functions with known cases + +### Task 4.2: Create Overlap Detection +- [ ] Add `detectRoomOverlaps(positions, dimensions)` + - [ ] Get all room IDs + - [ ] Create overlaps array + - [ ] Nested loop through room pairs + - [ ] Call checkOverlap() for each pair + - [ ] If overlap, add to overlaps array with details + - [ ] Log all overlaps found + - [ ] Return overlaps array + - [ ] Add JSDoc +- [ ] Test with overlapping scenario +- [ ] Test with non-overlapping scenario + +### Task 4.3: Create Connection Validation +- [ ] Add `getOppositeDirection(direction)` helper + - [ ] Return opposite for north/south/east/west + - [ ] Add JSDoc +- [ ] Add `validateConnections(gameScenario)` + - [ ] Create errors array + - [ ] Loop through all rooms + - [ ] For each connection: + - [ ] Check connected room exists + - [ ] Get opposite direction + - [ ] Check reciprocal connection exists + - [ ] Check reciprocal points back to this room + - [ ] Add errors for any issues + - [ ] Log all errors + - [ ] Return errors array + - [ ] Add JSDoc +- [ ] Test with valid scenario +- [ ] Test with missing reciprocal +- [ ] Test with missing room + +### Task 4.4: Create Door Alignment Validation +- [ ] Add `validateDoorAlignment(allDoors)` + - [ ] Create door pairs map + - [ ] Group doors by connection (room1:room2) + - [ ] Create errors array + - [ ] For each pair: + - [ ] Check exactly 2 doors exist + - [ ] Calculate delta X and Y + - [ ] Check within tolerance (1px) + - [ ] Log errors if misaligned + - [ ] Add to errors array + - [ ] Log summary + - [ ] Return errors + - [ ] Add JSDoc +- [ ] Test with aligned doors +- [ ] Test with misaligned doors + +### Task 4.5: Create Starting Room Validation +- [ ] Add `validateStartingRoom(gameScenario)` + - [ ] Check startRoom defined + - [ ] Check startRoom exists in rooms + - [ ] Log errors + - [ ] Return boolean + - [ ] Add JSDoc +- [ ] Test with valid scenario +- [ ] Test with missing starting room + +### Task 4.6: Create Main Validation Function +- [ ] Add `validateScenario(gameScenario, positions, dimensions, allDoors)` + - [ ] Log validation header + - [ ] Create results object + - [ ] Call validateStartingRoom() + - [ ] Loop through rooms, call validateRoomSize() + - [ ] Loop through positions, call validateGridAlignment() + - [ ] Call detectRoomOverlaps() + - [ ] Call validateConnections() + - [ ] Call validateDoorAlignment() + - [ ] Aggregate results + - [ ] Log summary (errors and warnings) + - [ ] Return results + - [ ] Add comprehensive JSDoc +- [ ] Test with valid scenario +- [ ] Test with scenario containing errors + +### Task 4.7: Integrate Validation into initializeRooms +- [ ] Open `js/core/rooms.js` +- [ ] Locate initializeRooms function (lines 548-576) +- [ ] After calculateRoomPositions(): + - [ ] Extract all dimensions + - [ ] Calculate all door positions + - [ ] Call validateScenario() + - [ ] Store results in window.scenarioValidation + - [ ] Log results + - [ ] Continue initialization (don't fail) +- [ ] Test validation runs on scenario load +- [ ] Test validation results accessible via console + +--- + +## Phase 5: Testing and Refinement + +### Task 5.1: Test Existing Scenarios +- [ ] Test scenario1.json (biometric_breach): + - [ ] Load scenario + - [ ] Check no validation errors + - [ ] Navigate through all rooms + - [ ] Check all doors align + - [ ] Check no visual gaps + - [ ] Check player can navigate correctly +- [ ] Test cybok_heist.json: + - [ ] Same checks as above +- [ ] Test scenario2.json: + - [ ] Same checks as above +- [ ] Test ceo_exfil.json: + - [ ] Same checks as above +- [ ] Fix any issues found + +### Task 5.2: Create Test Scenario - Different Sizes +- [ ] Create `scenarios/test_different_sizes.json` +- [ ] Include closet (5×6) +- [ ] Include standard office (10×8) +- [ ] Include wide hall (20×6) if available +- [ ] Connect them vertically +- [ ] Load and test: + - [ ] Rooms positioned correctly + - [ ] Doors align + - [ ] No overlaps + - [ ] Navigation works + +### Task 5.3: Create Test Scenario - East/West +- [ ] Create `scenarios/test_east_west.json` +- [ ] Center room with east/west connections +- [ ] Load and test: + - [ ] Rooms positioned correctly + - [ ] Side doors created + - [ ] Side doors use correct sprite + - [ ] Doors align + - [ ] Navigation works + +### Task 5.4: Create Test Scenario - Multiple Connections +- [ ] Create `scenarios/test_multiple_connections.json` +- [ ] Base room with 3 north connections +- [ ] Load and test: + - [ ] All 3 rooms positioned correctly + - [ ] All doors align + - [ ] Doors evenly spaced + - [ ] Navigation works + +### Task 5.5: Create Test Scenario - Complex Layout +- [ ] Create `scenarios/test_complex.json` +- [ ] Multi-directional connections +- [ ] Include hallway connector +- [ ] Load and test: + - [ ] All rooms positioned + - [ ] No overlaps + - [ ] All doors align + - [ ] Navigation works + - [ ] Visual appearance correct + +### Task 5.6: Fix Common Issues +- [ ] Check for floating point errors + - [ ] Add Math.round() where needed +- [ ] Check for grid misalignment + - [ ] Ensure alignToGrid() called +- [ ] Check for door misalignment + - [ ] Verify calculation consistency +- [ ] Check for visual gaps + - [ ] Verify stacking height calculations +- [ ] Check for overlap false positives + - [ ] Verify overlap detection uses stacking height + +### Task 5.7: Performance Testing +- [ ] Load scenario with 10+ rooms +- [ ] Check load time +- [ ] Check frame rate during navigation +- [ ] Profile if needed +- [ ] Optimize if necessary + +--- + +## Phase 6: Documentation and Polish + +### Task 6.1: Add Code Comments +- [ ] Review all new functions +- [ ] Ensure each has comprehensive JSDoc +- [ ] Add inline comments for complex logic +- [ ] Explain grid unit conversions +- [ ] Explain deterministic door placement +- [ ] Explain edge cases + +### Task 6.2: Update Console Logging +- [ ] Review all console.log statements +- [ ] Ensure helpful debug output +- [ ] Add validation results logging +- [ ] Add room positioning logging +- [ ] Add door placement logging +- [ ] Use consistent formatting: + - [ ] ✅ for success + - [ ] ❌ for errors + - [ ] ⚠️ for warnings + - [ ] 🔧 for debug info + +### Task 6.3: Create Debug Tools +- [ ] Add window.showRoomBounds() + - [ ] Draw rectangles for each room's stacking area + - [ ] Use different colors for each room + - [ ] Add labels +- [ ] Add window.showGrid() + - [ ] Draw grid unit overlay + - [ ] Show grid coordinates +- [ ] Add window.checkScenario() + - [ ] Print validation results + - [ ] Print room positions + - [ ] Print door positions +- [ ] Add window.listRooms() + - [ ] Print all rooms with positions and sizes + - [ ] Print in table format +- [ ] Add window.testDoorAlignment() + - [ ] Highlight door positions + - [ ] Show alignment errors +- [ ] Document debug tools in console + +### Task 6.4: Clean Up Old Code +- [ ] Remove commented-out backup code +- [ ] Remove debug console.logs +- [ ] Remove unused functions +- [ ] Clean up imports + +### Task 6.5: Update User Documentation +- [ ] Update planning notes if needed +- [ ] Create migration guide for scenario authors +- [ ] Document grid unit system +- [ ] Document valid room sizes +- [ ] Document connection format + +--- + +## Final Checklist + +- [ ] All existing scenarios load correctly +- [ ] All test scenarios work correctly +- [ ] No validation errors for valid scenarios +- [ ] Validation catches invalid scenarios +- [ ] All doors align perfectly +- [ ] No room overlaps +- [ ] Navigation works in all directions +- [ ] Performance is acceptable +- [ ] Code is well documented +- [ ] Debug tools work +- [ ] No console errors +- [ ] Ready for commit + +--- + +## Commit Messages + +Use clear, descriptive commit messages: + +``` +feat: Add grid unit system constants and conversion functions + +- Add GRID_UNIT_WIDTH_TILES, GRID_UNIT_HEIGHT_TILES constants +- Add tilesToGridUnits(), gridToWorld(), worldToGrid() helpers +- Add alignToGrid() function for position alignment +- Add comprehensive tests for all conversion functions +``` + +``` +feat: Implement new room positioning algorithm + +- Support all 4 directions (north, south, east, west) +- Position rooms in multiples of grid units +- Center rooms when connecting to larger/smaller rooms +- Use breadth-first processing for deterministic layout +``` + +``` +feat: Update door placement for variable room sizes + +- Support north/south doors with deterministic left/right placement +- Support east/west doors with proper spacing +- Align all doors to connecting room doors +- Use grid coordinates for deterministic placement +``` + +``` +feat: Add scenario validation system + +- Validate room sizes are multiples of grid units +- Detect room overlaps +- Verify connection reciprocity +- Verify door alignment +- Log clear errors and warnings +``` + +``` +test: Add test scenarios for new room layout system + +- Test different room sizes +- Test east/west connections +- Test multiple connections +- Test complex layouts +``` + +``` +docs: Add comprehensive code documentation + +- Add JSDoc to all new functions +- Add inline comments for complex logic +- Document grid unit system +- Add debug tools +``` diff --git a/planning_notes/new_room_layout/UPDATED_FILES_SUMMARY.md b/planning_notes/new_room_layout/UPDATED_FILES_SUMMARY.md new file mode 100644 index 0000000..c28496d --- /dev/null +++ b/planning_notes/new_room_layout/UPDATED_FILES_SUMMARY.md @@ -0,0 +1,166 @@ +# Summary of Review Feedback Integration + +## Critical Updates Made + +### 1. Grid System Clarification (GRID_SYSTEM.md) +- ✅ Clarified that valid room heights are: **6, 10, 14, 18, 22, 26...** (formula: 2 + 4N) +- ✅ Updated room size table with formula verification +- ✅ Added invalid room size examples +- ✅ Specified minimum height for multiple E/W doors (8 tiles) + +### 2. Door Alignment for Asymmetric Connections +**CRITICAL FIX** documented in DOOR_PLACEMENT.md: + +When a room with a single connection links to a room with multiple connections in the opposite direction, the door position must be calculated to align with the correct door in the multi-door room. + +**Example**: +``` + [R2][R3] <- R1 has 2 south connections + [--R1--] + [--R0--] <- R0 has 1 north connection to R1 +``` + +R0's north door must align with whichever of R1's south doors connects to R0. + +**Solution**: Check if connected room has multiple connections in opposite direction, find this room's index in that array, calculate door position to match. + +This fix must be applied to all single door placement functions: +- `placeNorthDoorSingle()` +- `placeSouthDoorSingle()` +- `placeEastDoorSingle()` +- `placeWestDoorSingle()` + +### 3. Shared Door Positioning Module +**NEW FILE REQUIRED**: `js/systems/door-positioning.js` + +This module will be the single source of truth for door position calculations, used by: +- `createDoorSpritesForRoom()` in doors.js +- `removeTilesUnderDoor()` in collision.js +- Validation in validation.js + +Benefits: +- Eliminates code duplication +- Guarantees door alignment +- Easier to maintain and test + +### 4. Validation Enhancements +Additional validation checks needed: +- ✅ Connectivity validation (detect disconnected rooms) +- ✅ E/W door space validation (minimum height check) +- ✅ Structured validation report (ValidationReport class) + +### 5. Implementation Strategy +**NEW APPROACH**: Incremental implementation with feature flag + +Instead of "big bang" implementation, use phased approach: + +**Phase 0**: Add feature flag (`USE_NEW_ROOM_LAYOUT`) +- Allows easy rollback if critical bug found +- Enables A/B testing +- Supports gradual migration + +**Phases 1-2**: Implement north/south support only first +- Test with all existing scenarios (which only use N/S) +- Get early feedback +- Easier debugging + +**Phases 3-4**: Add east/west support +- Test with new scenarios +- Build on stable N/S foundation + +**Phases 5-6**: Validation and polish +- Add comprehensive validation +- Create debug tools +- Update documentation + +## Documents That Need Updates + +### High Priority (Before Implementation) + +1. **DOOR_PLACEMENT.md** ⚠️ CRITICAL + - Add asymmetric connection handling to ALL single door functions + - Add examples showing the problem and solution + - Update function signatures to include `gameScenario` parameter + +2. **IMPLEMENTATION_STEPS.md** ⚠️ CRITICAL + - Add Phase 0 for feature flag + - Split Phase 2 into 2a (N/S) and 2b (E/W) + - Add Phase 2.5 for shared door positioning module + - Update task sequence + +3. **TODO_LIST.md** ⚠️ CRITICAL + - Add feature flag tasks + - Add shared door positioning module tasks + - Update door placement tasks with asymmetric handling + - Reorganize for incremental implementation + +4. **VALIDATION.md** + - Add connectivity validation function + - Add E/W door space validation function + - Replace simple error logging with ValidationReport class + - Add examples of structured error reporting + +### Medium Priority (Can Update During Implementation) + +5. **POSITIONING_ALGORITHM.md** + - No critical changes needed + - Works correctly with integer grid units + - May add performance optimization notes + +6. **WALL_SYSTEM.md** + - No critical changes needed + - Current implementation compatible + +### New Documents to Create + +7. **MIGRATION_GUIDE.md** (NEW) + - How to update room JSON files to valid heights + - How to test updated scenarios + - Common migration issues and fixes + - Checklist for scenario authors + +8. **TROUBLESHOOTING.md** (NEW) + - Common validation errors and how to fix + - How to use debug tools + - Door misalignment troubleshooting + - Overlap detection help + +## Review Findings Summary + +### Critical Issues Found +1. ❌ Grid height calculation created fractional grid units +2. ❌ Door alignment broken for asymmetric connections +3. ❌ Code duplication in door positioning +4. ❌ Disconnected rooms not validated + +### All Issues Addressed +1. ✅ Grid heights clarified (6, 10, 14, 18...) +2. ✅ Door alignment solution documented +3. ✅ Shared module approach specified +4. ✅ Connectivity validation added +5. ✅ Feature flag strategy added +6. ✅ Incremental implementation planned + +## Next Steps + +1. **Update remaining documents** with review feedback: + - DOOR_PLACEMENT.md (add asymmetric handling code) + - IMPLEMENTATION_STEPS.md (add feature flag and incremental approach) + - TODO_LIST.md (reorganize tasks) + - VALIDATION.md (add new validation functions) + +2. **Create new documents**: + - MIGRATION_GUIDE.md + - TROUBLESHOOTING.md + - door-positioning.js module specification + +3. **Final review** of updated plan + +4. **Begin implementation** following updated plan + +## Confidence Level + +**Before Review**: 7/10 (significant edge cases not addressed) +**After Review**: 9/10 (critical issues identified and solutions specified) + +The plan is now solid and ready for implementation with significantly reduced risk. diff --git a/planning_notes/new_room_layout/VALIDATION.md b/planning_notes/new_room_layout/VALIDATION.md new file mode 100644 index 0000000..e1171cf --- /dev/null +++ b/planning_notes/new_room_layout/VALIDATION.md @@ -0,0 +1,503 @@ +# Scenario Validation System + +## Overview + +Validation ensures scenarios are correctly configured before the game starts. This catches authoring errors early and provides clear feedback. + +## Validation Checks + +### 1. Room Size Validation + +**Check**: All rooms are sized in multiples of grid units + +```javascript +function validateRoomSize(roomId, dimensions) { + const { widthTiles, heightTiles } = dimensions; + + // Check width is multiple of 5 + const validWidth = (widthTiles % GRID_UNIT_WIDTH_TILES) === 0; + + // Check height: total height should be (gridUnits × 4) + 2 visual tiles + const stackingHeight = heightTiles - VISUAL_TOP_TILES; + const validHeight = (stackingHeight % GRID_UNIT_HEIGHT_TILES) === 0; + + if (!validWidth || !validHeight) { + console.error(`❌ Invalid room size: ${roomId}`); + console.error(` Size: ${widthTiles}×${heightTiles} tiles`); + console.error(` Width must be multiple of ${GRID_UNIT_WIDTH_TILES} tiles`); + console.error(` Height must be (N×${GRID_UNIT_HEIGHT_TILES})+${VISUAL_TOP_TILES} tiles`); + return false; + } + + console.log(`✅ Room size valid: ${roomId} (${widthTiles}×${heightTiles} tiles)`); + return true; +} +``` + +### 2. Grid Alignment Validation + +**Check**: All room positions align to grid boundaries + +```javascript +function validateGridAlignment(roomId, position) { + const { x, y } = position; + + const alignedX = (x % GRID_UNIT_WIDTH_PX) === 0; + const alignedY = (y % GRID_UNIT_HEIGHT_PX) === 0; + + if (!alignedX || !alignedY) { + console.error(`❌ Room not grid-aligned: ${roomId}`); + console.error(` Position: (${x}, ${y})`); + console.error(` Expected multiples of (${GRID_UNIT_WIDTH_PX}, ${GRID_UNIT_HEIGHT_PX})`); + + // Calculate nearest aligned position + const nearestX = Math.round(x / GRID_UNIT_WIDTH_PX) * GRID_UNIT_WIDTH_PX; + const nearestY = Math.round(y / GRID_UNIT_HEIGHT_PX) * GRID_UNIT_HEIGHT_PX; + console.error(` Nearest valid: (${nearestX}, ${nearestY})`); + + return false; + } + + return true; +} +``` + +### 3. Room Overlap Detection + +**Check**: No two rooms occupy the same grid space + +```javascript +function detectRoomOverlaps(positions, dimensions) { + console.log('\n=== Room Overlap Detection ==='); + + const roomIds = Object.keys(positions); + const overlaps = []; + + for (let i = 0; i < roomIds.length; i++) { + for (let j = i + 1; j < roomIds.length; j++) { + const room1Id = roomIds[i]; + const room2Id = roomIds[j]; + + const overlap = checkOverlap( + positions[room1Id], + dimensions[room1Id], + positions[room2Id], + dimensions[room2Id] + ); + + if (overlap) { + overlaps.push({ room1: room1Id, room2: room2Id, ...overlap }); + } + } + } + + if (overlaps.length > 0) { + console.error(`❌ Found ${overlaps.length} room overlaps:`); + overlaps.forEach(overlap => { + console.error(` ${overlap.room1} ↔ ${overlap.room2}`); + console.error(` Overlap area: ${overlap.width}×${overlap.height} pixels`); + console.error(` Overlap position: (${overlap.x}, ${overlap.y})`); + }); + } else { + console.log('✅ No room overlaps detected'); + } + + return overlaps; +} + +function checkOverlap(pos1, dim1, pos2, dim2) { + // Use stacking height for overlap calculation + const r1 = { + left: pos1.x, + right: pos1.x + dim1.widthPx, + top: pos1.y, + bottom: pos1.y + dim1.stackingHeightPx + }; + + const r2 = { + left: pos2.x, + right: pos2.x + dim2.widthPx, + top: pos2.y, + bottom: pos2.y + dim2.stackingHeightPx + }; + + // Check for overlap + const noOverlap = ( + r1.right <= r2.left || + r2.right <= r1.left || + r1.bottom <= r2.top || + r2.bottom <= r1.top + ); + + if (noOverlap) { + return null; // No overlap + } + + // Calculate overlap area + const overlapX = Math.max(r1.left, r2.left); + const overlapY = Math.max(r1.top, r2.top); + const overlapWidth = Math.min(r1.right, r2.right) - overlapX; + const overlapHeight = Math.min(r1.bottom, r2.bottom) - overlapY; + + return { + x: overlapX, + y: overlapY, + width: overlapWidth, + height: overlapHeight + }; +} +``` + +### 4. Connection Reciprocity Validation + +**Check**: All connections are bidirectional + +```javascript +function validateConnections(gameScenario) { + console.log('\n=== Connection Validation ==='); + + const errors = []; + + Object.entries(gameScenario.rooms).forEach(([roomId, roomData]) => { + if (!roomData.connections) return; + + Object.entries(roomData.connections).forEach(([direction, connected]) => { + const connectedRooms = Array.isArray(connected) ? connected : [connected]; + + connectedRooms.forEach(connectedRoomId => { + // Check if connected room exists + if (!gameScenario.rooms[connectedRoomId]) { + errors.push({ + type: 'missing_room', + from: roomId, + to: connectedRoomId, + direction + }); + return; + } + + // Check if connection is reciprocal + const oppositeDir = getOppositeDirection(direction); + const connectedRoomData = gameScenario.rooms[connectedRoomId]; + + if (!connectedRoomData.connections) { + errors.push({ + type: 'missing_reciprocal', + from: roomId, + to: connectedRoomId, + direction, + expected: oppositeDir + }); + return; + } + + const reciprocalConnection = connectedRoomData.connections[oppositeDir]; + if (!reciprocalConnection) { + errors.push({ + type: 'missing_reciprocal', + from: roomId, + to: connectedRoomId, + direction, + expected: oppositeDir + }); + return; + } + + // Check if this room is in the reciprocal connection + const reciprocalRooms = Array.isArray(reciprocalConnection) + ? reciprocalConnection + : [reciprocalConnection]; + + if (!reciprocalRooms.includes(roomId)) { + errors.push({ + type: 'mismatched_reciprocal', + from: roomId, + to: connectedRoomId, + direction, + expected: oppositeDir, + actual: reciprocalRooms + }); + } + }); + }); + }); + + if (errors.length > 0) { + console.error(`❌ Found ${errors.length} connection errors:`); + errors.forEach(error => { + if (error.type === 'missing_room') { + console.error(` ${error.from} → ${error.to}: Room does not exist`); + } else if (error.type === 'missing_reciprocal') { + console.error(` ${error.from} → ${error.to} (${error.direction}): Missing reciprocal connection (${error.expected})`); + } else if (error.type === 'mismatched_reciprocal') { + console.error(` ${error.from} → ${error.to} (${error.direction}): Reciprocal doesn't point back (expected ${error.from}, found ${error.actual})`); + } + }); + } else { + console.log('✅ All connections are valid and reciprocal'); + } + + return errors; +} + +function getOppositeDirection(direction) { + const opposites = { + 'north': 'south', + 'south': 'north', + 'east': 'west', + 'west': 'east' + }; + return opposites[direction]; +} +``` + +### 5. Door Alignment Validation + +**Check**: Doors between connected rooms align perfectly + +```javascript +function validateDoorAlignment(allDoors) { + console.log('\n=== Door Alignment Validation ==='); + + const errors = []; + const tolerance = 1; // 1px tolerance for floating point + + // Build a map of door pairs (connections) + const doorPairs = new Map(); + + allDoors.forEach(door => { + const key = door.roomId < door.connectedRoom + ? `${door.roomId}:${door.connectedRoom}` + : `${door.connectedRoom}:${door.roomId}`; + + if (!doorPairs.has(key)) { + doorPairs.set(key, []); + } + doorPairs.get(key).push(door); + }); + + // Check each pair + doorPairs.forEach((doors, pairKey) => { + if (doors.length !== 2) { + console.error(`❌ Connection ${pairKey} has ${doors.length} doors (expected 2)`); + errors.push({ + type: 'door_count_mismatch', + connection: pairKey, + count: doors.length + }); + return; + } + + const [door1, door2] = doors; + + const deltaX = Math.abs(door1.x - door2.x); + const deltaY = Math.abs(door1.y - door2.y); + + if (deltaX > tolerance || deltaY > tolerance) { + console.error(`❌ Door misalignment: ${pairKey}`); + console.error(` Door 1: (${door1.x}, ${door1.y}) in ${door1.roomId}`); + console.error(` Door 2: (${door2.x}, ${door2.y}) in ${door2.roomId}`); + console.error(` Delta: (${deltaX}, ${deltaY})`); + + errors.push({ + type: 'door_misalignment', + connection: pairKey, + door1: { roomId: door1.roomId, x: door1.x, y: door1.y }, + door2: { roomId: door2.roomId, x: door2.x, y: door2.y }, + delta: { x: deltaX, y: deltaY } + }); + } + }); + + if (errors.length === 0) { + console.log(`✅ All ${doorPairs.size} door connections are properly aligned`); + } + + return errors; +} +``` + +### 6. Starting Room Validation + +**Check**: Starting room exists and is valid + +```javascript +function validateStartingRoom(gameScenario) { + console.log('\n=== Starting Room Validation ==='); + + const startRoom = gameScenario.startRoom; + + if (!startRoom) { + console.error('❌ No starting room defined in scenario'); + return false; + } + + if (!gameScenario.rooms[startRoom]) { + console.error(`❌ Starting room "${startRoom}" does not exist`); + return false; + } + + console.log(`✅ Starting room "${startRoom}" is valid`); + return true; +} +``` + +## Complete Validation Pipeline + +```javascript +function validateScenario(gameScenario, positions, dimensions, allDoors) { + console.log('\n╔════════════════════════════════════════╗'); + console.log('║ SCENARIO VALIDATION ║'); + console.log('╚════════════════════════════════════════╝\n'); + + const results = { + valid: true, + errors: [], + warnings: [] + }; + + // 1. Validate starting room + if (!validateStartingRoom(gameScenario)) { + results.valid = false; + results.errors.push('Invalid starting room'); + } + + // 2. Validate room sizes + Object.entries(dimensions).forEach(([roomId, dim]) => { + if (!validateRoomSize(roomId, dim)) { + results.valid = false; + results.errors.push(`Invalid room size: ${roomId}`); + } + }); + + // 3. Validate grid alignment + Object.entries(positions).forEach(([roomId, pos]) => { + if (!validateGridAlignment(roomId, pos)) { + results.warnings.push(`Room not grid-aligned: ${roomId}`); + // Not a fatal error, but should be fixed + } + }); + + // 4. Detect room overlaps + const overlaps = detectRoomOverlaps(positions, dimensions); + if (overlaps.length > 0) { + results.errors.push(`${overlaps.length} room overlaps detected`); + // Continue despite overlaps (per requirements) + } + + // 5. Validate connections + const connectionErrors = validateConnections(gameScenario); + if (connectionErrors.length > 0) { + results.valid = false; + results.errors.push(`${connectionErrors.length} connection errors`); + } + + // 6. Validate door alignment + const doorErrors = validateDoorAlignment(allDoors); + if (doorErrors.length > 0) { + results.valid = false; + results.errors.push(`${doorErrors.length} door alignment errors`); + } + + // Print summary + console.log('\n╔════════════════════════════════════════╗'); + console.log('║ VALIDATION SUMMARY ║'); + console.log('╚════════════════════════════════════════╝\n'); + + if (results.errors.length === 0 && results.warnings.length === 0) { + console.log('✅ All validation checks passed!'); + } else { + if (results.errors.length > 0) { + console.error(`❌ ${results.errors.length} errors found:`); + results.errors.forEach(err => console.error(` - ${err}`)); + } + + if (results.warnings.length > 0) { + console.warn(`⚠️ ${results.warnings.length} warnings:`); + results.warnings.forEach(warn => console.warn(` - ${warn}`)); + } + } + + console.log(''); + + return results; +} +``` + +## When to Run Validation + +```javascript +// In initializeRooms() function in js/core/rooms.js + +export function initializeRooms(gameInstance) { + gameRef = gameInstance; + console.log('Initializing rooms'); + + // ... existing setup code ... + + // Calculate room positions + window.roomPositions = calculateRoomPositions(gameInstance); + + // Extract dimensions for validation + const dimensions = extractAllRoomDimensions(gameInstance); + + // Calculate all door positions (before creating sprites) + const allDoors = calculateAllDoorPositions( + window.gameScenario, + window.roomPositions, + dimensions + ); + + // VALIDATE SCENARIO + const validationResults = validateScenario( + window.gameScenario, + window.roomPositions, + dimensions, + allDoors + ); + + // Store validation results for debugging + window.scenarioValidation = validationResults; + + // Continue initialization even if validation fails + // (per requirements: log error but attempt to continue) + + // ... rest of initialization ... +} +``` + +## Error Reporting + +Validation errors should be clear and actionable: + +``` +❌ OVERLAP DETECTED: room1 and room2 + room1: (0, 0) 320×192px + room2: (160, 0) 320×192px + Overlap area: 160×192 pixels at (160, 0) + + SUGGESTED FIX: + - Move room2 to (320, 0) to eliminate overlap + - Or reduce room sizes to fit +``` + +## Development Tools + +Add console commands for testing: + +```javascript +// Check scenario validation results +window.checkScenario = function() { + if (window.scenarioValidation) { + console.log(window.scenarioValidation); + } else { + console.log('No validation results available'); + } +}; + +// Visualize room bounds +window.showRoomBounds = function() { + // Draw debug rectangles around each room's stacking area + // Useful for identifying overlaps visually +}; +``` diff --git a/planning_notes/new_room_layout/WALL_SYSTEM.md b/planning_notes/new_room_layout/WALL_SYSTEM.md new file mode 100644 index 0000000..a80309e --- /dev/null +++ b/planning_notes/new_room_layout/WALL_SYSTEM.md @@ -0,0 +1,304 @@ +# Wall and Collision System + +## Overview + +The wall system creates invisible collision boxes at room boundaries to prevent the player from walking through walls. Doors remove these collision boxes to create passages between rooms. + +## Current Implementation + +Located in `js/systems/collision.js`: +- `createWallCollisionBoxes()` - Creates collision rectangles for wall tiles +- `removeTilesUnderDoor()` - Removes wall tiles where doors are placed + +## Wall Placement + +### Wall Edges + +Rooms have walls on all four sides: + +``` +WWWWWWWWWW <- North wall (top 2 rows, visual only) +WWWWWWWWWW +WFFFFFFFFW <- West/East walls (1 tile each side) +WFFFFFFFFW North wall collision starts here +WFFFFFFFFW +WFFFFFFFFW +WFFFFFFFFW +WFFFFFFFFW <- South wall (bottom row) +``` + +### Collision Box Placement + +Collision boxes are thin rectangles placed at the boundary between wall and floor: + +```javascript +// North wall: Top 2 rows (visual wall) +// Collision box at bottom edge of row 2 +if (tileY < 2) { + createCollisionBox( + worldX + TILE_SIZE / 2, // Center of tile + worldY + TILE_SIZE - 4, // 4px from bottom + TILE_SIZE, // Full tile width + 8 // 8px thick + ); +} + +// South wall: Bottom row +// Collision box at bottom edge +if (tileY === mapHeight - 1) { + createCollisionBox( + worldX + TILE_SIZE / 2, + worldY + TILE_SIZE - 4, + TILE_SIZE, + 8 + ); +} + +// West wall: Left column +// Collision box at right edge +if (tileX === 0) { + createCollisionBox( + worldX + TILE_SIZE - 4, // 4px from right edge + worldY + TILE_SIZE / 2, // Center of tile + 8, // 8px thick + TILE_SIZE // Full tile height + ); +} + +// East wall: Right column +// Collision box at left edge +if (tileX === mapWidth - 1) { + createCollisionBox( + worldX + 4, // 4px from left edge + worldY + TILE_SIZE / 2, + 8, + TILE_SIZE + ); +} +``` + +## Changes Needed for Variable Room Sizes + +### Current Issue + +The current implementation assumes all rooms are 10×10 tiles. Wall detection uses hardcoded checks: + +```javascript +// Current code +if (tileY < 2) { /* north wall */ } +if (tileY === map.height - 1) { /* south wall */ } +if (tileX === 0) { /* west wall */ } +if (tileX === map.width - 1) { /* east wall */ } +``` + +This works correctly and should continue to work for variable room sizes! + +### No Changes Needed + +The wall collision system is **already compatible** with variable room sizes: + +- Uses `map.width` and `map.height` from Tiled JSON +- Dynamically detects edges based on actual room dimensions +- Creates collision boxes for each wall tile + +The current implementation in `js/systems/collision.js` lines 22-151 should work without modification. + +## Door Integration + +### Removing Wall Tiles + +When doors are created, wall tiles must be removed: + +```javascript +function removeTilesUnderDoor(wallLayer, doorX, doorY, doorWidth, doorHeight) { + // Convert world coordinates to layer tile coordinates + const layerTileX = Math.floor((doorX - wallLayer.x) / TILE_SIZE); + const layerTileY = Math.floor((doorY - wallLayer.y) / TILE_SIZE); + + // Calculate how many tiles the door spans + const tilesWide = Math.ceil(doorWidth / TILE_SIZE); + const tilesTall = Math.ceil(doorHeight / TILE_SIZE); + + // Remove tiles in door area + for (let x = 0; x < tilesWide; x++) { + for (let y = 0; y < tilesTall; y++) { + const tileX = layerTileX + x; + const tileY = layerTileY + y; + + const tile = wallLayer.getTileAt(tileX, tileY); + if (tile) { + wallLayer.removeTileAt(tileX, tileY); + console.log(`Removed wall tile at (${tileX}, ${tileY}) for door`); + } + } + } +} +``` + +### Current Implementation + +The current `removeTilesUnderDoor()` function in `js/systems/collision.js` (lines 154-335): +- Calculates door positions using same logic as door sprites +- Converts door world coordinates to tile coordinates +- Removes tiles in door area + +**This should continue to work** with the new positioning system, as long as door positions are calculated correctly. + +### Updates Needed + +The door positioning logic in `removeTilesUnderDoor()` must match the new door placement algorithm: + +1. **Update door position calculation** to use new algorithm (from DOOR_PLACEMENT.md) +2. **Remove hardcoded positioning logic** (lines 195-283 currently duplicate door placement) +3. **Use shared door calculation** function instead + +## Collision Box Management + +### Creation During Room Load + +```javascript +export function createRoom(roomId, roomData, position) { + // ... create room layers ... + + // Create wall collision boxes + wallsLayers.forEach(wallLayer => { + createWallCollisionBoxes(wallLayer, roomId, position); + }); + + // Create door sprites (which remove wall tiles/collisions) + const doorSprites = createDoorSpritesForRoom(roomId, position); + + // ... rest of room creation ... +} +``` + +### Door-Specific Removal + +When a door is opened, wall collisions in that area are already removed (tiles removed). When door sprite has collision physics, closing the door is done by the door sprite's collision box. + +### Room-Specific Collision + +Each room maintains its own collision boxes: + +```javascript +rooms[roomId] = { + map, + layers, + wallsLayers, + wallCollisionBoxes: [], // All collision boxes for this room + doorSprites: [], + objects: {}, + position +}; +``` + +## Testing Wall Collisions + +### Visual Debug Mode + +Add ability to visualize collision boxes: + +```javascript +window.showWallCollisions = function() { + Object.values(rooms).forEach(room => { + if (room.wallCollisionBoxes) { + room.wallCollisionBoxes.forEach(box => { + box.setVisible(true); + box.setAlpha(0.3); + box.setFillStyle(0xff0000); // Red + }); + } + }); +}; + +window.hideWallCollisions = function() { + Object.values(rooms).forEach(room => { + if (room.wallCollisionBoxes) { + room.wallCollisionBoxes.forEach(box => { + box.setVisible(false); + }); + } + }); +}; +``` + +### Test Cases + +1. **Player vs Wall**: Walk into walls, should not pass through +2. **Player vs Door**: Walk through open door, should pass +3. **Player vs Closed Door**: Walk into closed door, should not pass +4. **Different Room Sizes**: Test walls work for small, standard, and large rooms +5. **Room Boundaries**: Test at edges where rooms connect + +## Implementation Notes + +### Order of Operations + +Critical: Wall collision boxes must be created **before** door tiles are removed: + +```javascript +// Correct order: +1. Create room layers +2. Create wall collision boxes (for all wall tiles) +3. Create door sprites +4. Remove tiles under doors (removes some collision boxes) + +// If done wrong: +1. Create room layers +2. Create door sprites +3. Remove tiles under doors +4. Create wall collision boxes <- Would create boxes where doors are! +``` + +The current implementation does this correctly. + +### Edge Cases + +#### Rooms with Shared Walls + +When two rooms are adjacent (east-west connection): + +``` +[Room1][Room2] + ^^ + Shared edge +``` + +- Room1 has east wall collision +- Room2 has west wall collision +- These are at the same location +- Both are removed when door is created +- Works correctly (two collision boxes at same spot is fine) + +#### Overlapping Visual Walls + +When rooms stack north-south: + +``` +[Room2] <- Bottom 2 rows visible +[Room1] <- Top 2 rows visible +``` + +- Room1's north wall (visual) overlaps Room2's south area +- Collision is only on Room1's floor edge (row 2 bottom) +- Room2's south wall collision is at its bottom edge +- No conflict, works correctly + +## Summary + +### What Works Already +- Dynamic wall detection based on room size +- Collision box creation at wall edges +- Player collision with walls +- Door removal of wall tiles + +### What Needs Updates +- Door position calculation in `removeTilesUnderDoor()` must use new algorithm +- Remove duplicate door positioning logic +- Ensure door positions match between `createDoorSpritesForRoom()` and `removeTilesUnderDoor()` + +### What Stays the Same +- Collision box placement logic +- Wall edge detection +- Collision thickness (8px) +- Order of operations diff --git a/planning_notes/new_room_layout/review1/CRITICAL_REVIEW.md b/planning_notes/new_room_layout/review1/CRITICAL_REVIEW.md new file mode 100644 index 0000000..dad4679 --- /dev/null +++ b/planning_notes/new_room_layout/review1/CRITICAL_REVIEW.md @@ -0,0 +1,511 @@ +# Critical Review of Room Layout Implementation Plan + +## Executive Summary + +The implementation plan is comprehensive and well-structured. However, there are several critical issues and edge cases that need to be addressed to ensure successful implementation. + +**Overall Assessment**: 7/10 +- Strong: Clear documentation, systematic approach, good testing strategy +- Weak: Some edge cases not fully addressed, potential performance issues, migration complexity + +--- + +## Critical Issues + +### Issue 1: Grid Height Calculation Ambiguity + +**Problem**: The grid height calculation has an ambiguity. + +Current spec states: +- Standard room: 10×8 tiles +- Grid height = (8 - 2) / 4 = 1.5 grid units + +This creates fractional grid units, but the plan states rooms must be in **whole** multiples of grid units. + +**Impact**: HIGH +- Affects room size validation +- Affects positioning calculations +- Could cause rounding errors + +**Recommendation**: +- Clarify: Should standard rooms be 10×10 tiles total (8 stackable + 2 visual)? +- Or: Should standard rooms be 10×8 tiles total (6 stackable + 2 visual)? +- **Proposed Solution**: Standard rooms should be 10×8 tiles where: + - Visual top: 2 tiles + - Stackable area: 6 tiles (not 8!) + - This gives gridHeight = 6/4 = 1.5... still fractional + +**Alternative Approach**: +- Redefine grid unit height as 3 tiles (not 4) +- Standard room: 10×8 = 2×2 grid units (visual: 2, stackable: 6 = 2×3) +- Closet: 5×5 = 1×1 grid units (visual: 2, stackable: 3 = 1×3) +- This eliminates fractions entirely + +OR + +- Keep grid as 5×4 but measure total height including visual: + - Closet: 5×6 tiles = 1×1.5 grid units (still fractional) + - Standard: 10×10 tiles = 2×2.5 grid units (still fractional) + +**NEEDS CLARIFICATION FROM USER** + +--- + +### Issue 2: Door Overlap at Room Connections + +**Problem**: When two rooms connect, both create door sprites at the same position. The plan mentions this creates "layered doors" which is intentional, but doesn't fully explain the interaction model. + +**Current Behavior**: +- Room A has door to Room B (locked with Room B's lock) +- Room B has door to Room A (locked with Room A's lock? or always open?) + +**Questions**: +1. Do both doors have the same lock properties? +2. Which door does the player interact with? +3. What happens when one door is open but the other is closed? + +**Recommendation**: +- Document the door interaction model clearly +- Consider: Only create door sprite from the "locked" side +- Or: Create single shared door sprite +- Add validation to ensure door lock consistency + +--- + +### Issue 3: Stacking Height in Overlap Detection + +**Problem**: The plan uses "stacking height" for overlap detection, but this might not be correct for visual accuracy. + +When Room A is north of Room B: +``` +[Room A] <- bottom tiles visible +[Room B] <- top wall tiles visible +``` + +Room B's visual top wall (2 tiles) overlaps Room A's visual floor. This is **intentional and correct** for rendering, but the overlap detection treats it as non-overlapping. + +**Impact**: MEDIUM +- Visual overlap is desired for rendering +- But positioning overlap is not desired +- Need to ensure both are handled correctly + +**Recommendation**: +- Clarify that overlap detection should use stacking height (excluding visual top) +- Add visual overlap diagram to documentation +- Ensure rendering depth handles overlapping visual elements + +--- + +### Issue 4: East/West Door Placement for Different Heights + +**Problem**: The plan specifies door placement for east/west connections with different heights, but doesn't fully address all cases. + +When a tall room (16 tiles high) connects east to a short room (8 tiles high): +``` +[Tall ] +[Room ][Short] +[ ] +``` + +The plan states: +- "First door: north corner (2 tiles from top)" +- "Second door: 3 tiles up from south" + +But what if the short room is only 8 tiles tall? +- North corner: Y = roomY + 64px +- South position: Y = roomY + heightPx - 96px + - For 8 tile room: Y = roomY + 256 - 96 = roomY + 160 + - Door spacing: 160 - 64 = 96px (3 tiles) - OK! + +- But what if room is 6 tiles tall (closet height)? + - South position: Y = roomY + 192 - 96 = roomY + 96 + - Door spacing: 96 - 64 = 32px (1 tile) - Too close! + +**Recommendation**: +- Add minimum height requirement for E/W connections (8 tiles) +- Or: Add validation to ensure sufficient height for door spacing +- Or: Adjust formula for small rooms + +--- + +### Issue 5: Grid Alignment Validation Timing + +**Problem**: The plan validates grid alignment **after** positioning, but alignment should be **guaranteed** by the positioning algorithm. + +If validation finds misalignment, it's too late - rooms are already positioned incorrectly. + +**Recommendation**: +- Remove grid alignment validation (should never fail if positioning is correct) +- Or: Use alignment validation as assertion/sanity check +- Add unit tests to ensure positioning functions always return grid-aligned positions + +--- + +### Issue 6: Performance with Large Scenarios + +**Problem**: The breadth-first room positioning processes all rooms sequentially. For scenarios with 50+ rooms, this could be slow. + +Also, validation does O(n²) overlap checks (all room pairs). + +**Impact**: LOW-MEDIUM +- Most scenarios have < 20 rooms (fast) +- But custom scenarios could have many rooms + +**Recommendation**: +- Profile with large scenario (50+ rooms) +- If slow: Consider spatial hashing for overlap detection +- If very slow: Consider caching dimension calculations + +--- + +## Edge Cases Not Addressed + +### Edge Case 1: Disconnected Rooms + +**Scenario**: Room exists in scenario but has no connections and is not the starting room. + +**Current Behavior**: Room won't be positioned (never added to queue) + +**Recommendation**: +- Add validation to detect disconnected rooms +- Either: Warn and skip them +- Or: Position them at a default location + +--- + +### Edge Case 2: Circular References with East/West + +**Scenario**: +```json +{ + "room1": { "connections": { "east": "room2" } }, + "room2": { "connections": { "west": "room1", "east": "room3" } }, + "room3": { "connections": { "west": "room2" } } +} +``` + +With breadth-first processing, this should work correctly. But what if room3 also connects back to room1? + +```json +{ + "room3": { "connections": { "west": "room2", "south": "room1" } } +} +``` + +This creates a loop. Room1 is processed first (starting room), positions room2 east. Room2 positions room3 east. Room3 tries to position room1 south, but room1 is already positioned (in processed set). + +**Current Behavior**: Should work correctly (room1 already processed, skipped) + +**Recommendation**: +- Add test case for circular connections +- Verify processed set prevents re-positioning + +--- + +### Edge Case 3: Asymmetric Connection Counts + +**Scenario**: Small room connects to large room with multiple children + +``` + [R2][R3][R4] + [--R1------] + [---R0----] +``` + +R1 connects to 3 rooms north (R2, R3, R4). +R0 connects to 1 room north (R1). + +When calculating R1's south door: +- R1 has 1 south connection (R0) +- Door placed deterministically (left or right) + +When calculating R0's north door: +- R0 has 1 north connection (R1) +- Door placed deterministically +- **BUT**: Does it align with R1's south door? + +**Problem**: If R0 chooses left and R1 chooses right, doors won't align! + +**Root Cause**: Deterministic placement uses grid coordinates, but R1 might be at different grid position than R0 expects. + +**Recommendation**: +- When placing door to connected room, check if connected room has **multiple connections in opposite direction** +- If so, calculate which index this room is in that array +- Use that index to determine door position (not grid coordinates) +- This ensures alignment + +**This is a CRITICAL issue that needs to be addressed!** + +--- + +### Edge Case 4: Very Small Rooms + +**Scenario**: Room is exactly 5×6 tiles (minimum size) + +Floor area: 3 tiles wide × 2 tiles tall (after removing walls) + +Can this fit: +- Player sprite? +- Objects? +- NPCs? + +**Recommendation**: +- Test with minimum size room +- Ensure collision boxes don't completely block room +- Consider documenting minimum recommended size vs minimum technical size + +--- + +## Design Improvements + +### Improvement 1: Shared Door Positioning Function + +**Current Plan**: Door positioning calculated in two places: +1. `createDoorSpritesForRoom()` in doors.js +2. `removeTilesUnderDoor()` in collision.js + +**Problem**: Code duplication, potential for divergence + +**Recommendation**: Create single source of truth +```javascript +// In doors.js +export function calculateDoorPositions(roomId, position, dimensions, connections) { + // Returns array of door positions for a room +} + +// Used by both: +createDoorSpritesForRoom() // for creating sprites +removeTilesUnderDoor() // for removing tiles +``` + +--- + +### Improvement 2: Room Dimension Caching + +**Current Plan**: `getRoomDimensions()` called multiple times for same room + +**Recommendation**: Cache dimensions in first pass +```javascript +const dimensionsCache = new Map(); + +function getRoomDimensions(roomId, roomData, gameInstance) { + if (dimensionsCache.has(roomId)) { + return dimensionsCache.get(roomId); + } + + const dimensions = /* calculate */; + dimensionsCache.set(roomId, dimensions); + return dimensions; +} +``` + +--- + +### Improvement 3: Validation Summary Report + +**Current Plan**: Validation logs errors to console + +**Recommendation**: Generate structured validation report +```javascript +window.scenarioValidation = { + valid: true, + errors: [ + { type: 'overlap', room1: 'office1', room2: 'office2', details: {...} } + ], + warnings: [ + { type: 'alignment', room: 'closet', details: {...} } + ], + summary: "3 errors, 1 warning", + timestamp: Date.now() +}; +``` + +This allows: +- Programmatic error checking +- Better debugging +- Potential UI for scenario authors + +--- + +### Improvement 4: Debug Visualization + +**Current Plan**: Debug tools added in Phase 6 + +**Recommendation**: Add debug visualization for positioning algorithm +- Show rooms being positioned in real-time +- Highlight current room, connected rooms +- Show grid overlay +- Animate positioning process + +This would help: +- Understanding the algorithm +- Debugging positioning issues +- Teaching scenario authors + +--- + +## Testing Gaps + +### Gap 1: Stress Testing + +**Missing**: Test with extreme scenarios +- 100+ rooms +- Very deep hierarchy (10+ levels) +- Very wide branching (10+ children) + +**Recommendation**: Create stress test scenarios + +--- + +### Gap 2: Invalid Scenario Testing + +**Missing**: Test with intentionally broken scenarios +- Invalid room sizes +- Missing reciprocal connections +- Circular connections +- Non-existent room references + +**Recommendation**: Create test suite for invalid scenarios + +--- + +### Gap 3: Migration Testing + +**Missing**: Test existing scenarios work with new system + +**Recommendation**: +- Run ALL existing scenarios +- Create regression test suite +- Document any breaking changes + +--- + +## Implementation Order Concerns + +### Concern 1: Big Bang Approach + +**Current Plan**: Implement all changes, then test + +**Risk**: If something breaks, hard to isolate + +**Recommendation**: More incremental approach +1. Phase 1: Add constants and helpers (test immediately) +2. Phase 2: Implement positioning for **north/south only** (test with existing scenarios) +3. Phase 3: Add east/west support (test with new scenarios) +4. Phase 4: Add door placement (test) +5. Phase 5: Add validation (test) + +This allows: +- Earlier testing +- Easier debugging +- Incremental commits + +--- + +### Concern 2: No Rollback Plan + +**Current Plan**: Comment out old code + +**Risk**: If new system has critical bug, hard to rollback + +**Recommendation**: +- Create feature flag +```javascript +const USE_NEW_POSITIONING = true; // Set to false to use old system + +function calculateRoomPositions(...) { + if (USE_NEW_POSITIONING) { + return newCalculateRoomPositions(...); + } else { + return oldCalculateRoomPositions(...); + } +} +``` + +This allows: +- Easy A/B testing +- Quick rollback if issues +- Gradual migration + +--- + +## Documentation Gaps + +### Gap 1: Migration Guide + +**Missing**: Guide for scenario authors + +**Needs**: +- How to update scenarios for new system +- How to use new room sizes +- How to test scenarios +- Common issues and solutions + +--- + +### Gap 2: Algorithm Visualization + +**Missing**: Visual diagrams of algorithm + +**Needs**: +- Flowchart of positioning algorithm +- Diagrams showing door placement rules +- Illustrations of grid system + +--- + +### Gap 3: Troubleshooting Guide + +**Missing**: Guide for debugging issues + +**Needs**: +- Common error messages and fixes +- How to use debug tools +- How to validate scenarios + +--- + +## Summary of Recommendations + +### Critical (Must Fix) +1. ✅ Resolve grid height calculation ambiguity +2. ✅ Fix door alignment for asymmetric connections +3. ✅ Create shared door positioning function +4. ✅ Add validation for disconnected rooms + +### High Priority (Should Fix) +5. Document door interaction model for layered doors +6. Add minimum height validation for E/W connections +7. Add test cases for edge cases +8. Implement feature flag for gradual migration + +### Medium Priority (Nice to Have) +9. Cache room dimensions for performance +10. Create structured validation report +11. Add debug visualization +12. Create migration guide + +### Low Priority (Future Enhancements) +13. Stress test with large scenarios +14. Create algorithm visualizations +15. Add troubleshooting guide + +--- + +## Overall Assessment + +The implementation plan is solid and well-thought-out. The main issues are: + +**Strengths**: +- Comprehensive documentation +- Systematic approach +- Good testing strategy +- Clear phases + +**Weaknesses**: +- Grid calculation needs clarification +- Some edge cases not fully addressed +- Big bang implementation approach +- Missing migration strategy + +**Verdict**: Plan is viable with the recommended fixes. Address critical issues before implementation begins. diff --git a/planning_notes/new_room_layout/review1/RECOMMENDATIONS.md b/planning_notes/new_room_layout/review1/RECOMMENDATIONS.md new file mode 100644 index 0000000..91c6037 --- /dev/null +++ b/planning_notes/new_room_layout/review1/RECOMMENDATIONS.md @@ -0,0 +1,653 @@ +# Implementation Recommendations + +## Priority 1: Critical Fixes (Must Implement Before Starting) + +### Fix 1: Resolve Grid Height Ambiguity + +**Problem**: Current spec creates fractional grid units (1.5, 2.5) + +**Solution**: Redefine grid unit to match actual room tile counts + +**Recommended Approach**: + +Keep the grid unit as **5 tiles wide × 4 tiles tall** for the **stacking area**, but clarify the total room heights: + +| Room Type | Total Tiles (W×H) | Grid Units | Calculation | +|-----------|-------------------|------------|-------------| +| Closet | 5×6 | 1×1 | Visual(2) + Stackable(4×1) = 6 | +| Standard | 10×10 | 2×2 | Visual(2) + Stackable(4×2) = 10 | +| Wide Hall | 20×6 | 4×1 | Visual(2) + Stackable(4×1) = 6 | +| Tall Room | 10×14 | 2×3 | Visual(2) + Stackable(4×3) = 14 | + +**Formula**: +```javascript +totalHeight = VISUAL_TOP_TILES + (gridHeight × GRID_UNIT_HEIGHT_TILES) +totalHeight = 2 + (gridHeight × 4) + +// Examples: +// 1×1 grid: 2 + (1 × 4) = 6 tiles +// 2×2 grid: 2 + (2 × 4) = 10 tiles +// 2×3 grid: 2 + (3 × 4) = 14 tiles +``` + +**Validation Function**: +```javascript +function validateRoomSize(roomId, dimensions) { + const { widthTiles, heightTiles } = dimensions; + + // Width must be multiple of 5 + const validWidth = (widthTiles % GRID_UNIT_WIDTH_TILES) === 0; + + // Height must be 2 + (N × 4) where N is whole number + const stackingHeight = heightTiles - VISUAL_TOP_TILES; + const validHeight = (stackingHeight % GRID_UNIT_HEIGHT_TILES) === 0 && + stackingHeight > 0; + + if (!validWidth) { + console.error(`❌ Invalid width: ${roomId} is ${widthTiles} tiles (must be multiple of 5)`); + } + + if (!validHeight) { + console.error(`❌ Invalid height: ${roomId} is ${heightTiles} tiles`); + console.error(` Must be 2 + (N × 4) where N >= 1`); + console.error(` Valid heights: 6, 10, 14, 18, 22, ...`); + } + + return validWidth && validHeight; +} +``` + +--- + +### Fix 2: Door Alignment for Asymmetric Connections + +**Problem**: When small room connects to large room with multiple children, deterministic placement may cause misalignment. + +**Example**: +``` + [R2][R3] <- R1 has 2 north connections + [--R1--] <- R1 is at grid (0, -2) + [--R0--] <- R0 is at grid (0, 0) +``` + +R1's south door uses `(0 + -2) % 2 = 0` → **left side** +R0's north door uses `(0 + 0) % 2 = 0` → **left side** +✅ They align! + +**But consider**: +``` + [R2][R3] <- R1 has 2 north connections + [-R1--] <- R1 is at grid (-1, -2) (offset to align with children) + [--R0--] <- R0 is at grid (0, 0) +``` + +R1's south door uses `(-1 + -2) % 2 = 1` → **right side** +R0's north door uses `(0 + 0) % 2 = 0` → **left side** +❌ They DON'T align! + +**Solution**: When positioning single door, check if connected room has multiple connections in opposite direction + +```javascript +function placeNorthDoorSingle(roomId, roomPosition, roomDimensions, gridCoords, connectedRoom, gameScenario) { + const roomWidthPx = roomDimensions.widthPx; + + // Check if the connected room has multiple south connections + const connectedRoomData = gameScenario.rooms[connectedRoom]; + const connectedRoomSouthConnections = connectedRoomData?.connections?.south; + + let useRightSide; + + if (Array.isArray(connectedRoomSouthConnections) && connectedRoomSouthConnections.length > 1) { + // Connected room has multiple south doors + // Find which index this room is in that array + const indexInArray = connectedRoomSouthConnections.indexOf(roomId); + + if (indexInArray >= 0) { + // Calculate door position to match the connected room's door layout + // This room's door must align with one of the connected room's doors + + // Instead of using grid coords, calculate which side based on index + // For multiple doors in connected room, they're spaced evenly + // We need to match the corresponding door position + + // Get connected room dimensions and position + const connectedPos = window.roomPositions[connectedRoom]; + const connectedDim = getRoomDimensions(connectedRoom, connectedRoomData, gameRef); + + // Calculate where the connected room's door is + // Using the same logic as placeSouthDoorsMultiple + const edgeInset = TILE_SIZE * 1.5; + const availableWidth = connectedDim.widthPx - (edgeInset * 2); + const doorCount = connectedRoomSouthConnections.length; + const doorSpacing = availableWidth / (doorCount - 1); + + const connectedDoorX = connectedPos.x + edgeInset + (doorSpacing * indexInArray); + + // This room's door X must match + const doorX = connectedDoorX; + const doorY = roomPosition.y + TILE_SIZE; + + return { x: doorX, y: doorY }; + } + } + + // Default: Use deterministic placement based on grid coordinates + useRightSide = (gridCoords.x + gridCoords.y) % 2 === 1; + + let doorX; + if (useRightSide) { + doorX = roomPosition.x + roomWidthPx - (TILE_SIZE * 1.5); + } else { + doorX = roomPosition.x + (TILE_SIZE * 1.5); + } + + const doorY = roomPosition.y + TILE_SIZE; + return { x: doorX, y: doorY }; +} +``` + +**Apply same logic to**: +- placeSouthDoorSingle +- placeEastDoorSingle +- placeWestDoorSingle + +This ensures doors ALWAYS align when connecting to room with multiple doors. + +--- + +### Fix 3: Create Shared Door Positioning Module + +**Problem**: Door positions calculated in multiple places + +**Solution**: Create single source of truth + +**File**: `js/systems/door-positioning.js` (new file) + +```javascript +/** + * DOOR POSITIONING SYSTEM + * ======================== + * + * Centralized door position calculations used by: + * - Door sprite creation (doors.js) + * - Wall tile removal (collision.js) + * - Validation (validation.js) + * + * Ensures consistency across all systems. + */ + +import { TILE_SIZE, GRID_UNIT_WIDTH_PX, GRID_UNIT_HEIGHT_PX } from '../utils/constants.js'; + +/** + * Calculate all door positions for a room + * + * Returns array of door objects with: + * - roomId: Source room + * - connectedRoom: Destination room + * - direction: north/south/east/west + * - x, y: World position + * + * @param {string} roomId + * @param {Object} roomPosition - {x, y} + * @param {Object} roomDimensions + * @param {Object} connections - Room connections from scenario + * @param {Object} allPositions - All room positions + * @param {Object} allDimensions - All room dimensions + * @param {Object} gameScenario - Full scenario for cross-referencing + * @returns {Array} Array of door position objects + */ +export function calculateDoorPositions(roomId, roomPosition, roomDimensions, + connections, allPositions, allDimensions, + gameScenario) { + const doors = []; + const gridCoords = worldToGrid(roomPosition.x, roomPosition.y); + + // Process each direction + ['north', 'south', 'east', 'west'].forEach(direction => { + if (!connections[direction]) return; + + const connected = connections[direction]; + const connectedRooms = Array.isArray(connected) ? connected : [connected]; + + let doorPositions; + + // Calculate door positions based on direction and count + if (direction === 'north') { + doorPositions = connectedRooms.length === 1 + ? [placeNorthDoorSingle(roomId, roomPosition, roomDimensions, gridCoords, + connectedRooms[0], gameScenario)] + : placeNorthDoorsMultiple(roomId, roomPosition, roomDimensions, connectedRooms); + } + // ... similar for other directions + + // Add to doors array with metadata + doorPositions.forEach((doorPos, index) => { + doors.push({ + roomId, + connectedRoom: connectedRooms[index] || doorPos.connectedRoom, + direction, + x: doorPos.x, + y: doorPos.y + }); + }); + }); + + return doors; +} + +// Export individual placement functions for testing +export { + placeNorthDoorSingle, + placeNorthDoorsMultiple, + placeSouthDoorSingle, + placeSouthDoorsMultiple, + placeEastDoorSingle, + placeEastDoorsMultiple, + placeWestDoorSingle, + placeWestDoorsMultiple +}; +``` + +**Update dependent files**: +- `doors.js`: Import and use calculateDoorPositions +- `collision.js`: Import and use calculateDoorPositions +- Both files use same function → guaranteed alignment + +--- + +### Fix 4: Validate Disconnected Rooms + +**Problem**: Rooms with no connections won't be positioned + +**Solution**: Add validation check + +```javascript +function validateConnectivity(gameScenario, positions) { + console.log('\n=== Connectivity Validation ==='); + + const allRoomIds = Object.keys(gameScenario.rooms); + const positionedRoomIds = Object.keys(positions); + + const disconnectedRooms = allRoomIds.filter(id => !positionedRoomIds.includes(id)); + + if (disconnectedRooms.length > 0) { + console.warn(`⚠️ Found ${disconnectedRooms.length} disconnected rooms:`); + disconnectedRooms.forEach(roomId => { + console.warn(` - ${roomId} (has no path from starting room)`); + }); + + return false; + } + + console.log(`✅ All ${allRoomIds.length} rooms are connected`); + return true; +} +``` + +**Add to validation pipeline** in `validateScenario()` + +--- + +## Priority 2: High Priority Improvements + +### Improvement 1: Feature Flag for Gradual Migration + +**Implementation**: + +```javascript +// In constants.js +export const USE_NEW_ROOM_LAYOUT = true; // Feature flag + +// In rooms.js +export function calculateRoomPositions(gameInstance) { + if (USE_NEW_ROOM_LAYOUT) { + return calculateRoomPositionsV2(gameInstance); + } else { + return calculateRoomPositionsV1(gameInstance); + } +} + +function calculateRoomPositionsV2(gameInstance) { + // New implementation +} + +function calculateRoomPositionsV1(gameInstance) { + // Old implementation (keep for safety) +} +``` + +**Benefits**: +- Easy rollback if critical bug found +- A/B testing +- Gradual migration per scenario + +--- + +### Improvement 2: Add Minimum Height Validation for E/W Doors + +**Problem**: Small rooms may not have space for two E/W doors + +**Solution**: + +```javascript +function validateEastWestDoorSpace(roomId, dimensions, connections) { + const minHeightForMultipleDoors = 8; // tiles + + ['east', 'west'].forEach(direction => { + if (!connections[direction]) return; + + const connected = connections[direction]; + const connectedRooms = Array.isArray(connected) ? connected : [connected]; + + if (connectedRooms.length > 1 && dimensions.heightTiles < minHeightForMultipleDoors) { + console.error(`❌ Room ${roomId} is too short for multiple ${direction} doors`); + console.error(` Height: ${dimensions.heightTiles} tiles`); + console.error(` Minimum: ${minHeightForMultipleDoors} tiles for ${connectedRooms.length} doors`); + return false; + } + }); + + return true; +} +``` + +**Add to validation pipeline** + +--- + +### Improvement 3: Structured Validation Report + +**Implementation**: + +```javascript +class ValidationReport { + constructor() { + this.errors = []; + this.warnings = []; + this.info = []; + this.timestamp = Date.now(); + } + + addError(type, message, details = {}) { + this.errors.push({ type, message, details }); + } + + addWarning(type, message, details = {}) { + this.warnings.push({ type, message, details }); + } + + addInfo(type, message, details = {}) { + this.info.push({ type, message, details }); + } + + isValid() { + return this.errors.length === 0; + } + + getSummary() { + return { + valid: this.isValid(), + errorCount: this.errors.length, + warningCount: this.warnings.length, + summary: `${this.errors.length} errors, ${this.warnings.length} warnings` + }; + } + + print() { + console.log('\n╔════════════════════════════════════════╗'); + console.log('║ SCENARIO VALIDATION REPORT ║'); + console.log('╚════════════════════════════════════════╝\n'); + + if (this.errors.length > 0) { + console.error(`❌ ERRORS (${this.errors.length}):`); + this.errors.forEach(err => { + console.error(` ${err.type}: ${err.message}`); + if (Object.keys(err.details).length > 0) { + console.error(` Details:`, err.details); + } + }); + console.log(''); + } + + if (this.warnings.length > 0) { + console.warn(`⚠️ WARNINGS (${this.warnings.length}):`); + this.warnings.forEach(warn => { + console.warn(` ${warn.type}: ${warn.message}`); + }); + console.log(''); + } + + if (this.errors.length === 0 && this.warnings.length === 0) { + console.log('✅ All validation checks passed!\n'); + } + + console.log(this.getSummary().summary); + } +} + +// Usage in validateScenario: +function validateScenario(gameScenario, positions, dimensions, allDoors) { + const report = new ValidationReport(); + + // Validate starting room + if (!validateStartingRoom(gameScenario)) { + report.addError('missing_start_room', 'No valid starting room defined'); + } + + // ... other validations ... + + // Store and print + report.print(); + return report; +} +``` + +--- + +### Improvement 4: Incremental Implementation Strategy + +**Recommended Order**: + +1. **Week 1: Foundation** + - Add constants + - Add helper functions + - Add tests for helpers + - **Commit**: "feat: Add grid unit system foundation" + +2. **Week 2: North/South Positioning** + - Implement north/south positioning only + - Keep east/west as TODO + - Test with existing scenarios (all use north/south) + - **Commit**: "feat: Implement north/south room positioning" + +3. **Week 3: East/West Support** + - Add east/west positioning + - Test with new scenarios + - **Commit**: "feat: Add east/west room connections" + +4. **Week 4: Door Placement** + - Create door positioning module + - Update door sprite creation + - Update wall tile removal + - **Commit**: "feat: Update door placement for variable sizes" + +5. **Week 5: Validation** + - Add validation system + - Test with invalid scenarios + - **Commit**: "feat: Add scenario validation system" + +6. **Week 6: Testing & Polish** + - Create test scenarios + - Fix bugs + - Add debug tools + - Update documentation + - **Commit**: "test: Add comprehensive test scenarios" + - **Commit**: "docs: Update room layout documentation" + +This allows: +- Early feedback +- Incremental testing +- Easier debugging +- Regular commits + +--- + +## Priority 3: Medium Priority Enhancements + +### Enhancement 1: Room Dimension Caching + +```javascript +const dimensionCache = new Map(); + +function getRoomDimensions(roomId, roomData, gameInstance) { + const cacheKey = `${roomId}_${roomData.type}`; + + if (dimensionCache.has(cacheKey)) { + return dimensionCache.get(cacheKey); + } + + // Calculate dimensions... + const dimensions = { /* ... */ }; + + dimensionCache.set(cacheKey, dimensions); + return dimensions; +} + +// Clear cache on scenario load +export function clearDimensionCache() { + dimensionCache.clear(); +} +``` + +--- + +### Enhancement 2: Debug Visualization + +```javascript +window.showRoomLayout = function(showGrid = true, showLabels = true, showDoors = true) { + const graphics = gameRef.add.graphics(); + graphics.setDepth(10000); // On top of everything + + Object.entries(window.roomPositions).forEach(([roomId, pos]) => { + const dim = getRoomDimensions(roomId, gameScenario.rooms[roomId], gameRef); + + // Draw stacking area + graphics.lineStyle(2, 0x00ff00, 1); + graphics.strokeRect(pos.x, pos.y, dim.widthPx, dim.stackingHeightPx); + + // Draw visual overlap area + graphics.lineStyle(1, 0xffff00, 0.5); + graphics.strokeRect(pos.x, pos.y - VISUAL_TOP_TILES * TILE_SIZE, + dim.widthPx, VISUAL_TOP_TILES * TILE_SIZE); + + if (showLabels) { + const text = gameRef.add.text( + pos.x + dim.widthPx / 2, + pos.y + dim.stackingHeightPx / 2, + roomId, + { fontSize: '16px', color: '#00ff00' } + ); + text.setOrigin(0.5); + text.setDepth(10001); + } + }); + + if (showGrid) { + // Draw grid overlay + const bounds = calculateWorldBounds(gameRef); + graphics.lineStyle(1, 0xff0000, 0.3); + + for (let x = bounds.x; x < bounds.x + bounds.width; x += GRID_UNIT_WIDTH_PX) { + graphics.lineBetween(x, bounds.y, x, bounds.y + bounds.height); + } + + for (let y = bounds.y; y < bounds.y + bounds.height; y += GRID_UNIT_HEIGHT_PX) { + graphics.lineBetween(bounds.x, y, bounds.x + bounds.width, y); + } + } + + if (showDoors) { + // Highlight door positions + // ... draw door markers ... + } + + window.debugGraphics = graphics; // Store for cleanup +}; + +window.hideRoomLayout = function() { + if (window.debugGraphics) { + window.debugGraphics.destroy(); + window.debugGraphics = null; + } +}; +``` + +--- + +## Summary of Changes to Implementation Plan + +### Documents to Update + +1. **GRID_SYSTEM.md** + - Clarify total height formula: 2 + (N × 4) + - Add validation formula + - Add valid height table (6, 10, 14, 18, ...) + +2. **POSITIONING_ALGORITHM.md** + - No changes needed (works with integer grid units) + +3. **DOOR_PLACEMENT.md** + - Update all single door placement functions + - Add logic to check connected room's multiple connections + - Add door alignment for asymmetric cases + +4. **VALIDATION.md** + - Update room size validation + - Add connectivity validation + - Add E/W door space validation + - Change report format to structured object + +5. **IMPLEMENTATION_STEPS.md** + - Add Phase 0: Create feature flag + - Update Phase 2: Implement N/S first, E/W later + - Add Phase 2.5: Create door positioning module + - Update Phase 4: Use ValidationReport class + - Add incremental testing steps + +6. **TODO_LIST.md** + - Add tasks for feature flag + - Add tasks for incremental implementation + - Add tasks for ValidationReport + - Add tasks for shared door positioning module + - Update door placement tasks with alignment logic + +### New Documents to Create + +7. **MIGRATION_GUIDE.md** (new) + - How to update existing scenarios + - How to create new room sizes + - How to test scenarios + - Common migration issues + +8. **TROUBLESHOOTING.md** (new) + - Common error messages + - How to use debug tools + - How to fix validation errors + - FAQ + +--- + +## Final Recommendation + +**Implement in this order**: + +1. ✅ Fix grid height calculation (document valid heights) +2. ✅ Create shared door positioning module +3. ✅ Fix door alignment for asymmetric connections +4. ✅ Add feature flag +5. ✅ Implement incrementally (N/S first, then E/W) +6. ✅ Add comprehensive validation +7. ✅ Test thoroughly +8. ✅ Create migration guide + +This approach minimizes risk while maximizing success probability.