Forced drops bank honestly; half-dead sockets confess
DROP OBJECT's forced drop hardcoded onHomeOf null and skipped the victory check — a treasure shaken loose over a home square neither reported the banking nor triggered scoring or the captured-treasures clause until something else prodded the engine. It now lands like any drop: homeOwnerAt reported, checkVictory immediate. And the stuck-game mystery from 38YP: a deploy restart left the browser holding a half-dead socket that never fired onclose, so the existing reconnect loop and masthead banner never engaged and a live game wore a frozen face. The client now pings every 15s and force- closes a socket silent for 45s, which wakes the reconnect machinery; the server answers pong. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015RCWSTnb1KYTPyL4GmhGnF
This commit is contained in:
co-authored by
Claude Fable 5
parent
5eb5312637
commit
ca0a4ef2e3
@@ -1265,3 +1265,34 @@ describe("the tear is an attack (rev 6)", () => {
|
||||
expect(r.events.some((e) => e.type === "treasureTorn" || e.type === "tearResisted")).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("a forced drop lands like any drop", () => {
|
||||
it("DROP OBJECT onto a home square reports the banking and checks victory", () => {
|
||||
let { state } = createGame({ playerIds: ["att", "def"], seed: 42, sets: ["basic", "expansion1"] });
|
||||
state = toRound2(state);
|
||||
while (activePlayer(state).id !== "att") {
|
||||
state = must(state, activePlayer(state).id, { type: "endTurn", draw: 0 });
|
||||
}
|
||||
const att = state.players.find((p) => p.id === "att")!;
|
||||
const def = state.players.find((p) => p.id === "def")!;
|
||||
// The defender stands on their OWN home carrying the attacker's gold —
|
||||
// the forced drop banks it for them, cruel as that is for the caster.
|
||||
def.position = { ...def.home };
|
||||
att.position = { ...def.home };
|
||||
const gold = state.treasures.find((t) => t.owner === "att" && t.position)!;
|
||||
gold.position = null;
|
||||
gold.carriedBy = "def";
|
||||
def.carriedTreasureId = gold.id;
|
||||
const dob = giveCard(state, "att", "drop-object", "D", 0);
|
||||
let r = applyCommand(state, "att", {
|
||||
type: "cast", instanceId: dob.instanceId,
|
||||
target: { kind: "player", playerId: "def" }, params: { cardId: "treasure" },
|
||||
});
|
||||
if (!r.ok) throw new Error(r.error);
|
||||
r = applyCommand(r.state, "def", { type: "pass" });
|
||||
if (!r.ok) throw new Error(r.error);
|
||||
const drop = r.events.find((e) => e.type === "treasureDropped");
|
||||
expect(drop).toBeTruthy();
|
||||
if (drop?.type === "treasureDropped") expect(drop.onHomeOf).toBe("def");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user