The facing ribbon rides the mount

While the pane possesses a creature, the keymap's golden ribbon moves
to the mount's token — same shallow triangle, sized to the smaller
body, turning with the borrowed eyes — and leaves the wizard's brow
until the rider returns.

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-27 15:02:28 -04:00
co-authored by Claude Fable 5
parent 714c5bef80
commit e499df22d7
3 changed files with 27 additions and 3 deletions
+5 -1
View File
@@ -56,6 +56,8 @@
let fpBatch = $state<{ n: number; events: GameEvent[]; view: GameView } | null>(null);
/** The pane camera's compass quadrant, worn by the keymap token. */
let fpvFacing = $state<Side | null>(null);
/** The creature the pane is riding, if any: the ribbon moves to it. */
let fpvBody = $state<string | null>(null);
let fxCancels: (() => void)[] = [];
/** A trap drawn by YOU earns a modal; buried log lines get missed. */
let trapNotice = $state<string | null>(null);
@@ -1908,7 +1910,8 @@
? { type: "pickUpTreasure", treasureId: w.id }
: { type: "pickUpObject", instanceId: w.id })}
onCreatureMove={(creatureId, direction) => dispatch({ type: "moveCreature", creatureId, direction })}
onCreatureAttack={(creatureId, targetId) => dispatch({ type: "creatureAttack", creatureId, targetId })} />
onCreatureAttack={(creatureId, targetId) => dispatch({ type: "creatureAttack", creatureId, targetId })}
onbody={(id) => (fpvBody = id)} />
{/if}
<Board
{view}
@@ -1931,6 +1934,7 @@
{litCells}
{sightTrace}
povFacing={prefs.liveFp && view.you && !net.spectating ? fpvFacing : null}
povCreatureId={prefs.liveFp && !net.spectating ? fpvBody : null}
/>
</div>
</section>
+17 -1
View File
@@ -24,6 +24,7 @@
onEdgeClick,
onPlayerClick,
povFacing = null,
povCreatureId = null,
onCreatureClick,
onWarpClick,
onCellPeek,
@@ -47,6 +48,8 @@
/** 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;
/** While the pane rides a creature, the ribbon moves to ITS token. */
povCreatureId?: string | 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. */
@@ -592,7 +595,7 @@
{/if}
{/if}
</g>
{#if povFacing && p.id === view.you}
{#if povFacing && !povCreatureId && p.id === view.you}
<!-- A short wide ribbon hugging the token's leading edge: base as
wide as the token, rising a fifth of that outward. -->
{@const fa = povFacing === "E" ? 0 : povFacing === "S" ? Math.PI / 2 : povFacing === "W" ? Math.PI : -Math.PI / 2}
@@ -653,6 +656,19 @@
<text x="0" y="4" class="creature-label">{c.kind === "fire-imp" ? "I" : c.kind === "democratic-monster" ? "D" : c.kind[0]?.toUpperCase()}</text>
{/if}
</g>
{#if povFacing && povCreatureId === c.id}
<!-- The rider's facing ribbon, worn by the mount. -->
{@const fa2 = povFacing === "E" ? 0 : povFacing === "S" ? Math.PI / 2 : povFacing === "W" ? Math.PI : -Math.PI / 2}
{@const ch = CELL * 0.26}
{@const cfx = Math.cos(fa2)}
{@const cfy = Math.sin(fa2)}
{@const cpx = -cfy}
{@const cpy = cfx}
<polygon
points={`${cfx * ch + cpx * ch},${cfy * ch + cpy * ch} ${cfx * ch - cpx * ch},${cfy * ch - cpy * ch} ${cfx * ch * 1.5},${cfy * ch * 1.5}`}
class="pov-wedge"
/>
{/if}
</g>
{/each}
+5 -1
View File
@@ -17,7 +17,7 @@
import { untrack } from "svelte";
import type { GameEvent, GameView, Side } from "@wizwar/engine";
let { view, batch, onhide, onstride = null, canStride = false, ontarget = null, onfacing = null, litCells = null, onpickup = null, onCreatureMove = null, onCreatureAttack = null }: {
let { view, batch, onhide, onstride = null, canStride = false, ontarget = null, onfacing = null, litCells = null, onpickup = null, onCreatureMove = null, onCreatureAttack = null, onbody = null }: {
view: GameView;
/** The latest live event batch, numbered so each plays once, with
* the view the server sent alongside it. */
@@ -40,6 +40,9 @@
/** Possession: ride a creature's eyes and drive it from the pane. */
onCreatureMove?: ((creatureId: string, side: Side) => void) | null;
onCreatureAttack?: ((creatureId: string, targetId: string) => void) | null;
/** Reports which creature (if any) the pane is riding, so the keymap
* can move the facing ribbon to the mount's token. */
onbody?: ((creatureId: string | null) => void) | null;
} = $props();
const povId = $derived(view.you);
@@ -363,6 +366,7 @@
raf = requestAnimationFrame(tick);
return () => cancelAnimationFrame(raf);
});
$effect(() => { onbody?.(possessedId); });
let lastQuad = -99;
$effect(() => {
const q = ((Math.round(cam.facing / (Math.PI / 2)) % 4) + 4) % 4;