Files
systemic-game-design-course/tools/mutants.json
T

132 lines
6.3 KiB
JSON

[
{
"name": "End of Phase: Aura Update before the Death Creation Step",
"file": "src/engine/queue.ts",
"find": "export const END_OF_PHASE: readonly EndOfPhaseStep[] = [\"deathCreation\", \"auraUpdate\"];",
"replace": "export const END_OF_PHASE: readonly EndOfPhaseStep[] = [\"auraUpdate\", \"deathCreation\"];",
"expect": [
"test_04_simultaneous_death_not_doubled",
"test_04_aura_timing_order",
"test_10_level_3_solution",
"test_10_level_3_naive_would_differ"
]
},
{
"name": "Turn: no parse between movement and conversion",
"file": "src/engine/puzzle/turn.ts",
"find": "export const TURN: readonly Phase[] = [\"movement\", \"parse\", \"conversion\", \"parse\", \"hazards\", \"win\", \"parse\"];",
"replace": "export const TURN: readonly Phase[] = [\"movement\", \"conversion\", \"parse\", \"hazards\", \"win\", \"parse\"];",
"expect": [
"test_09_mid_turn_conversion",
"test_10_level_5_solution"
]
},
{
"name": "Turn: win check before hazards",
"file": "src/engine/puzzle/turn.ts",
"find": "export const TURN: readonly Phase[] = [\"movement\", \"parse\", \"conversion\", \"parse\", \"hazards\", \"win\", \"parse\"];",
"replace": "export const TURN: readonly Phase[] = [\"movement\", \"parse\", \"conversion\", \"parse\", \"win\", \"hazards\", \"parse\"];",
"expect": [
"test_09_defeat_beats_win",
"test_10_level_2_solution"
]
},
{
"name": "Turn: no parse between conversion and hazards",
"file": "src/engine/puzzle/turn.ts",
"find": "export const TURN: readonly Phase[] = [\"movement\", \"parse\", \"conversion\", \"parse\", \"hazards\", \"win\", \"parse\"];",
"replace": "export const TURN: readonly Phase[] = [\"movement\", \"parse\", \"conversion\", \"hazards\", \"win\", \"parse\"];",
"expect": [
"test_09_conversion_reparses_before_hazards"
]
},
{
"name": "Turn: hazards before conversion",
"file": "src/engine/puzzle/turn.ts",
"find": "export const TURN: readonly Phase[] = [\"movement\", \"parse\", \"conversion\", \"parse\", \"hazards\", \"win\", \"parse\"];",
"replace": "export const TURN: readonly Phase[] = [\"movement\", \"parse\", \"hazards\", \"parse\", \"conversion\", \"win\", \"parse\"];",
"expect": [
"test_09_conversion_before_hazards",
"test_09_conversion_reparses_before_hazards",
"test_09_undo_restores_everything"
]
},
{
"name": "Turn: no last parse",
"file": "src/engine/puzzle/turn.ts",
"find": "export const TURN: readonly Phase[] = [\"movement\", \"parse\", \"conversion\", \"parse\", \"hazards\", \"win\", \"parse\"];",
"replace": "export const TURN: readonly Phase[] = [\"movement\", \"parse\", \"conversion\", \"parse\", \"hazards\", \"win\"];",
"expect": [
"test_09_last_parse_matches_board"
]
},
{
"name": "Hazards in reverse order",
"file": "src/content/hazards.ts",
"find": "return [sink, hotMelt, defeat];",
"replace": "return [defeat, hotMelt, sink];",
"expect": [
"test_09_hazard_order_melt_before_defeat",
"test_09_hazard_order_sink_before_melt",
"test_10_level_6_solution",
"test_11_condition_rechecked_between_hazards"
]
},
{
"name": "Auras read live from the board, no snapshot",
"file": "src/engine/world.ts",
"find": " activeAuras(): readonly ActiveAura[] {\n return this.auras;\n }",
"replace": " activeAuras(): readonly ActiveAura[] {\n // MUTANT: every minion in play now, and the rule auras, read at each use.\n return [\n ...this.minionsInPlay().flatMap((id) => this.card(this.cardName(id)).auras.map((aura) => ({ owner: this.ownerOf(id), source: id, aura }))),\n ...this.rules.flatMap((aura) => ([0, 1] as const).map((owner) => ({ owner, aura }))),\n ];\n }",
"expect": [
"test_05_own_aura_misses_own_battlecry",
"test_10_level_3_naive_would_differ"
]
},
{
"name": "Hazards: DEFEAT before HOT/MELT",
"file": "src/content/hazards.ts",
"find": "return [sink, hotMelt, defeat];",
"replace": "return [sink, defeat, hotMelt];",
"expect": [
"test_09_hazard_order_melt_before_defeat",
"test_10_level_6_solution"
]
},
{
"name": "Damage: Armor before redirect",
"file": "src/engine/queue.ts",
"find": "export const DAMAGE_STEPS: readonly DamageStep[] = [\"redirect\", \"armor\"];",
"replace": "export const DAMAGE_STEPS: readonly DamageStep[] = [\"armor\", \"redirect\"];",
"expect": [
"test_04_redirect_before_armor"
]
},
{
"name": "Conditions decided at parse time",
"file": "src/engine/puzzle/level.ts",
"find": " this.compiled = parse(this.board, this.lexicon);\n this.active = resolve(this.compiled);\n }",
"replace": " this.compiled = parse(this.board, this.lexicon);\n this.active = resolve(this.compiled);\n // MUTANT: decide every condition now, for every object, and answer from that table until the next parse.\n const self = this as unknown as { live?: ConditionChecks; conditions: ConditionChecks };\n const live = (self.live ??= self.conditions);\n const decided = new Map<string, boolean>();\n const key = (word: string, id: ObjectId, nouns: readonly string[]) => `${word} ${nouns.join(\" \")} ${id}`;\n for (const o of this.board.objects()) {\n for (const r of this.active) for (const c of r.conditions) decided.set(key(c.word, o.id, c.nouns), live[c.word](this, o.id, c.nouns));\n }\n self.conditions = Object.fromEntries(\n Object.keys(live).map((word) => [word, (_: Level, id: ObjectId, nouns: readonly string[]) => decided.get(key(word, id, nouns)) ?? false]),\n );\n }",
"expect": [
"test_11_condition_rechecked_between_hazards"
]
},
{
"name": "Combat: no defender priority",
"file": "src/engine/queue.ts",
"find": " queue.sort((a, b) => b[4] - a[4]);\n",
"replace": " // MUTANT: no sort; the Queue keeps the order it was filled in.\n",
"expect": [
"test_12_defender_triggers_first"
]
},
{
"name": "SpellGrid: a summoned minion without a noun enters the grid",
"file": "src/engine/spellgrid.ts",
"find": " if (this.ids.has(event.id) || this.world.card(card).def.noun === undefined) continue;\n",
"replace": " if (this.ids.has(event.id)) continue;\n",
"expect": [
"test_10_summon_without_noun_stays_off_grid"
]
}
]