From 35309bc1066be2cf7c09e8d01e430c4636bcc8e4 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 2 Feb 2026 19:51:11 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20revert=20incorrect=20Y=E2=86=92Z=20coord?= =?UTF-8?q?inate=20changes,=20keep=20map=20dimension=20fix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reverted the transform.y → transform.z changes that broke the minimap and fog of war. In this codebase, transform.y IS the depth coordinate (not altitude), which is an unconventional but valid convention. The following fixes remain in place: - setMapDimensions() method in EffectPasses.ts for dynamic map sizing - PostProcessing.ts using setMapDimensions() with cell size - PathfindingSystem.ts cloning navmesh arrays to prevent detachment - Y-flip in FogOfWar.ts restored for correct texture coordinates https://claude.ai/code/session_013Hqn3PxBW8h7hpMNMKKv1e --- src/components/game/Minimap.tsx | 16 ++++----- src/engine/systems/VisionSystem.ts | 48 +++++++++++++------------- src/rendering/BuildingRenderer.ts | 2 +- src/rendering/UnitRenderer.ts | 2 +- src/rendering/compute/VisionCompute.ts | 11 +++--- src/rendering/tsl/FogOfWar.ts | 12 +++---- 6 files changed, 45 insertions(+), 46 deletions(-) diff --git a/src/components/game/Minimap.tsx b/src/components/game/Minimap.tsx index 7976efa0..2ee10797 100644 --- a/src/components/game/Minimap.tsx +++ b/src/components/game/Minimap.tsx @@ -174,13 +174,13 @@ export function Minimap() { // Draw resources const resources = worldAdapter.getEntitiesWith('Transform', 'Resource'); for (const entity of resources) { - const transform = entity.get('Transform') as { x: number; y: number; z: number } | undefined; + const transform = entity.get('Transform') as { x: number; y: number } | undefined; const resource = entity.get('Resource') as { resourceType: string } | undefined; if (!transform || !resource) continue; const x = transform.x * scale; - const y = transform.z * scale; // Use z (depth) for minimap Y + const y = transform.y * scale; ctx.fillStyle = resource.resourceType === 'minerals' ? '#00aaff' : '#00ff66'; ctx.beginPath(); @@ -191,7 +191,7 @@ export function Minimap() { // Draw buildings const buildings = worldAdapter.getEntitiesWith('Transform', 'Building', 'Selectable', 'Health'); for (const entity of buildings) { - const transform = entity.get('Transform') as { x: number; y: number; z: number } | undefined; + const transform = entity.get('Transform') as { x: number; y: number } | undefined; const building = entity.get('Building') as { width: number; height: number; state: string; isComplete?: () => boolean } | undefined; const selectable = entity.get('Selectable') as { playerId: string } | undefined; const health = entity.get('Health') as { current: number; max: number } | undefined; @@ -202,13 +202,13 @@ export function Minimap() { // Skip enemy buildings that are not visible due to fog of war (unless spectator) const fogOfWarEnabled = useGameSetupStore.getState().fogOfWar; if (localPlayer && selectable.playerId !== localPlayer && fogOfWarEnabled && game.visionSystem && !isSpectating) { - if (!game.visionSystem.isVisible(localPlayer, transform.x, transform.z)) { + if (!game.visionSystem.isVisible(localPlayer, transform.x, transform.y)) { continue; } } const x = transform.x * scale; - const y = transform.z * scale; // Use z (depth) for minimap Y + const y = transform.y * scale; const w = Math.max(building.width * scale, 4); const h = Math.max(building.height * scale, 4); @@ -229,7 +229,7 @@ export function Minimap() { // Draw units const units = worldAdapter.getEntitiesWith('Transform', 'Unit', 'Selectable', 'Health'); for (const entity of units) { - const transform = entity.get('Transform') as { x: number; y: number; z: number } | undefined; + const transform = entity.get('Transform') as { x: number; y: number } | undefined; const selectable = entity.get('Selectable') as { playerId: string } | undefined; const health = entity.get('Health') as { current: number; max: number } | undefined; @@ -239,13 +239,13 @@ export function Minimap() { // Skip enemy units that are not visible due to fog of war (unless spectator) const fogEnabled = useGameSetupStore.getState().fogOfWar; if (localPlayer && selectable.playerId !== localPlayer && fogEnabled && game.visionSystem && !isSpectating) { - if (!game.visionSystem.isVisible(localPlayer, transform.x, transform.z)) { + if (!game.visionSystem.isVisible(localPlayer, transform.x, transform.y)) { continue; } } const x = transform.x * scale; - const y = transform.z * scale; // Use z (depth) for minimap Y + const y = transform.y * scale; // Unit color based on player's assigned color const unitPlayerHex = getPlayerColor(selectable.playerId); diff --git a/src/engine/systems/VisionSystem.ts b/src/engine/systems/VisionSystem.ts index 279e742a..41749fbb 100644 --- a/src/engine/systems/VisionSystem.ts +++ b/src/engine/systems/VisionSystem.ts @@ -503,7 +503,7 @@ export class VisionSystem extends System { casters.push({ x: transform.x, - y: transform.z, // Use Z (depth) not Y (altitude) for 2D vision grid + y: transform.y, sightRange: unit.sightRange, playerId: playerIndex, }); @@ -524,7 +524,7 @@ export class VisionSystem extends System { casters.push({ x: transform.x, - y: transform.z, // Use Z (depth) not Y (altitude) for 2D vision grid + y: transform.y, sightRange: building.sightRange, playerId: playerIndex, }); @@ -569,8 +569,8 @@ export class VisionSystem extends System { if (!transform || !selectable) continue; const dx = transform.x - tower.x; - const dz = transform.z - tower.y; // tower.y is stored depth, compare with transform.z - const distSq = dx * dx + dz * dz; + const dy = transform.y - tower.y; + const distSq = dx * dx + dy * dy; if (distSq <= captureRadiusSq) { tower.controllingPlayers.add(selectable.playerId); @@ -699,7 +699,7 @@ export class VisionSystem extends System { units.push({ id: entity.id, x: transform.x, - y: transform.z, // Use Z (depth) for 2D vision grid + y: transform.y, sightRange: unit.sightRange, playerId: selectable.playerId, }); @@ -729,7 +729,7 @@ export class VisionSystem extends System { buildings.push({ id: entity.id, x: transform.x, - y: transform.z, // Use Z (depth) for 2D vision grid + y: transform.y, sightRange: building.sightRange, playerId: selectable.playerId, isOperational: building.isOperational(), @@ -869,8 +869,8 @@ export class VisionSystem extends System { if (!transform || !selectable) continue; const dx = transform.x - tower.x; - const dz = transform.z - tower.y; // tower.y is stored depth, compare with transform.z - const distSq = dx * dx + dz * dz; + const dy = transform.y - tower.y; + const distSq = dx * dx + dy * dy; if (distSq <= captureRadiusSq) { tower.controllingPlayers.add(selectable.playerId); @@ -896,7 +896,7 @@ export class VisionSystem extends System { if (!transform || !unit || !selectable) return; - this.revealArea(selectable.playerId, transform.x, transform.z, unit.sightRange); + this.revealArea(selectable.playerId, transform.x, transform.y, unit.sightRange); } private updateBuildingVision(entity: Entity): void { @@ -909,10 +909,10 @@ export class VisionSystem extends System { // Only provide vision if building is operational if (!building.isOperational()) return; - this.revealArea(selectable.playerId, transform.x, transform.z, building.sightRange); + this.revealArea(selectable.playerId, transform.x, transform.y, building.sightRange); } - private revealArea(playerId: string, worldX: number, worldZ: number, range: number): void { + private revealArea(playerId: string, worldX: number, worldY: number, range: number): void { this.ensurePlayerRegistered(playerId); const visionGrid = this.visionMap.playerVision.get(playerId)!; @@ -921,7 +921,7 @@ export class VisionSystem extends System { const gridHeight = this.visionMap.height; const cellX = Math.floor(worldX / this.cellSize); - const cellY = Math.floor(worldZ / this.cellSize); // cellY maps to worldZ (depth) + const cellY = Math.floor(worldY / this.cellSize); const cellRange = Math.ceil(range / this.cellSize); const cellRangeSq = cellRange * cellRange; @@ -948,7 +948,7 @@ export class VisionSystem extends System { */ private detectCloakedUnitsInArea( playerId: string, - position: { x: number; y: number }, // y here is actually Z (depth) in world space + position: { x: number; y: number }, radius: number ): void { const units = this.world.getEntitiesWith('Unit', 'Transform', 'Selectable'); @@ -963,21 +963,21 @@ export class VisionSystem extends System { if (selectable.playerId === playerId) continue; if (!unit.isCloaked) continue; - // Check if within detection radius (position.y is depth/Z coordinate) + // Check if within detection radius const dx = transform.x - position.x; - const dz = transform.z - position.y; // position.y is Z in 2D map space - const distSq = dx * dx + dz * dz; + const dy = transform.y - position.y; + const distSq = dx * dx + dy * dy; if (distSq <= radiusSq) { // Emit detection event this.game.eventBus.emit('unit:detected', { entityId: entity.id, detectedBy: playerId, - position: { x: transform.x, y: transform.z }, // Report 2D map position + position: { x: transform.x, y: transform.y }, }); debugPathfinding.log( - `[VisionSystem] Detected cloaked unit ${entity.id} at (${transform.x.toFixed(1)}, ${transform.z.toFixed(1)})` + `[VisionSystem] Detected cloaked unit ${entity.id} at (${transform.x.toFixed(1)}, ${transform.y.toFixed(1)})` ); } } @@ -985,12 +985,12 @@ export class VisionSystem extends System { // Public API for checking visibility - public getVisionState(playerId: string, worldX: number, worldZ: number): VisionState { + public getVisionState(playerId: string, worldX: number, worldY: number): VisionState { const visionGrid = this.visionMap.playerVision.get(playerId); if (!visionGrid) return 'unexplored'; const cellX = Math.floor(worldX / this.cellSize); - const cellY = Math.floor(worldZ / this.cellSize); // cellY maps to worldZ (depth) + const cellY = Math.floor(worldY / this.cellSize); if (cellX < 0 || cellX >= this.visionMap.width || cellY < 0 || cellY >= this.visionMap.height) { return 'unexplored'; @@ -999,12 +999,12 @@ export class VisionSystem extends System { return visionGrid[cellY][cellX]; } - public isVisible(playerId: string, worldX: number, worldZ: number): boolean { - return this.getVisionState(playerId, worldX, worldZ) === 'visible'; + public isVisible(playerId: string, worldX: number, worldY: number): boolean { + return this.getVisionState(playerId, worldX, worldY) === 'visible'; } - public isExplored(playerId: string, worldX: number, worldZ: number): boolean { - const state = this.getVisionState(playerId, worldX, worldZ); + public isExplored(playerId: string, worldX: number, worldY: number): boolean { + const state = this.getVisionState(playerId, worldX, worldY); return state === 'visible' || state === 'explored'; } diff --git a/src/rendering/BuildingRenderer.ts b/src/rendering/BuildingRenderer.ts index 15757ca7..62a3251c 100644 --- a/src/rendering/BuildingRenderer.ts +++ b/src/rendering/BuildingRenderer.ts @@ -647,7 +647,7 @@ export class BuildingRenderer { // Check visibility for enemy buildings (skip in spectator mode - show all) let shouldShow = true; if (isEnemy && this.visionSystem && this.playerId) { - shouldShow = this.visionSystem.isExplored(this.playerId, transform.x, transform.z); + shouldShow = this.visionSystem.isExplored(this.playerId, transform.x, transform.y); } // PERF: Get cached terrain height (buildings rarely move) diff --git a/src/rendering/UnitRenderer.ts b/src/rendering/UnitRenderer.ts index 63693afb..805f9ddd 100644 --- a/src/rendering/UnitRenderer.ts +++ b/src/rendering/UnitRenderer.ts @@ -1005,7 +1005,7 @@ export class UnitRenderer { // Check visibility for enemy units (skip in spectator mode - show all) let shouldShow = true; if (isEnemy && this.visionSystem && this.playerId) { - shouldShow = this.visionSystem.isVisible(this.playerId, transform.x, transform.z); + shouldShow = this.visionSystem.isVisible(this.playerId, transform.x, transform.y); } // Skip dead units diff --git a/src/rendering/compute/VisionCompute.ts b/src/rendering/compute/VisionCompute.ts index 5e3046fb..becf166d 100644 --- a/src/rendering/compute/VisionCompute.ts +++ b/src/rendering/compute/VisionCompute.ts @@ -197,10 +197,9 @@ export class VisionCompute { return; }); - // Cell center in world coordinates (X horizontal, Z depth) - // Note: cellY maps to worldZ (depth), not worldY (altitude) + // Cell center in world coordinates const worldX = cellX.toFloat().mul(cellSize).add(cellSize.mul(0.5)); - const worldZ = cellY.toFloat().mul(cellSize).add(cellSize.mul(0.5)); + const worldY = cellY.toFloat().mul(cellSize).add(cellSize.mul(0.5)); // Read previous frame's data for temporal accumulation const prevUV = vec2( @@ -219,13 +218,13 @@ export class VisionCompute { Loop(casterCount, () => { const caster = casterBuffer.element(i); const casterX = caster.x; - const casterZ = caster.y; // caster.y stores Z (depth) coordinate + const casterY = caster.y; const sightRange = caster.z; const casterPlayer = caster.w.toInt(); const dx = worldX.sub(casterX); - const dz = worldZ.sub(casterZ); - const distSq = dx.mul(dx).add(dz.mul(dz)); + const dy = worldY.sub(casterY); + const distSq = dx.mul(dx).add(dy.mul(dy)); const rangeSq = sightRange.mul(sightRange); If(distSq.lessThanEqual(rangeSq).and(casterPlayer.equal(targetPlayerId)), () => { diff --git a/src/rendering/tsl/FogOfWar.ts b/src/rendering/tsl/FogOfWar.ts index b1703a10..6904c53e 100644 --- a/src/rendering/tsl/FogOfWar.ts +++ b/src/rendering/tsl/FogOfWar.ts @@ -201,9 +201,9 @@ export class TSLFogOfWar { // Update CPU texture from vision grid with temporal smoothing for (let y = 0; y < this.gridHeight; y++) { for (let x = 0; x < this.gridWidth; x++) { - // No Y-flip needed - grid y maps directly to texture row - // Grid y=0 (worldZ~0) → texture row 0 (UV v~0) - const i = y * this.gridWidth + x; + // Flip Y for texture coordinates + const textureY = this.gridHeight - 1 - y; + const i = textureY * this.gridWidth + x; const state = visionGrid[y]?.[x] ?? 'unexplored'; // Current visibility (target) @@ -281,9 +281,9 @@ export class TSLFogOfWar { // Update CPU texture from serialized data with temporal smoothing for (let y = 0; y < this.gridHeight; y++) { for (let x = 0; x < this.gridWidth; x++) { - // No Y-flip needed - grid y maps directly to texture row - // Grid y=0 (worldZ~0) → texture row 0 (UV v~0) - const textureIndex = y * this.gridWidth + x; + // Flip Y for texture coordinates + const textureY = this.gridHeight - 1 - y; + const textureIndex = textureY * this.gridWidth + x; const dataIndex = y * this.gridWidth + x; const state = serializedData[dataIndex];