Attachments ride every targeted cast, not just player targets

The engine learned to bend neutral spells, but the client only sent
aroundCornerInstanceId (and amplify/add/extend) on player-target casts
— applyMods was never called on the cell, edge, creature, two-cell,
trap, or teleport-opponent paths, so a SAFE aimed around the corner
went out bare and the engine rightly refused it. All targeted cast
dispatches now pass through withMods, so what the hand shows attached
is what the command carries.

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 17:41:52 -04:00
co-authored by Claude Fable 5
parent 3bf3c33fdc
commit 358633022c
+17 -11
View File
@@ -423,6 +423,12 @@
}
}
/** Every targeted cast goes out through here so attachments never drop. */
function withMods<T extends Parameters<typeof net.command>[0] & { type: "cast" }>(cmd: T): T {
applyMods(cmd);
return cmd;
}
/** Suggest-as-you-type pool for the card-naming field, tuned per card. */
const nameSuggestions = $derived.by(() => {
if (!selectedCard) return [];
@@ -622,11 +628,11 @@
if (!yourMoment) { peekAt(cell); return; }
if (pendingCellFor && selectedCard) {
// Stage 2 of teleport-opponent: destination chosen.
dispatch({
dispatch(withMods({
type: "cast", instanceId: selectedCard.instanceId,
target: { kind: "player", playerId: pendingCellFor },
params: { cell },
});
}));
clearSelection();
return;
}
@@ -653,17 +659,17 @@
? trapCells.filter((_, i) => i !== already)
: [...trapCells, cell];
if (trapCells.length === 4) {
dispatch({ type: "cast", instanceId: selectedCard.instanceId, params: { cells: trapCells } });
dispatch(withMods({ type: "cast", instanceId: selectedCard.instanceId, params: { cells: trapCells } }));
clearSelection();
}
return;
}
if (selectedCard && TWO_CELL_CARDS.has(selectedCard.cardId)) {
if (!tradeFrom) { tradeFrom = cell; return; }
dispatch({
dispatch(withMods({
type: "cast", instanceId: selectedCard.instanceId,
target: { kind: "cell", cell }, params: { cell: tradeFrom },
});
}));
clearSelection();
return;
}
@@ -676,11 +682,11 @@
return;
}
if (selectedCard && CELL_CARDS.has(selectedCard.cardId)) {
dispatch({
dispatch(withMods({
type: "cast", instanceId: selectedCard.instanceId,
target: { kind: "cell", cell },
...(attachedNumber ? { numberInstanceIds: [attachedNumber.instanceId] } : {}),
});
}));
clearSelection();
return;
}
@@ -858,14 +864,14 @@
if (!selectedCard || !edgeSelectMode) return;
// The attached number rides along for every edge cast: wand charges,
// wall-of-fire durations, wall attacks alike.
dispatch({
dispatch(withMods({
type: "cast",
instanceId: selectedCard.instanceId,
target: { kind: "edge", cell, side },
...(holdDoor && (selectedCard.cardId === "pick-lock" || selectedCard.cardId === "master-key")
? { params: { hold: true } } : {}),
...(attachedNumber ? { numberInstanceIds: [attachedNumber.instanceId] } : {}),
});
}));
clearSelection();
}
@@ -881,12 +887,12 @@
return;
}
if (selectedCard && (CREATURE_TARGET_CARDS.has(selectedCard.cardId) || cardDef(selectedCard.cardId).cardType === "attack")) {
dispatch({
dispatch(withMods({
type: "cast", instanceId: selectedCard.instanceId,
target: { kind: "creature", creatureId },
...(selectedCard.cardId === "mega-monster" ? { params: { boost: megaBoost } } : {}),
...(attachedNumber ? { numberInstanceIds: [attachedNumber.instanceId] } : {}),
});
}));
clearSelection();
return;
}