Milestone two: the pane becomes the wand hand

Clicks in the first-person view now act. The renderer keeps what the
last frame knew — per-column depth, warp bends, every wall hit's edge,
every sprite's screen rect with its identity — and hitTest inverts the
projection: the nearest visible sprite answers as its wizard or
creature, a struck wall or door answers as its edge, and the ground
answers as the square under the pixel, warp-bent columns mapping their
virtual floor back to real cells through the warp's own rigid motion.
The pane hands each target to the very functions the top-down board's
clicks use, so pane and map can never disagree: click the floor ahead
and you walk there; click a door with a key selected and the lock
turns; click a wizard with an attack and the spell flies.

The keymap token now wears a golden wedge showing the camera's compass
quadrant — the cockpit and the chart always agree on which way you
face. Verified live: a floor click became a real move command in the
room ledger, edge clicks resolve and defer correctly, and the wedge
tracks quarter-turns.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015RCWSTnb1KYTPyL4GmhGnF
This commit is contained in:
Eric Wagoner
2026-08-26 12:28:51 -04:00
co-authored by Claude Fable 5
parent aac06dd2b5
commit 42d872df6d
5 changed files with 183 additions and 11 deletions
+17
View File
@@ -23,6 +23,7 @@
onCellClick,
onEdgeClick,
onPlayerClick,
povFacing = null,
onCreatureClick,
onWarpClick,
onCellPeek,
@@ -43,6 +44,9 @@
onCellClick?: (cell: { x: number; y: number }) => void;
onEdgeClick?: (cell: { x: number; y: number }, side: Side) => void;
onPlayerClick?: (playerId: string) => void;
/** Which way YOUR first-person camera faces: a wedge on your token
* keeps the keymap and the pane pointing the same way. */
povFacing?: "N" | "E" | "S" | "W" | null;
onCreatureClick?: (creatureId: string) => void;
onWarpClick?: (cell: { x: number; y: number }, side: Side) => void;
/** Long-press on a square: read the card behind whatever occupies it. */
@@ -539,6 +543,14 @@
{@const cx = p.position.x * CELL + CELL / 2 + (group.length > 1 ? (gi - (group.length - 1) / 2) * 14 : 0)}
{@const cy = p.position.y * CELL + CELL * 0.36}
<g class="mover" class:snap={snapsTo(`w:${p.id}`, cx, cy)} style={`transform: translate(${cx}px, ${cy}px)`}>
{#if povFacing && p.id === view.you}
{@const fr = CELL * 0.34}
{@const fa = povFacing === "E" ? 0 : povFacing === "S" ? Math.PI / 2 : povFacing === "W" ? Math.PI : -Math.PI / 2}
<polygon
points={`${Math.cos(fa) * fr},${Math.sin(fa) * fr} ${Math.cos(fa + 2.6) * fr * 0.55},${Math.sin(fa + 2.6) * fr * 0.55} ${Math.cos(fa - 2.6) * fr * 0.55},${Math.sin(fa - 2.6) * fr * 0.55}`}
class="pov-wedge"
/>
{/if}
<g
role="button" tabindex="-1"
onclick={(ev) => { ev.stopPropagation(); if (peekFired) { peekFired = false; return; } onPlayerClick?.(p.id); }}
@@ -765,6 +777,11 @@
fill: #6a5c44;
pointer-events: none;
}
.pov-wedge {
fill: #c9a72a;
opacity: 0.85;
pointer-events: none;
}
.peekable { cursor: pointer; pointer-events: all; }
.sight-corner {
fill: none;