From d89d995dafacba3eeaedd4cc79c04f3ad2ee76ae Mon Sep 17 00:00:00 2001 From: Eric Wagoner Date: Mon, 17 Aug 2026 16:48:20 -0400 Subject: [PATCH] The sector ghost slides where the sector actually went MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The relocation flourish corrected its coordinates against the view — but effects are mapped when the events arrive, one message before the new state, so the correction read the PRE-move board and the ghost slid between unrelated squares. The sectorRelocated event now carries its origins in final coordinates, computed after the maze renormalizes, and the flourish trusts the event alone. Co-Authored-By: Claude Fable 5 --- packages/engine/src/game.ts | 9 ++++++++- packages/web/src/fx.ts | 10 +++------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/packages/engine/src/game.ts b/packages/engine/src/game.ts index 1a2b4ee..1992cb8 100644 --- a/packages/engine/src/game.ts +++ b/packages/engine/src/game.ts @@ -528,7 +528,10 @@ export type GameEvent = | { type: "illusionWallCreated"; caster: PlayerId; edge: { cell: Cell; side: Side } } | { type: "illusionTested"; player: PlayerId; edge: string; result: "believes" | "seesThrough" } | { type: "sectorRotated"; caster: PlayerId; sectorIndex: number; clockwise: boolean } - | { type: "sectorRelocated"; caster: PlayerId; sectorIndex: number; from: Cell; to: Cell } + | { type: "sectorRelocated"; caster: PlayerId; sectorIndex: number; from: Cell; to: Cell; + /** Origins in FINAL coordinates (the maze may renormalize after the + * landing); from/to record the pre-shift request. */ + finalFrom: Cell; finalTo: Cell } | { type: "creatureCreated"; creatureId: string; kind: CreatureState["kind"]; controller: PlayerId; at: Cell } | { type: "creatureMoved"; creatureId: string; from: Cell; to: Cell; direction: Side; by: PlayerId } | { type: "creatureAttacked"; creatureId: string; kind: CreatureState["kind"]; target: PlayerId | string; dieRoll: number | null } @@ -1459,9 +1462,13 @@ const CARD_EFFECTS: Record const fromOrigin = { ...state.board.placements[idx]!.origin }; const problem = relocateSector(state, idx, dest); if (problem) return problem; + const finalTo = { ...state.board.placements[idx]!.origin }; + const shift = { x: finalTo.x - dest.x, y: finalTo.y - dest.y }; events.push({ type: "sectorRelocated", caster: caster.id, sectorIndex: idx, from: fromOrigin, to: dest, + finalFrom: { x: fromOrigin.x + shift.x, y: fromOrigin.y + shift.y }, + finalTo, }); return null; }, diff --git a/packages/web/src/fx.ts b/packages/web/src/fx.ts index 4521977..e82f73b 100644 --- a/packages/web/src/fx.ts +++ b/packages/web/src/fx.ts @@ -296,13 +296,9 @@ export function fxForEvents( return out; } case "sectorRelocated": { - // The event records pre-normalization origins; the view holds the - // truth. Shift the recorded start by the same correction. - const trueTo = view.board.placements[e.sectorIndex]?.origin; - if (trueTo) { - const dx = trueTo.x - e.to.x, dy = trueTo.y - e.to.y; - push({ kind: "sector-slide", from: { x: e.from.x + dx, y: e.from.y + dy }, to: { ...trueTo } }); - } + // The event carries origins in final coordinates — the view at fx + // time is still the PRE-move board and cannot be trusted for this. + push({ kind: "sector-slide", from: { ...e.finalFrom }, to: { ...e.finalTo } }); return out; } default: