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.
19 KiB
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:
// 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)
/**
* 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)
/**
* 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:
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
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
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:
- Extract all room dimensions first
- Support all 4 directions (north, south, east, west)
- Use breadth-first processing
- Call new positioning functions
- 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:
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:
- Get room dimensions using
getRoomDimensions() - Calculate grid coordinates using
worldToGrid() - For each connection direction:
- Determine single vs multiple connections
- Call appropriate door placement function
- Create door sprite at returned position
- 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:
- Import door placement functions from doors.js
- Use same door positioning functions as door sprites
- Remove duplicate positioning logic (lines 197-283)
- 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:
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():
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:
scenario1.json(biometric_breach)cybok_heist.jsonscenario2.jsonceo_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
{
"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
{
"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
{
"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
{
"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:
-
Floating point errors: Door positions off by 1-2px
- Fix: Use
Math.round()for all position calculations
- Fix: Use
-
Grid misalignment: Rooms not aligned to grid boundaries
- Fix: Use
alignToGrid()function
- Fix: Use
-
Door misalignment: Doors don't line up between rooms
- Fix: Ensure both rooms use same calculation logic
-
Visual gaps: Rooms don't touch properly
- Fix: Check stacking height calculations
-
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:
// 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:
// 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:
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:
- Complete Phase 1 → Test → Commit
- Complete Phase 2 → Test → Commit
- etc.
Keep Old Code Available
Comment out old code instead of deleting initially:
// OLD IMPLEMENTATION - Remove after testing
// function oldCalculateRoomPositions() { ... }
// NEW IMPLEMENTATION
function calculateRoomPositions() { ... }
This allows easy comparison if issues arise.