Rev 22: the self-stack resolves — one wizard cannot bounce to himself
A democratic monster touching its own creator opens a stack where one player wears both hats, and doPass matched the attacker first: every pass "bounced the exchange to the defender" — the same wizard — forever. Room 928D livelocked there at seq 83; once the boot kick revived its pump, the automaton politely passed 389 times. At rev 22 a self-stack pass RESOLVES (the claw lands, the game moves on); older ledgers hold their recorded bounces and replay so. 928D itself was repaired by truncating the no-op run and lifting it to rev 22, verified clean and unstuck on a copy before installation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
4a75370de5
commit
7372bfc5e8
@@ -5071,7 +5071,13 @@ function doPass(prev: GameState, playerId: PlayerId): CommandResult {
|
|||||||
const state = clone(prev);
|
const state = clone(prev);
|
||||||
const stack = state.stack!;
|
const stack = state.stack!;
|
||||||
const events: GameEvent[] = [];
|
const events: GameEvent[] = [];
|
||||||
if (playerId === stack.attackerId) {
|
// A self-stack (a democratic monster touching its own creator) has one
|
||||||
|
// party wearing both hats: their pass RESOLVES — bouncing it "to the
|
||||||
|
// defender" would hand the exchange back to themselves forever
|
||||||
|
// (rules rev 22; older ledgers hold those no-op bounces and replay so).
|
||||||
|
const selfStack =
|
||||||
|
stack.attackerId === stack.defenderId && (state.config.deckRev ?? 1) >= 22;
|
||||||
|
if (playerId === stack.attackerId && !selfStack) {
|
||||||
// The attacker declining to answer bounces the exchange back so the
|
// The attacker declining to answer bounces the exchange back so the
|
||||||
// defender may stack further counters — but not past a total stop:
|
// defender may stack further counters — but not past a total stop:
|
||||||
// re-prompting then reads as "your shield failed" and goads the table
|
// re-prompting then reads as "your shield failed" and goads the table
|
||||||
|
|||||||
@@ -683,3 +683,40 @@ describe("collapsing walls crush monsters too (rules rev 21)", () => {
|
|||||||
expect(state.creatures.find((c) => c.id === "tr1")!.damage).toBe(0);
|
expect(state.creatures.find((c) => c.id === "tr1")!.damage).toBe(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("the self-stack resolves on a pass (rules rev 22)", () => {
|
||||||
|
function selfTouch(deckRev: number) {
|
||||||
|
let { state } = createGame({ playerIds: ["a", "b"], seed: 42, sets: ["basic", "expansion1"], deckRev });
|
||||||
|
state = toRound2(state);
|
||||||
|
const creator = activePlayer(state);
|
||||||
|
state.creatures.push({
|
||||||
|
id: "dm1", kind: "democratic-monster", controllerId: creator.id,
|
||||||
|
position: { x: creator.position.x - 1, y: creator.position.y },
|
||||||
|
damage: 0, maxDamage: 5, movesPerTurn: 3, movementUsed: 0,
|
||||||
|
attackUsed: false, justCreated: false,
|
||||||
|
wallPassesPerTurn: 0, wallPassUsed: 0, scorchedThisTurn: [],
|
||||||
|
});
|
||||||
|
const r = applyCommand(state, creator.id, { type: "moveCreature", creatureId: "dm1", direction: "E" });
|
||||||
|
if (!r.ok) throw new Error(r.error);
|
||||||
|
return { state: r.state, creator: creator.id };
|
||||||
|
}
|
||||||
|
|
||||||
|
it("rev 22: passing your own monster's touch takes the claw and moves on", () => {
|
||||||
|
const { state, creator } = selfTouch(22);
|
||||||
|
if (!state.stack) return; // a wall between: the touch never opened
|
||||||
|
expect(state.stack.attackerId).toBe(creator);
|
||||||
|
expect(state.stack.defenderId).toBe(creator);
|
||||||
|
const r = applyCommand(state, creator, { type: "pass" });
|
||||||
|
if (!r.ok) throw new Error(r.error);
|
||||||
|
expect(r.state.stack).toBeNull();
|
||||||
|
expect(r.state.players.find((p) => p.id === creator)!.life).toBe(13);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rev 21 keeps its recorded bounce, no-op as it was", () => {
|
||||||
|
const { state, creator } = selfTouch(21);
|
||||||
|
if (!state.stack) return;
|
||||||
|
const r = applyCommand(state, creator, { type: "pass" });
|
||||||
|
if (!r.ok) throw new Error(r.error);
|
||||||
|
expect(r.state.stack).not.toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ export interface Room {
|
|||||||
const rooms = new Map<string, Room>();
|
const rooms = new Map<string, Room>();
|
||||||
|
|
||||||
/** Rules revision new games are dealt under (stored games keep their own). */
|
/** Rules revision new games are dealt under (stored games keep their own). */
|
||||||
const RULES_REV = 21;
|
const RULES_REV = 22;
|
||||||
|
|
||||||
const ROOM_CODE_ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
|
const ROOM_CODE_ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
|
||||||
|
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ class LocalGame {
|
|||||||
seed,
|
seed,
|
||||||
sets: expansion ? ["basic", "expansion1"] : ["basic"],
|
sets: expansion ? ["basic", "expansion1"] : ["basic"],
|
||||||
...(colors ? { colors } : {}),
|
...(colors ? { colors } : {}),
|
||||||
deckRev: 21,
|
deckRev: 22,
|
||||||
};
|
};
|
||||||
const { state, events } = createGame(config);
|
const { state, events } = createGame(config);
|
||||||
for (const e of events) {
|
for (const e of events) {
|
||||||
|
|||||||
Reference in New Issue
Block a user