Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions src/components/game/Minimap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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;
Expand All @@ -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);

Expand All @@ -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;

Expand All @@ -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);
Expand Down
48 changes: 24 additions & 24 deletions src/engine/systems/VisionSystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
});
Expand All @@ -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,
});
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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,
});
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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);
Expand All @@ -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 {
Expand All @@ -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)!;
Expand All @@ -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;
Expand All @@ -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');
Expand All @@ -963,34 +963,34 @@ 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)})`
);
}
}
}

// 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';
Expand All @@ -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';
}

Expand Down
2 changes: 1 addition & 1 deletion src/rendering/BuildingRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion src/rendering/UnitRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 5 additions & 6 deletions src/rendering/compute/VisionCompute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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)), () => {
Expand Down
12 changes: 6 additions & 6 deletions src/rendering/tsl/FogOfWar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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];

Expand Down
Loading