From 93f478954fb2e990c3ef5453873fd4d9fa6e93ae Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 30 Jan 2026 15:54:15 +0000 Subject: [PATCH 1/2] fix: Add VelocityAdapter to RenderStateWorldAdapter for unit animations The UnitRenderer uses Velocity component data to determine if a unit is moving (for walk/idle animation transitions). In worker mode, entities come from RenderStateWorldAdapter which was missing a VelocityAdapter, causing entity.get('Velocity') to always return undefined. This resulted in isMoving always being false, so units would only play idle animations even while moving. The fix adds a VelocityAdapter that derives velocity from position delta (current - previous) using the prevX/prevY data already in UnitRenderState. https://claude.ai/code/session_01NUQdm7SkEPq2rQZcSFmRQf --- src/engine/workers/RenderStateAdapter.ts | 28 ++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/engine/workers/RenderStateAdapter.ts b/src/engine/workers/RenderStateAdapter.ts index 38e3a413..33d048bf 100644 --- a/src/engine/workers/RenderStateAdapter.ts +++ b/src/engine/workers/RenderStateAdapter.ts @@ -34,6 +34,7 @@ class UnitEntityAdapter implements IEntity { private unitComponent: UnitAdapter; private healthComponent: HealthAdapter; private selectableComponent: SelectableAdapter; + private velocityComponent: VelocityAdapter; constructor(data: UnitRenderState) { this.id = data.id; @@ -43,6 +44,7 @@ class UnitEntityAdapter implements IEntity { this.unitComponent = new UnitAdapter(data); this.healthComponent = new HealthAdapter(data); this.selectableComponent = new SelectableAdapter(data); + this.velocityComponent = new VelocityAdapter(data); } public get(componentType: string): T | undefined { @@ -55,6 +57,8 @@ class UnitEntityAdapter implements IEntity { return this.healthComponent as unknown as T; case 'Selectable': return this.selectableComponent as unknown as T; + case 'Velocity': + return this.velocityComponent as unknown as T; default: return undefined; } @@ -74,6 +78,7 @@ class UnitEntityAdapter implements IEntity { this.unitComponent.update(data); this.healthComponent.update(data); this.selectableComponent.update(data); + this.velocityComponent.update(data); } } @@ -212,6 +217,29 @@ class SelectableAdapter { } } +/** + * Velocity-like adapter - derives velocity from position delta (current - previous) + * This allows animation systems to detect movement for walk/idle transitions. + */ +class VelocityAdapter { + public x: number; + public y: number; + public z: number; + + constructor(data: UnitRenderState) { + // Velocity is approximated from position change between frames + this.x = data.x - data.prevX; + this.y = data.y - data.prevY; + this.z = data.z - data.prevZ; + } + + public update(data: UnitRenderState): void { + this.x = data.x - data.prevX; + this.y = data.y - data.prevY; + this.z = data.z - data.prevZ; + } +} + // ============================================================================ // BUILDING ENTITY ADAPTER // ============================================================================ From 4f6257c62e83b9bedb180ef17dfe133d70ff1d8d Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 30 Jan 2026 16:13:48 +0000 Subject: [PATCH 2/2] fix: Add missing component properties to RenderStateWorldAdapter The RenderStateWorldAdapter was missing several properties that renderers need for proper functionality in worker mode: Units: - targetX, targetY: needed for CommandQueueRenderer waypoint visualization - commandQueue: needed for shift-click command queue visualization - attackRange, sightRange: needed for GameOverlay range ring rendering - speed: needed for VehicleEffectsSystem speed-based effects Buildings: - attackRange, sightRange: needed for GameOverlay range ring rendering Changes: 1. types.ts: Added new properties to UnitRenderState and BuildingRenderState interfaces, plus SerializedQueuedCommand type 2. GameWorker.ts: Updated collectUnitRenderState() and collectBuildingRenderState() to populate the new fields 3. RenderStateAdapter.ts: Updated UnitAdapter and BuildingComponentAdapter to expose the new properties https://claude.ai/code/session_01NUQdm7SkEPq2rQZcSFmRQf --- src/engine/workers/GameWorker.ts | 17 ++++++++++++++ src/engine/workers/RenderStateAdapter.ts | 28 ++++++++++++++++++++++++ src/engine/workers/types.ts | 23 +++++++++++++++++++ 3 files changed, 68 insertions(+) diff --git a/src/engine/workers/GameWorker.ts b/src/engine/workers/GameWorker.ts index 993f1952..f202bcdd 100644 --- a/src/engine/workers/GameWorker.ts +++ b/src/engine/workers/GameWorker.ts @@ -665,6 +665,20 @@ export class WorkerGame extends GameCore { repairTargetId: unit.repairTargetId, hasSpeedBuff: unit.hasBuff('stim'), hasDamageBuff: unit.hasBuff('damage_boost'), + // Movement/targeting for waypoint visualization + targetX: unit.targetX, + targetY: unit.targetY, + speed: unit.speed, + // Command queue (serialized for transfer) + commandQueue: unit.commandQueue.map(cmd => ({ + type: cmd.type, + targetX: cmd.targetX, + targetY: cmd.targetY, + targetEntityId: cmd.targetEntityId, + })), + // Combat stats for range overlays + attackRange: unit.attackRange, + sightRange: unit.sightRange, }); } @@ -706,6 +720,9 @@ export class WorkerGame extends GameCore { hasProductionQueue: building.productionQueue.length > 0, rallyX: building.rallyX, rallyY: building.rallyY, + // Combat stats for range overlays + attackRange: building.attackRange, + sightRange: building.sightRange, }); } diff --git a/src/engine/workers/RenderStateAdapter.ts b/src/engine/workers/RenderStateAdapter.ts index 33d048bf..6cd2f7fd 100644 --- a/src/engine/workers/RenderStateAdapter.ts +++ b/src/engine/workers/RenderStateAdapter.ts @@ -123,6 +123,15 @@ class UnitAdapter { public carryingMinerals: number; public carryingVespene: number; public gatherTargetId: number | null; + // Movement/targeting for waypoint visualization + public targetX: number | null; + public targetY: number | null; + public speed: number; + // Command queue for shift-click visualization + public commandQueue: Array<{ type: string; targetX?: number; targetY?: number; targetEntityId?: number }>; + // Combat stats for range overlays + public attackRange: number; + public sightRange: number; constructor(data: UnitRenderState) { this.unitId = data.unitId; @@ -138,6 +147,12 @@ class UnitAdapter { this.carryingMinerals = data.carryingMinerals; this.carryingVespene = data.carryingVespene; this.gatherTargetId = data.gatherTargetId; + this.targetX = data.targetX; + this.targetY = data.targetY; + this.speed = data.speed; + this.commandQueue = data.commandQueue; + this.attackRange = data.attackRange; + this.sightRange = data.sightRange; } public update(data: UnitRenderState): void { @@ -154,6 +169,12 @@ class UnitAdapter { this.carryingMinerals = data.carryingMinerals; this.carryingVespene = data.carryingVespene; this.gatherTargetId = data.gatherTargetId; + this.targetX = data.targetX; + this.targetY = data.targetY; + this.speed = data.speed; + this.commandQueue = data.commandQueue; + this.attackRange = data.attackRange; + this.sightRange = data.sightRange; } public isSelected(): boolean { @@ -328,6 +349,9 @@ class BuildingComponentAdapter { public liftProgress: number; // Production queue simulation for renderer compatibility public productionQueue: { progress: number }[]; + // Combat stats for range overlays + public attackRange: number; + public sightRange: number; constructor(data: BuildingRenderState) { this.buildingId = data.buildingId; @@ -343,6 +367,8 @@ class BuildingComponentAdapter { this.productionQueue = data.hasProductionQueue ? [{ progress: data.productionProgress }] : []; + this.attackRange = data.attackRange; + this.sightRange = data.sightRange; } public update(data: BuildingRenderState): void { @@ -359,6 +385,8 @@ class BuildingComponentAdapter { this.productionQueue = data.hasProductionQueue ? [{ progress: data.productionProgress }] : []; + this.attackRange = data.attackRange; + this.sightRange = data.sightRange; } public isReady(): boolean { diff --git a/src/engine/workers/types.ts b/src/engine/workers/types.ts index 8c143789..ab3908bb 100644 --- a/src/engine/workers/types.ts +++ b/src/engine/workers/types.ts @@ -72,6 +72,26 @@ export interface UnitRenderState { // Buff indicators hasSpeedBuff: boolean; hasDamageBuff: boolean; + // Movement/targeting for waypoint visualization + targetX: number | null; + targetY: number | null; + speed: number; + // Command queue for shift-click visualization (serialized) + commandQueue: SerializedQueuedCommand[]; + // Combat stats for range overlays + attackRange: number; + sightRange: number; +} + +/** + * Serialized form of QueuedCommand for transfer to main thread. + * Simplified to only include render-relevant data. + */ +export interface SerializedQueuedCommand { + type: string; + targetX?: number; + targetY?: number; + targetEntityId?: number; } /** @@ -111,6 +131,9 @@ export interface BuildingRenderState { // Rally point rallyX: number | null; rallyY: number | null; + // Combat stats for range overlays + attackRange: number; + sightRange: number; } /**