mirror of
https://github.com/cliffe/BreakEscape.git
synced 2026-02-20 13:50:46 +00:00
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:
393
planning_notes/new_room_layout/DOOR_PLACEMENT.md
Normal file
393
planning_notes/new_room_layout/DOOR_PLACEMENT.md
Normal 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.
|
||||
241
planning_notes/new_room_layout/GRID_SYSTEM.md
Normal file
241
planning_notes/new_room_layout/GRID_SYSTEM.md
Normal 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
|
||||
714
planning_notes/new_room_layout/IMPLEMENTATION_STEPS.md
Normal file
714
planning_notes/new_room_layout/IMPLEMENTATION_STEPS.md
Normal 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.
|
||||
74
planning_notes/new_room_layout/OVERVIEW.md
Normal file
74
planning_notes/new_room_layout/OVERVIEW.md
Normal 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
|
||||
469
planning_notes/new_room_layout/POSITIONING_ALGORITHM.md
Normal file
469
planning_notes/new_room_layout/POSITIONING_ALGORITHM.md
Normal 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
|
||||
261
planning_notes/new_room_layout/README.md
Normal file
261
planning_notes/new_room_layout/README.md
Normal 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)
|
||||
165
planning_notes/new_room_layout/TERMINOLOGY.md
Normal file
165
planning_notes/new_room_layout/TERMINOLOGY.md
Normal 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
|
||||
661
planning_notes/new_room_layout/TODO_LIST.md
Normal file
661
planning_notes/new_room_layout/TODO_LIST.md
Normal 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
|
||||
```
|
||||
166
planning_notes/new_room_layout/UPDATED_FILES_SUMMARY.md
Normal file
166
planning_notes/new_room_layout/UPDATED_FILES_SUMMARY.md
Normal 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.
|
||||
503
planning_notes/new_room_layout/VALIDATION.md
Normal file
503
planning_notes/new_room_layout/VALIDATION.md
Normal 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
|
||||
};
|
||||
```
|
||||
304
planning_notes/new_room_layout/WALL_SYSTEM.md
Normal file
304
planning_notes/new_room_layout/WALL_SYSTEM.md
Normal 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
|
||||
511
planning_notes/new_room_layout/review1/CRITICAL_REVIEW.md
Normal file
511
planning_notes/new_room_layout/review1/CRITICAL_REVIEW.md
Normal 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.
|
||||
653
planning_notes/new_room_layout/review1/RECOMMENDATIONS.md
Normal file
653
planning_notes/new_room_layout/review1/RECOMMENDATIONS.md
Normal 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.
|
||||
Reference in New Issue
Block a user