feat(validation): Add comprehensive room layout validation system

- Add validateRoomSize() to check room dimensions match grid units
- Add validateGridAlignment() to verify positions align to grid
- Add validateNoOverlaps() to detect room collisions
- Add validateRoomLayout() as main validation entry point
- Integrate validation into calculateRoomPositions (Phase 5)
- Store roomPositions globally for cross-system access
- Log validation errors/warnings but don't block game load

Validation checks:
✓ Room width is multiple of 5 tiles (grid unit)
✓ Room stacking height is multiple of 4 tiles (grid unit)
✓ Room positions align to 160px × 128px grid
✓ No rooms overlap (AABB collision detection)

Warnings: Room size mismatches (for backwards compatibility)
Errors: Grid misalignment, overlaps (critical issues)
This commit is contained in:
Z. Cliffe Schreuders
2025-11-16 08:45:06 +00:00
parent 0325412342
commit 7cec8b2e17

View File

@@ -780,6 +780,176 @@ function getRoomDimensions(roomId, roomData, gameInstance) {
};
}
// ============================================================================
// VALIDATION FUNCTIONS
// ============================================================================
/**
* Validate that a room's dimensions are multiples of grid units
*
* @param {string} roomId - Room identifier
* @param {Object} dimensions - Room dimensions from getRoomDimensions
* @returns {{valid: boolean, errors: string[]}}
*/
function validateRoomSize(roomId, dimensions) {
const errors = [];
// Check if width is multiple of grid unit width
const widthRemainder = dimensions.widthTiles % GRID_UNIT_WIDTH_TILES;
if (widthRemainder !== 0) {
errors.push(`Room ${roomId} width ${dimensions.widthTiles} tiles is not a multiple of ${GRID_UNIT_WIDTH_TILES} (grid unit width). Remainder: ${widthRemainder} tiles`);
}
// Check if stacking height is multiple of grid unit height
const stackingHeightTiles = dimensions.heightTiles - VISUAL_TOP_TILES;
const heightRemainder = stackingHeightTiles % GRID_UNIT_HEIGHT_TILES;
if (heightRemainder !== 0) {
errors.push(`Room ${roomId} stacking height ${stackingHeightTiles} tiles is not a multiple of ${GRID_UNIT_HEIGHT_TILES} (grid unit height). Remainder: ${heightRemainder} tiles`);
}
return {
valid: errors.length === 0,
errors
};
}
/**
* Validate that all room positions are grid-aligned
*
* @param {Object} positions - Map of roomId -> {x, y}
* @returns {{valid: boolean, errors: string[]}}
*/
function validateGridAlignment(positions) {
const errors = [];
Object.entries(positions).forEach(([roomId, pos]) => {
// Check X alignment
const xRemainder = pos.x % GRID_UNIT_WIDTH_PX;
if (xRemainder !== 0) {
errors.push(`Room ${roomId} X position ${pos.x} is not grid-aligned (remainder: ${xRemainder}px, should be multiple of ${GRID_UNIT_WIDTH_PX}px)`);
}
// Check Y alignment
const yRemainder = pos.y % GRID_UNIT_HEIGHT_PX;
if (yRemainder !== 0) {
errors.push(`Room ${roomId} Y position ${pos.y} is not grid-aligned (remainder: ${yRemainder}px, should be multiple of ${GRID_UNIT_HEIGHT_PX}px)`);
}
});
return {
valid: errors.length === 0,
errors
};
}
/**
* Check if two rooms overlap
*
* @param {string} roomId1 - First room ID
* @param {string} roomId2 - Second room ID
* @param {Object} positions - Map of roomId -> {x, y}
* @param {Object} dimensions - Map of roomId -> dimensions
* @returns {boolean} True if rooms overlap
*/
function roomsOverlap(roomId1, roomId2, positions, dimensions) {
const pos1 = positions[roomId1];
const dim1 = dimensions[roomId1];
const pos2 = positions[roomId2];
const dim2 = dimensions[roomId2];
// Check for overlap using AABB (Axis-Aligned Bounding Box) collision
const overlap = !(
pos1.x + dim1.widthPx <= pos2.x ||
pos2.x + dim2.widthPx <= pos1.x ||
pos1.y + dim1.stackingHeightPx <= pos2.y ||
pos2.y + dim2.stackingHeightPx <= pos1.y
);
return overlap;
}
/**
* Validate that no rooms overlap
*
* @param {Object} positions - Map of roomId -> {x, y}
* @param {Object} dimensions - Map of roomId -> dimensions
* @returns {{valid: boolean, errors: string[]}}
*/
function validateNoOverlaps(positions, dimensions) {
const errors = [];
const roomIds = Object.keys(positions);
// Check each pair of rooms
for (let i = 0; i < roomIds.length; i++) {
for (let j = i + 1; j < roomIds.length; j++) {
const roomId1 = roomIds[i];
const roomId2 = roomIds[j];
if (roomsOverlap(roomId1, roomId2, positions, dimensions)) {
const pos1 = positions[roomId1];
const pos2 = positions[roomId2];
errors.push(`Rooms ${roomId1} and ${roomId2} overlap! ${roomId1} at (${pos1.x}, ${pos1.y}), ${roomId2} at (${pos2.x}, ${pos2.y})`);
}
}
}
return {
valid: errors.length === 0,
errors
};
}
/**
* Validate all room layout constraints
*
* @param {Object} dimensions - Map of roomId -> dimensions
* @param {Object} positions - Map of roomId -> {x, y}
* @returns {{valid: boolean, errors: string[], warnings: string[]}}
*/
function validateRoomLayout(dimensions, positions) {
const errors = [];
const warnings = [];
console.log('\n=== Validating Room Layout ===');
// Validate room sizes
console.log('Validating room sizes...');
Object.entries(dimensions).forEach(([roomId, dim]) => {
const result = validateRoomSize(roomId, dim);
if (!result.valid) {
warnings.push(...result.errors); // Size issues are warnings, not errors
}
});
// Validate grid alignment
console.log('Validating grid alignment...');
const alignmentResult = validateGridAlignment(positions);
if (!alignmentResult.valid) {
errors.push(...alignmentResult.errors);
}
// Validate no overlaps
console.log('Validating room overlaps...');
const overlapResult = validateNoOverlaps(positions, dimensions);
if (!overlapResult.valid) {
errors.push(...overlapResult.errors);
}
const valid = errors.length === 0;
console.log(`Validation ${valid ? 'PASSED' : 'FAILED'}`);
if (warnings.length > 0) {
console.log(`${warnings.length} warnings:`);
warnings.forEach(w => console.warn(` ⚠️ ${w}`));
}
if (errors.length > 0) {
console.log(`${errors.length} errors:`);
errors.forEach(e => console.error(`${e}`));
}
return { valid, errors, warnings };
}
// ============================================================================
// ROOM POSITIONING FUNCTIONS
// ============================================================================
@@ -1075,10 +1245,20 @@ export function calculateRoomPositions(gameInstance) {
console.log(`${roomId}: world(${pos.x}, ${pos.y}) = grid(${gridCoords.gridX}, ${gridCoords.gridY})`);
});
// Store dimensions globally for use by door placement
// Phase 5: Validate room layout
const validation = validateRoomLayout(dimensions, positions);
// Store dimensions and positions globally for use by door placement and validation
window.roomDimensions = dimensions;
window.roomPositions = positions;
console.log('\n=== Room Position Calculations Complete ===\n');
// If validation failed, log but don't block (existing scenarios may have issues)
if (!validation.valid) {
console.error('⚠️ Room layout validation found errors. The game may not work correctly.');
}
return positions;
}