docs: Add comprehensive room layout system redesign plan

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.
This commit is contained in:
Z. Cliffe Schreuders
2025-11-15 23:58:19 +00:00
parent 32d5bbfab5
commit 019986ceef
13 changed files with 5115 additions and 0 deletions

View File

@@ -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.

View File

@@ -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

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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
```

View File

@@ -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.

View File

@@ -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
};
```

View File

@@ -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

View File

@@ -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.

View File

@@ -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.