From 492b217f57fe923f761c39505d92bd9b4a9fa623 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sun, 6 Sep 2026 11:49:56 -0700 Subject: [PATCH 01/17] refactor: separate Fifty-Three Days from Scared Sacred expansion --- .../sets/fifty-three-days}/cards/README.md | 0 .../sets/fifty-three-days}/cards/agendas/g01.json | 0 .../sets/fifty-three-days}/cards/agendas/g02.json | 0 .../sets/fifty-three-days}/cards/agendas/g03.json | 0 .../sets/fifty-three-days}/cards/agendas/g04.json | 0 .../sets/fifty-three-days}/cards/agendas/g05.json | 0 .../sets/fifty-three-days}/cards/agendas/g06.json | 0 .../sets/fifty-three-days}/cards/arcana/a00.json | 0 .../sets/fifty-three-days}/cards/arcana/a02.json | 0 .../sets/fifty-three-days}/cards/arcana/a08.json | 0 .../sets/fifty-three-days}/cards/arcana/a09.json | 0 .../sets/fifty-three-days}/cards/arcana/a10.json | 0 .../sets/fifty-three-days}/cards/arcana/a12.json | 0 .../sets/fifty-three-days}/cards/arcana/a13.json | 0 .../sets/fifty-three-days}/cards/arcana/a16.json | 0 .../sets/fifty-three-days}/cards/arcana/a20.json | 0 .../sets/fifty-three-days}/cards/index.json | 0 .../sets/fifty-three-days}/cards/machine/m01.json | 0 .../sets/fifty-three-days}/cards/machine/m02.json | 0 .../sets/fifty-three-days}/cards/machine/m03.json | 0 .../sets/fifty-three-days}/cards/machine/m04.json | 0 .../sets/fifty-three-days}/cards/machine/m05.json | 0 .../sets/fifty-three-days}/cards/machine/m06.json | 0 .../sets/fifty-three-days}/cards/machine/m07.json | 0 .../sets/fifty-three-days}/cards/machine/m08.json | 0 .../sets/fifty-three-days}/cards/machine/m09.json | 0 .../sets/fifty-three-days}/cards/machine/m10.json | 0 .../sets/fifty-three-days}/cards/machine/m11.json | 0 .../sets/fifty-three-days}/cards/machine/m12.json | 0 .../sets/fifty-three-days}/cards/machine/m13.json | 0 .../sets/fifty-three-days}/cards/machine/m14.json | 0 .../sets/fifty-three-days}/cards/machine/m15.json | 0 .../sets/fifty-three-days}/cards/machine/m16.json | 0 .../sets/fifty-three-days}/cards/machine/m17.json | 0 .../sets/fifty-three-days}/cards/machine/m18.json | 0 .../sets/fifty-three-days}/cards/machine/m19.json | 0 .../sets/fifty-three-days}/cards/machine/m20.json | 0 .../sets/fifty-three-days}/cards/machine/m21.json | 0 .../sets/fifty-three-days}/cards/machine/m22.json | 0 .../sets/fifty-three-days}/cards/machine/m23.json | 0 .../sets/fifty-three-days}/cards/machine/m24.json | 0 .../sets/fifty-three-days}/cards/machine/m25.json | 0 .../sets/fifty-three-days}/cards/machine/m26.json | 0 .../sets/fifty-three-days}/cards/machine/m27.json | 0 .../sets/fifty-three-days}/cards/machine/m28.json | 0 .../sets/fifty-three-days}/cards/machine/m29.json | 0 .../sets/fifty-three-days}/cards/machine/m30.json | 0 .../sets/fifty-three-days}/cards/machine/m31.json | 0 .../sets/fifty-three-days}/cards/machine/m32.json | 0 .../sets/fifty-three-days}/cards/machine/m33.json | 0 .../sets/fifty-three-days}/cards/machine/m34.json | 0 .../sets/fifty-three-days}/cards/machine/m35.json | 0 .../sets/fifty-three-days}/cards/machine/m36.json | 0 .../sets/fifty-three-days}/cards/machine/m37.json | 0 .../sets/fifty-three-days}/cards/machine/m38.json | 0 .../sets/fifty-three-days}/cards/machine/m39.json | 0 .../sets/fifty-three-days}/cards/machine/m40.json | 0 .../sets/fifty-three-days}/cards/reserve/k01.json | 0 .../sets/fifty-three-days}/cards/reserve/k02.json | 0 .../sets/fifty-three-days}/cards/reserve/k03.json | 0 .../sets/fifty-three-days}/cards/reserve/k04.json | 0 .../sets/fifty-three-days}/cards/reserve/k05.json | 0 .../sets/fifty-three-days}/cards/reserve/k06.json | 0 .../sets/fifty-three-days}/cards/reserve/k07.json | 0 .../sets/fifty-three-days}/cards/reserve/k08.json | 0 .../sets/fifty-three-days}/cards/reserve/k09.json | 0 .../sets/fifty-three-days}/cards/reserve/k10.json | 0 .../sets/fifty-three-days}/cards/reserve/k11.json | 0 .../sets/fifty-three-days}/cards/reserve/k12.json | 0 .../sets/fifty-three-days}/cards/response/r01.json | 0 .../sets/fifty-three-days}/cards/response/r02.json | 0 .../sets/fifty-three-days}/cards/response/r03.json | 0 .../sets/fifty-three-days}/cards/response/r04.json | 0 .../sets/fifty-three-days}/cards/response/r05.json | 0 .../sets/fifty-three-days}/cards/response/r06.json | 0 .../sets/fifty-three-days}/cards/response/r07.json | 0 .../sets/fifty-three-days}/cards/response/r08.json | 0 .../sets/fifty-three-days}/cards/response/r09.json | 0 .../sets/fifty-three-days}/cards/response/r10.json | 0 .../sets/fifty-three-days}/cards/response/r11.json | 0 .../sets/fifty-three-days}/cards/response/r12.json | 0 .../sets/fifty-three-days}/cards/response/r13.json | 0 .../sets/fifty-three-days}/cards/response/r14.json | 0 .../sets/fifty-three-days}/cards/response/r15.json | 0 .../sets/fifty-three-days}/cards/response/r16.json | 0 .../sets/fifty-three-days}/cards/response/r17.json | 0 .../sets/fifty-three-days}/cards/setup/x1.json | 0 .../sets/fifty-three-days}/cards/setup/x2.json | 0 .../sets/fifty-three-days}/cards/setup/x3.json | 0 .../sets/fifty-three-days}/decks/v01_deck.md | 0 .../sets/fifty-three-days}/decks/weimar_53_days_v01.md | 0 91 files changed, 0 insertions(+), 0 deletions(-) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/README.md (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/agendas/g01.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/agendas/g02.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/agendas/g03.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/agendas/g04.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/agendas/g05.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/agendas/g06.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/arcana/a00.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/arcana/a02.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/arcana/a08.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/arcana/a09.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/arcana/a10.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/arcana/a12.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/arcana/a13.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/arcana/a16.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/arcana/a20.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/index.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/machine/m01.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/machine/m02.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/machine/m03.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/machine/m04.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/machine/m05.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/machine/m06.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/machine/m07.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/machine/m08.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/machine/m09.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/machine/m10.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/machine/m11.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/machine/m12.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/machine/m13.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/machine/m14.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/machine/m15.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/machine/m16.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/machine/m17.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/machine/m18.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/machine/m19.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/machine/m20.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/machine/m21.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/machine/m22.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/machine/m23.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/machine/m24.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/machine/m25.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/machine/m26.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/machine/m27.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/machine/m28.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/machine/m29.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/machine/m30.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/machine/m31.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/machine/m32.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/machine/m33.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/machine/m34.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/machine/m35.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/machine/m36.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/machine/m37.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/machine/m38.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/machine/m39.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/machine/m40.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/reserve/k01.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/reserve/k02.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/reserve/k03.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/reserve/k04.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/reserve/k05.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/reserve/k06.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/reserve/k07.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/reserve/k08.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/reserve/k09.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/reserve/k10.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/reserve/k11.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/reserve/k12.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/response/r01.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/response/r02.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/response/r03.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/response/r04.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/response/r05.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/response/r06.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/response/r07.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/response/r08.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/response/r09.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/response/r10.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/response/r11.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/response/r12.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/response/r13.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/response/r14.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/response/r15.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/response/r16.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/response/r17.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/setup/x1.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/setup/x2.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/cards/setup/x3.json (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/decks/v01_deck.md (100%) rename {expansions/scared-sacred => base-game/sets/fifty-three-days}/decks/weimar_53_days_v01.md (100%) diff --git a/expansions/scared-sacred/cards/README.md b/base-game/sets/fifty-three-days/cards/README.md similarity index 100% rename from expansions/scared-sacred/cards/README.md rename to base-game/sets/fifty-three-days/cards/README.md diff --git a/expansions/scared-sacred/cards/agendas/g01.json b/base-game/sets/fifty-three-days/cards/agendas/g01.json similarity index 100% rename from expansions/scared-sacred/cards/agendas/g01.json rename to base-game/sets/fifty-three-days/cards/agendas/g01.json diff --git a/expansions/scared-sacred/cards/agendas/g02.json b/base-game/sets/fifty-three-days/cards/agendas/g02.json similarity index 100% rename from expansions/scared-sacred/cards/agendas/g02.json rename to base-game/sets/fifty-three-days/cards/agendas/g02.json diff --git a/expansions/scared-sacred/cards/agendas/g03.json b/base-game/sets/fifty-three-days/cards/agendas/g03.json similarity index 100% rename from expansions/scared-sacred/cards/agendas/g03.json rename to base-game/sets/fifty-three-days/cards/agendas/g03.json diff --git a/expansions/scared-sacred/cards/agendas/g04.json b/base-game/sets/fifty-three-days/cards/agendas/g04.json similarity index 100% rename from expansions/scared-sacred/cards/agendas/g04.json rename to base-game/sets/fifty-three-days/cards/agendas/g04.json diff --git a/expansions/scared-sacred/cards/agendas/g05.json b/base-game/sets/fifty-three-days/cards/agendas/g05.json similarity index 100% rename from expansions/scared-sacred/cards/agendas/g05.json rename to base-game/sets/fifty-three-days/cards/agendas/g05.json diff --git a/expansions/scared-sacred/cards/agendas/g06.json b/base-game/sets/fifty-three-days/cards/agendas/g06.json similarity index 100% rename from expansions/scared-sacred/cards/agendas/g06.json rename to base-game/sets/fifty-three-days/cards/agendas/g06.json diff --git a/expansions/scared-sacred/cards/arcana/a00.json b/base-game/sets/fifty-three-days/cards/arcana/a00.json similarity index 100% rename from expansions/scared-sacred/cards/arcana/a00.json rename to base-game/sets/fifty-three-days/cards/arcana/a00.json diff --git a/expansions/scared-sacred/cards/arcana/a02.json b/base-game/sets/fifty-three-days/cards/arcana/a02.json similarity index 100% rename from expansions/scared-sacred/cards/arcana/a02.json rename to base-game/sets/fifty-three-days/cards/arcana/a02.json diff --git a/expansions/scared-sacred/cards/arcana/a08.json b/base-game/sets/fifty-three-days/cards/arcana/a08.json similarity index 100% rename from expansions/scared-sacred/cards/arcana/a08.json rename to base-game/sets/fifty-three-days/cards/arcana/a08.json diff --git a/expansions/scared-sacred/cards/arcana/a09.json b/base-game/sets/fifty-three-days/cards/arcana/a09.json similarity index 100% rename from expansions/scared-sacred/cards/arcana/a09.json rename to base-game/sets/fifty-three-days/cards/arcana/a09.json diff --git a/expansions/scared-sacred/cards/arcana/a10.json b/base-game/sets/fifty-three-days/cards/arcana/a10.json similarity index 100% rename from expansions/scared-sacred/cards/arcana/a10.json rename to base-game/sets/fifty-three-days/cards/arcana/a10.json diff --git a/expansions/scared-sacred/cards/arcana/a12.json b/base-game/sets/fifty-three-days/cards/arcana/a12.json similarity index 100% rename from expansions/scared-sacred/cards/arcana/a12.json rename to base-game/sets/fifty-three-days/cards/arcana/a12.json diff --git a/expansions/scared-sacred/cards/arcana/a13.json b/base-game/sets/fifty-three-days/cards/arcana/a13.json similarity index 100% rename from expansions/scared-sacred/cards/arcana/a13.json rename to base-game/sets/fifty-three-days/cards/arcana/a13.json diff --git a/expansions/scared-sacred/cards/arcana/a16.json b/base-game/sets/fifty-three-days/cards/arcana/a16.json similarity index 100% rename from expansions/scared-sacred/cards/arcana/a16.json rename to base-game/sets/fifty-three-days/cards/arcana/a16.json diff --git a/expansions/scared-sacred/cards/arcana/a20.json b/base-game/sets/fifty-three-days/cards/arcana/a20.json similarity index 100% rename from expansions/scared-sacred/cards/arcana/a20.json rename to base-game/sets/fifty-three-days/cards/arcana/a20.json diff --git a/expansions/scared-sacred/cards/index.json b/base-game/sets/fifty-three-days/cards/index.json similarity index 100% rename from expansions/scared-sacred/cards/index.json rename to base-game/sets/fifty-three-days/cards/index.json diff --git a/expansions/scared-sacred/cards/machine/m01.json b/base-game/sets/fifty-three-days/cards/machine/m01.json similarity index 100% rename from expansions/scared-sacred/cards/machine/m01.json rename to base-game/sets/fifty-three-days/cards/machine/m01.json diff --git a/expansions/scared-sacred/cards/machine/m02.json b/base-game/sets/fifty-three-days/cards/machine/m02.json similarity index 100% rename from expansions/scared-sacred/cards/machine/m02.json rename to base-game/sets/fifty-three-days/cards/machine/m02.json diff --git a/expansions/scared-sacred/cards/machine/m03.json b/base-game/sets/fifty-three-days/cards/machine/m03.json similarity index 100% rename from expansions/scared-sacred/cards/machine/m03.json rename to base-game/sets/fifty-three-days/cards/machine/m03.json diff --git a/expansions/scared-sacred/cards/machine/m04.json b/base-game/sets/fifty-three-days/cards/machine/m04.json similarity index 100% rename from expansions/scared-sacred/cards/machine/m04.json rename to base-game/sets/fifty-three-days/cards/machine/m04.json diff --git a/expansions/scared-sacred/cards/machine/m05.json b/base-game/sets/fifty-three-days/cards/machine/m05.json similarity index 100% rename from expansions/scared-sacred/cards/machine/m05.json rename to base-game/sets/fifty-three-days/cards/machine/m05.json diff --git a/expansions/scared-sacred/cards/machine/m06.json b/base-game/sets/fifty-three-days/cards/machine/m06.json similarity index 100% rename from expansions/scared-sacred/cards/machine/m06.json rename to base-game/sets/fifty-three-days/cards/machine/m06.json diff --git a/expansions/scared-sacred/cards/machine/m07.json b/base-game/sets/fifty-three-days/cards/machine/m07.json similarity index 100% rename from expansions/scared-sacred/cards/machine/m07.json rename to base-game/sets/fifty-three-days/cards/machine/m07.json diff --git a/expansions/scared-sacred/cards/machine/m08.json b/base-game/sets/fifty-three-days/cards/machine/m08.json similarity index 100% rename from expansions/scared-sacred/cards/machine/m08.json rename to base-game/sets/fifty-three-days/cards/machine/m08.json diff --git a/expansions/scared-sacred/cards/machine/m09.json b/base-game/sets/fifty-three-days/cards/machine/m09.json similarity index 100% rename from expansions/scared-sacred/cards/machine/m09.json rename to base-game/sets/fifty-three-days/cards/machine/m09.json diff --git a/expansions/scared-sacred/cards/machine/m10.json b/base-game/sets/fifty-three-days/cards/machine/m10.json similarity index 100% rename from expansions/scared-sacred/cards/machine/m10.json rename to base-game/sets/fifty-three-days/cards/machine/m10.json diff --git a/expansions/scared-sacred/cards/machine/m11.json b/base-game/sets/fifty-three-days/cards/machine/m11.json similarity index 100% rename from expansions/scared-sacred/cards/machine/m11.json rename to base-game/sets/fifty-three-days/cards/machine/m11.json diff --git a/expansions/scared-sacred/cards/machine/m12.json b/base-game/sets/fifty-three-days/cards/machine/m12.json similarity index 100% rename from expansions/scared-sacred/cards/machine/m12.json rename to base-game/sets/fifty-three-days/cards/machine/m12.json diff --git a/expansions/scared-sacred/cards/machine/m13.json b/base-game/sets/fifty-three-days/cards/machine/m13.json similarity index 100% rename from expansions/scared-sacred/cards/machine/m13.json rename to base-game/sets/fifty-three-days/cards/machine/m13.json diff --git a/expansions/scared-sacred/cards/machine/m14.json b/base-game/sets/fifty-three-days/cards/machine/m14.json similarity index 100% rename from expansions/scared-sacred/cards/machine/m14.json rename to base-game/sets/fifty-three-days/cards/machine/m14.json diff --git a/expansions/scared-sacred/cards/machine/m15.json b/base-game/sets/fifty-three-days/cards/machine/m15.json similarity index 100% rename from expansions/scared-sacred/cards/machine/m15.json rename to base-game/sets/fifty-three-days/cards/machine/m15.json diff --git a/expansions/scared-sacred/cards/machine/m16.json b/base-game/sets/fifty-three-days/cards/machine/m16.json similarity index 100% rename from expansions/scared-sacred/cards/machine/m16.json rename to base-game/sets/fifty-three-days/cards/machine/m16.json diff --git a/expansions/scared-sacred/cards/machine/m17.json b/base-game/sets/fifty-three-days/cards/machine/m17.json similarity index 100% rename from expansions/scared-sacred/cards/machine/m17.json rename to base-game/sets/fifty-three-days/cards/machine/m17.json diff --git a/expansions/scared-sacred/cards/machine/m18.json b/base-game/sets/fifty-three-days/cards/machine/m18.json similarity index 100% rename from expansions/scared-sacred/cards/machine/m18.json rename to base-game/sets/fifty-three-days/cards/machine/m18.json diff --git a/expansions/scared-sacred/cards/machine/m19.json b/base-game/sets/fifty-three-days/cards/machine/m19.json similarity index 100% rename from expansions/scared-sacred/cards/machine/m19.json rename to base-game/sets/fifty-three-days/cards/machine/m19.json diff --git a/expansions/scared-sacred/cards/machine/m20.json b/base-game/sets/fifty-three-days/cards/machine/m20.json similarity index 100% rename from expansions/scared-sacred/cards/machine/m20.json rename to base-game/sets/fifty-three-days/cards/machine/m20.json diff --git a/expansions/scared-sacred/cards/machine/m21.json b/base-game/sets/fifty-three-days/cards/machine/m21.json similarity index 100% rename from expansions/scared-sacred/cards/machine/m21.json rename to base-game/sets/fifty-three-days/cards/machine/m21.json diff --git a/expansions/scared-sacred/cards/machine/m22.json b/base-game/sets/fifty-three-days/cards/machine/m22.json similarity index 100% rename from expansions/scared-sacred/cards/machine/m22.json rename to base-game/sets/fifty-three-days/cards/machine/m22.json diff --git a/expansions/scared-sacred/cards/machine/m23.json b/base-game/sets/fifty-three-days/cards/machine/m23.json similarity index 100% rename from expansions/scared-sacred/cards/machine/m23.json rename to base-game/sets/fifty-three-days/cards/machine/m23.json diff --git a/expansions/scared-sacred/cards/machine/m24.json b/base-game/sets/fifty-three-days/cards/machine/m24.json similarity index 100% rename from expansions/scared-sacred/cards/machine/m24.json rename to base-game/sets/fifty-three-days/cards/machine/m24.json diff --git a/expansions/scared-sacred/cards/machine/m25.json b/base-game/sets/fifty-three-days/cards/machine/m25.json similarity index 100% rename from expansions/scared-sacred/cards/machine/m25.json rename to base-game/sets/fifty-three-days/cards/machine/m25.json diff --git a/expansions/scared-sacred/cards/machine/m26.json b/base-game/sets/fifty-three-days/cards/machine/m26.json similarity index 100% rename from expansions/scared-sacred/cards/machine/m26.json rename to base-game/sets/fifty-three-days/cards/machine/m26.json diff --git a/expansions/scared-sacred/cards/machine/m27.json b/base-game/sets/fifty-three-days/cards/machine/m27.json similarity index 100% rename from expansions/scared-sacred/cards/machine/m27.json rename to base-game/sets/fifty-three-days/cards/machine/m27.json diff --git a/expansions/scared-sacred/cards/machine/m28.json b/base-game/sets/fifty-three-days/cards/machine/m28.json similarity index 100% rename from expansions/scared-sacred/cards/machine/m28.json rename to base-game/sets/fifty-three-days/cards/machine/m28.json diff --git a/expansions/scared-sacred/cards/machine/m29.json b/base-game/sets/fifty-three-days/cards/machine/m29.json similarity index 100% rename from expansions/scared-sacred/cards/machine/m29.json rename to base-game/sets/fifty-three-days/cards/machine/m29.json diff --git a/expansions/scared-sacred/cards/machine/m30.json b/base-game/sets/fifty-three-days/cards/machine/m30.json similarity index 100% rename from expansions/scared-sacred/cards/machine/m30.json rename to base-game/sets/fifty-three-days/cards/machine/m30.json diff --git a/expansions/scared-sacred/cards/machine/m31.json b/base-game/sets/fifty-three-days/cards/machine/m31.json similarity index 100% rename from expansions/scared-sacred/cards/machine/m31.json rename to base-game/sets/fifty-three-days/cards/machine/m31.json diff --git a/expansions/scared-sacred/cards/machine/m32.json b/base-game/sets/fifty-three-days/cards/machine/m32.json similarity index 100% rename from expansions/scared-sacred/cards/machine/m32.json rename to base-game/sets/fifty-three-days/cards/machine/m32.json diff --git a/expansions/scared-sacred/cards/machine/m33.json b/base-game/sets/fifty-three-days/cards/machine/m33.json similarity index 100% rename from expansions/scared-sacred/cards/machine/m33.json rename to base-game/sets/fifty-three-days/cards/machine/m33.json diff --git a/expansions/scared-sacred/cards/machine/m34.json b/base-game/sets/fifty-three-days/cards/machine/m34.json similarity index 100% rename from expansions/scared-sacred/cards/machine/m34.json rename to base-game/sets/fifty-three-days/cards/machine/m34.json diff --git a/expansions/scared-sacred/cards/machine/m35.json b/base-game/sets/fifty-three-days/cards/machine/m35.json similarity index 100% rename from expansions/scared-sacred/cards/machine/m35.json rename to base-game/sets/fifty-three-days/cards/machine/m35.json diff --git a/expansions/scared-sacred/cards/machine/m36.json b/base-game/sets/fifty-three-days/cards/machine/m36.json similarity index 100% rename from expansions/scared-sacred/cards/machine/m36.json rename to base-game/sets/fifty-three-days/cards/machine/m36.json diff --git a/expansions/scared-sacred/cards/machine/m37.json b/base-game/sets/fifty-three-days/cards/machine/m37.json similarity index 100% rename from expansions/scared-sacred/cards/machine/m37.json rename to base-game/sets/fifty-three-days/cards/machine/m37.json diff --git a/expansions/scared-sacred/cards/machine/m38.json b/base-game/sets/fifty-three-days/cards/machine/m38.json similarity index 100% rename from expansions/scared-sacred/cards/machine/m38.json rename to base-game/sets/fifty-three-days/cards/machine/m38.json diff --git a/expansions/scared-sacred/cards/machine/m39.json b/base-game/sets/fifty-three-days/cards/machine/m39.json similarity index 100% rename from expansions/scared-sacred/cards/machine/m39.json rename to base-game/sets/fifty-three-days/cards/machine/m39.json diff --git a/expansions/scared-sacred/cards/machine/m40.json b/base-game/sets/fifty-three-days/cards/machine/m40.json similarity index 100% rename from expansions/scared-sacred/cards/machine/m40.json rename to base-game/sets/fifty-three-days/cards/machine/m40.json diff --git a/expansions/scared-sacred/cards/reserve/k01.json b/base-game/sets/fifty-three-days/cards/reserve/k01.json similarity index 100% rename from expansions/scared-sacred/cards/reserve/k01.json rename to base-game/sets/fifty-three-days/cards/reserve/k01.json diff --git a/expansions/scared-sacred/cards/reserve/k02.json b/base-game/sets/fifty-three-days/cards/reserve/k02.json similarity index 100% rename from expansions/scared-sacred/cards/reserve/k02.json rename to base-game/sets/fifty-three-days/cards/reserve/k02.json diff --git a/expansions/scared-sacred/cards/reserve/k03.json b/base-game/sets/fifty-three-days/cards/reserve/k03.json similarity index 100% rename from expansions/scared-sacred/cards/reserve/k03.json rename to base-game/sets/fifty-three-days/cards/reserve/k03.json diff --git a/expansions/scared-sacred/cards/reserve/k04.json b/base-game/sets/fifty-three-days/cards/reserve/k04.json similarity index 100% rename from expansions/scared-sacred/cards/reserve/k04.json rename to base-game/sets/fifty-three-days/cards/reserve/k04.json diff --git a/expansions/scared-sacred/cards/reserve/k05.json b/base-game/sets/fifty-three-days/cards/reserve/k05.json similarity index 100% rename from expansions/scared-sacred/cards/reserve/k05.json rename to base-game/sets/fifty-three-days/cards/reserve/k05.json diff --git a/expansions/scared-sacred/cards/reserve/k06.json b/base-game/sets/fifty-three-days/cards/reserve/k06.json similarity index 100% rename from expansions/scared-sacred/cards/reserve/k06.json rename to base-game/sets/fifty-three-days/cards/reserve/k06.json diff --git a/expansions/scared-sacred/cards/reserve/k07.json b/base-game/sets/fifty-three-days/cards/reserve/k07.json similarity index 100% rename from expansions/scared-sacred/cards/reserve/k07.json rename to base-game/sets/fifty-three-days/cards/reserve/k07.json diff --git a/expansions/scared-sacred/cards/reserve/k08.json b/base-game/sets/fifty-three-days/cards/reserve/k08.json similarity index 100% rename from expansions/scared-sacred/cards/reserve/k08.json rename to base-game/sets/fifty-three-days/cards/reserve/k08.json diff --git a/expansions/scared-sacred/cards/reserve/k09.json b/base-game/sets/fifty-three-days/cards/reserve/k09.json similarity index 100% rename from expansions/scared-sacred/cards/reserve/k09.json rename to base-game/sets/fifty-three-days/cards/reserve/k09.json diff --git a/expansions/scared-sacred/cards/reserve/k10.json b/base-game/sets/fifty-three-days/cards/reserve/k10.json similarity index 100% rename from expansions/scared-sacred/cards/reserve/k10.json rename to base-game/sets/fifty-three-days/cards/reserve/k10.json diff --git a/expansions/scared-sacred/cards/reserve/k11.json b/base-game/sets/fifty-three-days/cards/reserve/k11.json similarity index 100% rename from expansions/scared-sacred/cards/reserve/k11.json rename to base-game/sets/fifty-three-days/cards/reserve/k11.json diff --git a/expansions/scared-sacred/cards/reserve/k12.json b/base-game/sets/fifty-three-days/cards/reserve/k12.json similarity index 100% rename from expansions/scared-sacred/cards/reserve/k12.json rename to base-game/sets/fifty-three-days/cards/reserve/k12.json diff --git a/expansions/scared-sacred/cards/response/r01.json b/base-game/sets/fifty-three-days/cards/response/r01.json similarity index 100% rename from expansions/scared-sacred/cards/response/r01.json rename to base-game/sets/fifty-three-days/cards/response/r01.json diff --git a/expansions/scared-sacred/cards/response/r02.json b/base-game/sets/fifty-three-days/cards/response/r02.json similarity index 100% rename from expansions/scared-sacred/cards/response/r02.json rename to base-game/sets/fifty-three-days/cards/response/r02.json diff --git a/expansions/scared-sacred/cards/response/r03.json b/base-game/sets/fifty-three-days/cards/response/r03.json similarity index 100% rename from expansions/scared-sacred/cards/response/r03.json rename to base-game/sets/fifty-three-days/cards/response/r03.json diff --git a/expansions/scared-sacred/cards/response/r04.json b/base-game/sets/fifty-three-days/cards/response/r04.json similarity index 100% rename from expansions/scared-sacred/cards/response/r04.json rename to base-game/sets/fifty-three-days/cards/response/r04.json diff --git a/expansions/scared-sacred/cards/response/r05.json b/base-game/sets/fifty-three-days/cards/response/r05.json similarity index 100% rename from expansions/scared-sacred/cards/response/r05.json rename to base-game/sets/fifty-three-days/cards/response/r05.json diff --git a/expansions/scared-sacred/cards/response/r06.json b/base-game/sets/fifty-three-days/cards/response/r06.json similarity index 100% rename from expansions/scared-sacred/cards/response/r06.json rename to base-game/sets/fifty-three-days/cards/response/r06.json diff --git a/expansions/scared-sacred/cards/response/r07.json b/base-game/sets/fifty-three-days/cards/response/r07.json similarity index 100% rename from expansions/scared-sacred/cards/response/r07.json rename to base-game/sets/fifty-three-days/cards/response/r07.json diff --git a/expansions/scared-sacred/cards/response/r08.json b/base-game/sets/fifty-three-days/cards/response/r08.json similarity index 100% rename from expansions/scared-sacred/cards/response/r08.json rename to base-game/sets/fifty-three-days/cards/response/r08.json diff --git a/expansions/scared-sacred/cards/response/r09.json b/base-game/sets/fifty-three-days/cards/response/r09.json similarity index 100% rename from expansions/scared-sacred/cards/response/r09.json rename to base-game/sets/fifty-three-days/cards/response/r09.json diff --git a/expansions/scared-sacred/cards/response/r10.json b/base-game/sets/fifty-three-days/cards/response/r10.json similarity index 100% rename from expansions/scared-sacred/cards/response/r10.json rename to base-game/sets/fifty-three-days/cards/response/r10.json diff --git a/expansions/scared-sacred/cards/response/r11.json b/base-game/sets/fifty-three-days/cards/response/r11.json similarity index 100% rename from expansions/scared-sacred/cards/response/r11.json rename to base-game/sets/fifty-three-days/cards/response/r11.json diff --git a/expansions/scared-sacred/cards/response/r12.json b/base-game/sets/fifty-three-days/cards/response/r12.json similarity index 100% rename from expansions/scared-sacred/cards/response/r12.json rename to base-game/sets/fifty-three-days/cards/response/r12.json diff --git a/expansions/scared-sacred/cards/response/r13.json b/base-game/sets/fifty-three-days/cards/response/r13.json similarity index 100% rename from expansions/scared-sacred/cards/response/r13.json rename to base-game/sets/fifty-three-days/cards/response/r13.json diff --git a/expansions/scared-sacred/cards/response/r14.json b/base-game/sets/fifty-three-days/cards/response/r14.json similarity index 100% rename from expansions/scared-sacred/cards/response/r14.json rename to base-game/sets/fifty-three-days/cards/response/r14.json diff --git a/expansions/scared-sacred/cards/response/r15.json b/base-game/sets/fifty-three-days/cards/response/r15.json similarity index 100% rename from expansions/scared-sacred/cards/response/r15.json rename to base-game/sets/fifty-three-days/cards/response/r15.json diff --git a/expansions/scared-sacred/cards/response/r16.json b/base-game/sets/fifty-three-days/cards/response/r16.json similarity index 100% rename from expansions/scared-sacred/cards/response/r16.json rename to base-game/sets/fifty-three-days/cards/response/r16.json diff --git a/expansions/scared-sacred/cards/response/r17.json b/base-game/sets/fifty-three-days/cards/response/r17.json similarity index 100% rename from expansions/scared-sacred/cards/response/r17.json rename to base-game/sets/fifty-three-days/cards/response/r17.json diff --git a/expansions/scared-sacred/cards/setup/x1.json b/base-game/sets/fifty-three-days/cards/setup/x1.json similarity index 100% rename from expansions/scared-sacred/cards/setup/x1.json rename to base-game/sets/fifty-three-days/cards/setup/x1.json diff --git a/expansions/scared-sacred/cards/setup/x2.json b/base-game/sets/fifty-three-days/cards/setup/x2.json similarity index 100% rename from expansions/scared-sacred/cards/setup/x2.json rename to base-game/sets/fifty-three-days/cards/setup/x2.json diff --git a/expansions/scared-sacred/cards/setup/x3.json b/base-game/sets/fifty-three-days/cards/setup/x3.json similarity index 100% rename from expansions/scared-sacred/cards/setup/x3.json rename to base-game/sets/fifty-three-days/cards/setup/x3.json diff --git a/expansions/scared-sacred/decks/v01_deck.md b/base-game/sets/fifty-three-days/decks/v01_deck.md similarity index 100% rename from expansions/scared-sacred/decks/v01_deck.md rename to base-game/sets/fifty-three-days/decks/v01_deck.md diff --git a/expansions/scared-sacred/decks/weimar_53_days_v01.md b/base-game/sets/fifty-three-days/decks/weimar_53_days_v01.md similarity index 100% rename from expansions/scared-sacred/decks/weimar_53_days_v01.md rename to base-game/sets/fifty-three-days/decks/weimar_53_days_v01.md From 901af00556b633e37ac063503785882295713ee3 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sun, 6 Sep 2026 11:51:26 -0700 Subject: [PATCH 02/17] docs: reconcile implemented game status and set boundaries --- README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index c32c3b1..e47afc1 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,10 @@ guarantees total conversion absent human play) / L3 TABLE (players; the only source of repair in the universe). - canon/ base canon v0.3 + Scared Sacred expansion canon v0.2 -- base-game/ play rules v0.3 (consolidated) + arcana v0.2 -- expansions/scared-sacred/ Expansion Set 1 (fundamentalism block) +- base-game/ play rules v0.3, arcana v0.2, executable engine data, and sets/fifty-three-days/ +- expansions/scared-sacred/ Expansion Set 1 boundary; its distinctive expansion content remains separate from Fifty-Three Days +- engine/ executable rules, Machine, inertial field, agendas/arcana, and balance harness +- render/ playable browser table over the real engine - funding/ GoFundMe entry - .agents/skills/ vendored from skill-lib (if present) @@ -18,6 +20,6 @@ VIRTUAL ONLY — no physical printing of any set, ever. The Litany (never satirical): I'm sorry. I forgive you. You are not alone. I love you. -Status: pre-playtest. All balance numbers conjectural. +Status: executable base ruleset and automated balance harness are implemented and test-backed; human playtest and store/mobile delivery remain pending. Some explicitly logged card effects remain unresolved and must not be represented as complete. -hmmm — fear and the holy are the same six letters. +hmmm — fear and the holy are the same six letters; the base game and its first expansion now also have separate addresses. From f33548806ce05870bd8059c4ba598caff6334cab Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sun, 6 Sep 2026 11:51:44 -0700 Subject: [PATCH 03/17] fix: export Fifty-Three Days into base-game set namespace --- engine/cards_export.py | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/engine/cards_export.py b/engine/cards_export.py index 41fdfd6..88f97b3 100644 --- a/engine/cards_export.py +++ b/engine/cards_export.py @@ -1,16 +1,17 @@ # ratios: loc_comments=38:26 imports_exports=5:1 calls_definitions=23:2 -"""cards_export — emit every card as a repo-resident JSON asset. +"""cards_export — emit every Fifty-Three Days card as a repo-resident JSON asset. Single source of truth stays weimar_data.py / arcana_agendas_v1.py; this -exporter renders each card into its own file so art lands beside data: -grok's m07.png drops next to m07.json and the render layer needs no -lookup table. Regenerate after any data change; hand-editing the JSON is -the anti-pattern (the export overwrites without mercy). +exporter renders the POLITICS base set into its own namespace so art lands +beside data without occupying the Scared Sacred expansion boundary. +Grok's m07.png drops next to m07.json and the render layer needs no lookup +table. Regenerate after any data change; hand-editing the JSON is the +anti-pattern (the export overwrites without mercy). Usage Guidance -------------- python3 cards_export.py [repo_root] # default .. -Writes expansions/scared-sacred/cards/{setup,machine,reserve,response, +Writes base-game/sets/fifty-three-days/cards/{setup,machine,reserve,response, arcana,agendas}/.json plus index.json. Each card carries an "art" block: {"file": ".png", "status": "awaiting"} until validated art exists beside it, then rerun flips status to "present". Prints counts; @@ -18,12 +19,13 @@ # === MODULE_BUILD === # id: cards_export_v01 -# purpose: cards as first-class repo files; art drop-in slots +# purpose: Fifty-Three Days cards as first-class repo files; art drop-in slots # surfaces: export(root) -> index dict; __main__ CLI -# boundaries: read-only over data modules; overwrites cards/ tree +# boundaries: read-only over data modules; overwrites only the base-game +# Fifty-Three Days cards tree; never writes an expansion namespace # tests: self-verifying counts on run (index vs files written) # rollout: run per data change; committed output is canon-derived -# rollback: delete cards/ tree; data modules unaffected +# rollback: delete generated cards/ tree; data modules unaffected # hmmm: art status flips by file presence, not by validation record -- # the validation ledger is a later, honest upgrade # === END MODULE_BUILD === @@ -37,8 +39,12 @@ from arcana_agendas_v1 import AGENDAS, ARCANA +def _cards_root(root): + return os.path.join(root, "base-game", "sets", "fifty-three-days", "cards") + + def _write(root, deck, ident, card): - d = os.path.join(root, "expansions", "scared-sacred", "cards", deck) + d = os.path.join(_cards_root(root), deck) os.makedirs(d, exist_ok=True) png = ident.lower() + ".png" card = dict(card) @@ -65,7 +71,7 @@ def export(root=".."): index["cards"].append(_write(root, "arcana", f"a{c['num']:02d}", c)) for i, c in enumerate(AGENDAS): index["cards"].append(_write(root, "agendas", f"g{i+1:02d}", c)) - d = os.path.join(root, "expansions", "scared-sacred", "cards") + d = _cards_root(root) with open(os.path.join(d, "index.json"), "w") as f: json.dump(index, f, indent=1) return index From 24736ba655bd33787f9d45427e080becce37c9f0 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sun, 6 Sep 2026 11:51:55 -0700 Subject: [PATCH 04/17] docs: establish Scared Sacred expansion boundary --- expansions/scared-sacred/README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 expansions/scared-sacred/README.md diff --git a/expansions/scared-sacred/README.md b/expansions/scared-sacred/README.md new file mode 100644 index 0000000..0604a88 --- /dev/null +++ b/expansions/scared-sacred/README.md @@ -0,0 +1,16 @@ +# SCARED SACRED — Expansion Set 1 + +This directory is reserved for the distinctive SCARED SACRED expansion. +POLITICS / FIFTY-THREE DAYS is base-game content and lives under +`base-game/sets/fifty-three-days/`. + +Authoritative expansion doctrine remains in +`canon/canon_v02_scared_sacred.md` until executable expansion content is +promoted here. Do not regenerate the Fifty-Three Days Machine, response, +reserve, agenda, or arcana corpus into this namespace. + +Implemented base-game engine code lives under `engine/`; this expansion +may consume those contracts without owning them. + +hmmm — an expansion directory is allowed to be mostly empty when the +alternative is lying about which game the cards belong to. From 3720f978ac1f67817e2d5ff69bd37b6a05257a72 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sun, 6 Sep 2026 11:53:29 -0700 Subject: [PATCH 05/17] fix: enforce arcana ownership and score Organizer by resolved R --- engine/arcana_agendas_v1.py | 42 +++++++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/engine/arcana_agendas_v1.py b/engine/arcana_agendas_v1.py index 2d9d513..eb8b3b2 100644 --- a/engine/arcana_agendas_v1.py +++ b/engine/arcana_agendas_v1.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=119:51 imports_exports=2:3 calls_definitions=45:5 +# ratios: loc_comments=130:54 imports_exports=2:4 calls_definitions=54:6 """arcana_agendas_v1 — secret agendas and the nine trumps. Build step 5c. AGENDAS: dealt one per player at setup, secret, verified at match end. @@ -11,11 +11,12 @@ Usage Guidance -------------- - from arcana_agendas_v1 import (deal_agendas, verify_agendas, - ARCANA, ArcanaModule) - deal_agendas(state, players=3, rng) + from arcana_agendas_v1 import (deal_agendas, deal_arcana, + verify_agendas, ARCANA, ArcanaModule) + deal_agendas(state, players=3, rng=rng) + deal_arcana(state, players=3, rng=rng) runner = MatchRunner(..., arcana=ArcanaModule()) - plays = TurnPlays(arcana={"name": "THE WAYSEER"}) # from your deal + plays = TurnPlays(arcana=state.tallies["arcana"][pid]) result.agenda_outcomes # per-player verdicts at match end Filibuster and the Witness Rule share the suspension primitive (GameState.suspend_beats): the world stopping is sequence-level plumbing, @@ -24,12 +25,13 @@ # === MODULE_BUILD === # id: arcana_agendas_v1 # purpose: non-alignment (secret agendas) and rule-class trumps -# surfaces: AGENDAS, deal_agendas, verify_agendas, ARCANA, ArcanaModule +# surfaces: AGENDAS, deal_agendas, deal_arcana, verify_agendas, ARCANA, +# ArcanaModule # boundaries: no sequence (runner hooks), no field math (engine); # arcana effects mutate state through declared primitives only # tests: test_arcana_agendas.py # rollout: step 5c; completes the ruleset -# rollback: omit arcana= param; skip deal_agendas +# rollback: omit arcana= param; skip deal_agendas/deal_arcana # hmmm: FIRST-TIME VOTER's blind-draw-as-R deferred (SE shield only); # AUDIT compatibility is a coarse bool pending richer agenda algebra # === END MODULE_BUILD === @@ -39,8 +41,9 @@ # behavior: every dealt agenda is verified at match end against state # and log; ashes-mode verifies on loss as well as win # id: agendas_organizer_counts_r -# behavior: the organizer agenda holds only for the player with the -# strictly greatest attributed r tally +# behavior: organizer uses resolved attributed R, not action count +# id: arcana_dealt_ownership +# behavior: a player may resolve only the arcanum dealt to that seat # id: arcana_once_per_game # behavior: a player's second arcanum play never resolves # id: arcana_wayseer_reveals @@ -84,7 +87,8 @@ def verify_agendas(state, outcome): laid_by_pid = {} for ev in state.log: if ev[0] == "action": - r_by_pid[ev[1]] = r_by_pid.get(ev[1], 0) + 1 + resolved_r = ev[4] if len(ev) > 4 else 0 + r_by_pid[ev[1]] = r_by_pid.get(ev[1], 0) + resolved_r plays_by_pid.setdefault(ev[1], []).append(ev[2]) if ev[0] == "static": laid_by_pid.setdefault(ev[1], []).append(ev[2]) @@ -126,16 +130,28 @@ def verify_agendas(state, outcome): ] +def deal_arcana(state, players, rng): + pool = list(ARCANA) + rng.shuffle(pool) + state.tallies["arcana"] = [dict(pool[i % len(pool)]) + for i in range(players)] + + class ArcanaModule: - """Applies trumps. Public, attributed, once per player per game.""" + """Applies trumps. Public, attributed, dealt one per seat, once per game.""" def __init__(self): self.used = set() def apply(self, arcanum, state, machine, engine, pid, players): name = arcanum.get("name") + dealt = state.tallies.get("arcana") + if dealt: + if pid >= len(dealt) or dealt[pid].get("name") != name: + state.log.append(("arcana_refused", pid, name, "not_dealt")) + return if pid in self.used: - state.log.append(("arcana_refused", pid, name)) + state.log.append(("arcana_refused", pid, name, "already_used")) return self.used.add(pid) state.log.append(("arcana", pid, name)) @@ -187,4 +203,4 @@ def apply(self, arcanum, state, machine, engine, pid, players): else -incompat))) state.log.append(("audit", [a["name"] for a in ag])) -# ratios: loc_comments=119:51 imports_exports=2:3 calls_definitions=45:5 +# ratios: loc_comments=130:54 imports_exports=2:4 calls_definitions=54:6 From b62666aade7bc3da0633c029ec24b21102c6a724 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sun, 6 Sep 2026 11:55:08 -0700 Subject: [PATCH 06/17] fix: make discard costs real and log resolved repair --- engine/politics_runner.py | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/engine/politics_runner.py b/engine/politics_runner.py index 3911487..6c2e179 100644 --- a/engine/politics_runner.py +++ b/engine/politics_runner.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=193:103 imports_exports=3:8 calls_definitions=95:20 +# ratios: loc_comments=203:108 imports_exports=3:8 calls_definitions=101:20 """politics_runner — turn-sequence runner for POLITICS (base game). The runner owns the ORDER of play and nothing else. All game truth lives @@ -30,7 +30,9 @@ print(result.outcome, result.machine_beats, result.state.population) Swap NullPlayer for any object with take_turn(state, pid) -> TurnPlays to -seat a human UI, an a0 agent, or an older-model playtester. Swap +seat a human UI, an a0 agent, or an older-model playtester. In hand mode, +discard_cards must contain the actual cards being burned; an integer +discard claim without cards does not buy tempo or reach. Swap ScriptedMachine for a policy machine in later sets; Weimar ships scripted. Integration: this module is build-order step 1; the engine here is the step-2 stub already carrying the real leaky-integrator math so the null @@ -81,6 +83,9 @@ # behavior: in hand mode, a play resolves only from the hand holding # it; played and burned cards move to the discard pile; the turn ends # with one draw while the pile lasts +# id: runner_attributed_r_log +# behavior: every resolved action log records the actual R contributed +# after habituation so agenda accounting can consume evidence # id: runner_suspension_primitive # behavior: while suspend_beats > 0 a machine beat consumes the # suspension instead of a card: no card, no pmr, population unchanged @@ -120,6 +125,7 @@ class TurnPlays: static: dict | None = None actions: list = field(default_factory=list) discards: int = 0 + discard_cards: list = field(default_factory=list) arcana: dict | None = None @@ -270,6 +276,14 @@ def _machine_beat(self): def _player_beat(self, pid): plays = self.players[pid].take_turn(self.state, pid) + if self.hand_mode: + paid = [] + for card in list(getattr(plays, "discard_cards", [])): + if self._take_from_hand(pid, card): + paid.append(card) + self.state.log.append(("discard", pid, card.get("name"))) + plays.discard_cards = paid + plays.discards = len(paid) if hasattr(self.rules, "validate_turn"): plays = self.rules.validate_turn(self.state, plays) if self.arcana and getattr(plays, "arcana", None): @@ -287,14 +301,15 @@ def _player_beat(self, pid): for action in plays.actions: if self.rules.legal(self.state, action): if hasattr(self.rules, "effective_r"): - r_total += self.rules.effective_r(self.state, action) + resolved_r = self.rules.effective_r(self.state, action) else: - r_total += action.get("r", 0) + resolved_r = action.get("r", 0) + r_total += resolved_r n = self.state.tallies.get("played:" + action.get("name", ""), 0) self.state.tallies["played:" + action.get("name", "")] = n + 1 self._targets.append(action.get("declared_target")) self.state.log.append(("action", pid, action.get("name"), - action.get("target"))) + action.get("target"), resolved_r)) if hasattr(self.rules, "interference"): ds, dm = self.rules.interference(self.state, action) s_extra += ds @@ -335,4 +350,4 @@ def run(self, max_beats=500): return MatchResult(outcome, self.state.machine_beats, self.state, verdicts) -# ratios: loc_comments=193:103 imports_exports=3:8 calls_definitions=95:20 +# ratios: loc_comments=203:108 imports_exports=3:8 calls_definitions=101:20 From 17f19e41daa412b42084495887accd833e8563db Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sun, 6 Sep 2026 11:56:43 -0700 Subject: [PATCH 07/17] fix: make First-Time Voter SE shield executable --- engine/cards_v1.py | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/engine/cards_v1.py b/engine/cards_v1.py index 0911879..88621d9 100644 --- a/engine/cards_v1.py +++ b/engine/cards_v1.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=160:65 imports_exports=4:4 calls_definitions=80:13 +# ratios: loc_comments=171:68 imports_exports=4:4 calls_definitions=85:14 """cards_v1 — secret effects, interaction links, the M15 hinge, deck law. WeimarMachine plays the scripted set with its mechanics live: SE gating @@ -44,6 +44,9 @@ # chain with delay_if_gated fires on the following beat instead # id: cards_se_doubles_at_low_m # behavior: at m <= 1 stacking effects apply twice +# id: cards_se_shield_consumed +# behavior: an active SE shield prevents one otherwise-resolving secret +# effect and is consumed exactly once, including delayed effects # id: cards_destruction_feeds_prereqs # behavior: destroying a machine card or static causes every later # card with that prereq to be skipped @@ -129,6 +132,14 @@ def _tick_timed(self, state): if s["timed_beats"] <= 0: s["timed_debuff"] = 0 + def _consume_se_shield(self, state, card_id): + shield = state.tallies.get("se_shield", 0) + if shield <= 0: + return False + state.tallies["se_shield"] = shield - 1 + state.log.append(("se_shielded", card_id)) + return True + def _apply_effect(self, se, state, card): kind = se.get("kind") if kind in NOOP_EFFECTS: @@ -171,6 +182,8 @@ def _resolve_se(self, card, state): return gate = se.get("a_se", 0) if state.e >= gate: + if self._consume_se_shield(state, card["id"]): + return times = 2 if (state.m <= 1 and se.get("kind") not in {"chain"} | NOOP_EFFECTS) else 1 for _ in range(times): @@ -203,10 +216,12 @@ def next_card(self, state): for s in prey[:eaters]: state.in_play_statics.remove(s) state.log.append(("gleichgeschaltet", s.get("name"))) - for se in self.delayed: + for se in list(self.delayed): if state.e >= se.get("a_se", 0): - self._apply_effect(se, state, {"id": se["card_id"]}) self.delayed.remove(se) + if self._consume_se_shield(state, se["card_id"]): + continue + self._apply_effect(se, state, {"id": se["card_id"]}) while self.cursor < len(self.script): card = self.script[self.cursor] self.cursor += 1 @@ -249,4 +264,4 @@ def resolve_se(self, played, state, countered=False): return self._resolve_se(card, state) -# ratios: loc_comments=160:65 imports_exports=4:4 calls_definitions=80:13 +# ratios: loc_comments=171:68 imports_exports=4:4 calls_definitions=85:14 From 0fa4e60e8de503f95148c283065ca16716f5f2a8 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sun, 6 Sep 2026 11:57:33 -0700 Subject: [PATCH 08/17] test: cover arcana ownership and resolved-R agenda scoring --- engine/test_arcana_agendas.py | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/engine/test_arcana_agendas.py b/engine/test_arcana_agendas.py index 10e4927..a5d5bbf 100644 --- a/engine/test_arcana_agendas.py +++ b/engine/test_arcana_agendas.py @@ -1,11 +1,13 @@ -# ratios: loc_comments=94:21 imports_exports=8:2 calls_definitions=54:10 +# ratios: loc_comments=107:23 imports_exports=8:2 calls_definitions=61:11 """Checks for arcana + agendas + habituation + reclaim pricing. # === CHECKS === # id: check_agendas_verify_at_end # witnesses: agendas_verify_at_end # id: check_organizer_counts_r -# witnesses: agendas_organizer_counts_r +# witnesses: agendas_organizer_counts_r, runner_attributed_r_log +# id: check_arcana_dealt_ownership +# witnesses: arcana_dealt_ownership # id: check_arcana_once # witnesses: arcana_once_per_game # id: check_wayseer @@ -25,7 +27,8 @@ import politics_runner as pr import weimar_data as wd -from arcana_agendas_v1 import ArcanaModule, deal_agendas, verify_agendas +from arcana_agendas_v1 import (ArcanaModule, deal_agendas, deal_arcana, + verify_agendas) from cards_v1 import WeimarMachine from inertial_engine import InertialEngine, weimar_seed from rules_v1 import RulesV1 @@ -58,12 +61,25 @@ def test_check_organizer_counts_r(self): st.tallies["agendas"] = [a for a in __import__("arcana_agendas_v1").AGENDAS if a["name"] == "THE ORGANIZER"] * 2 - st.log += [("action", 0, "X"), ("action", 0, "X"), - ("action", 1, "X")] + st.log += [("action", 0, "X", None, 3), + ("action", 1, "X", None, 1), + ("action", 1, "X", None, 1)] v = verify_agendas(st, "win") - self.assertTrue(v[0]["held"]) # pid 0 strictly greatest + self.assertTrue(v[0]["held"]) # 3 R beats two 1 R actions self.assertFalse(v[1]["held"]) + def test_check_arcana_dealt_ownership(self): + st = opening() + deal_arcana(st, 2, random.Random(3)) + arc = ArcanaModule() + mine = st.tallies["arcana"][0] + other = st.tallies["arcana"][1] + arc.apply(other, st, WeimarMachine(wd.MACHINE_SCRIPT), None, 0, 2) + self.assertEqual([ev for ev in st.log if ev[0] == "arcana"], + []) + arc.apply(mine, st, WeimarMachine(wd.MACHINE_SCRIPT), None, 0, 2) + self.assertTrue(any(ev[0] == "arcana" for ev in st.log)) + def test_check_arcana_once(self): st = opening() arc = ArcanaModule() @@ -130,4 +146,4 @@ def test_check_reclaim_priced(self): if __name__ == "__main__": unittest.main(verbosity=2) -# ratios: loc_comments=94:21 imports_exports=8:2 calls_definitions=54:10 +# ratios: loc_comments=107:23 imports_exports=8:2 calls_definitions=61:11 From f7ac12af7f32bf3b8f2dadc3e0ba37fcccbfdf6f Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sun, 6 Sep 2026 11:58:15 -0700 Subject: [PATCH 09/17] test: prove physical discards and attributed R logging --- engine/test_politics_runner.py | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/engine/test_politics_runner.py b/engine/test_politics_runner.py index b5ff0f2..ab6c198 100644 --- a/engine/test_politics_runner.py +++ b/engine/test_politics_runner.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=50:17 imports_exports=2:3 calls_definitions=26:12 +# ratios: loc_comments=69:21 imports_exports=2:3 calls_definitions=39:14 """Checks for politics_runner. Run: python3 test_politics_runner.py # === CHECKS === @@ -14,6 +14,10 @@ # witnesses: runner_null_clock_terminates # id: check_rejects_illegal_plays # witnesses: runner_rejects_illegal_plays +# id: check_hand_discard_is_real +# witnesses: runner_hand_law +# id: check_action_log_carries_r +# witnesses: runner_attributed_r_log # === END CHECKS === """ import unittest @@ -81,7 +85,28 @@ def legal(self, state, play): res = self._run(deck(2, s=1), [active], rules=NoRules()) self.assertEqual(res.state.in_play_statics, []) # dropped silently + def test_check_hand_discard_is_real(self): + class Burner: + def __init__(self): + self.done = False + def take_turn(self, state, pid): + if self.done: + return TurnPlays() + self.done = True + return TurnPlays(discard_cards=[state.hands[pid][0]]) + st = GameState(draw_pile=[{"name": "BURN"}, {"name": "DRAW"}]) + res = MatchRunner(LeakyEngine(), ScriptedMachine(deck(2, s=0)), + [Burner()], st, hand_size=1).run() + self.assertIn("BURN", [c["name"] for c in res.state.discard_pile]) + self.assertNotIn("BURN", [c["name"] for c in res.state.hands[0]]) + + def test_check_action_log_carries_r(self): + active = RecordingPlayer(TurnPlays(actions=[{"name": "a", "r": 3}])) + res = self._run(deck(2, s=0), [active]) + action = next(ev for ev in res.state.log if ev[0] == "action") + self.assertEqual(action[4], 3) + if __name__ == "__main__": unittest.main(verbosity=2) -# ratios: loc_comments=50:17 imports_exports=2:3 calls_definitions=26:12 +# ratios: loc_comments=69:21 imports_exports=2:3 calls_definitions=39:14 From 5633159c2fadbd2a22398414d5618cf8b04800b3 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sun, 6 Sep 2026 11:59:03 -0700 Subject: [PATCH 10/17] test: prove First-Time Voter secret-effect shield --- engine/test_cards_v1.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/engine/test_cards_v1.py b/engine/test_cards_v1.py index 48c3b96..bfcc838 100644 --- a/engine/test_cards_v1.py +++ b/engine/test_cards_v1.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=83:18 imports_exports=6:2 calls_definitions=54:9 +# ratios: loc_comments=97:20 imports_exports=6:2 calls_definitions=62:10 """Checks for cards_v1 + weimar_data. Run: python3 test_cards_v1.py # === CHECKS === @@ -8,6 +8,8 @@ # witnesses: cards_se_gated_by_e # id: check_se_doubles_at_low_m # witnesses: cards_se_doubles_at_low_m +# id: check_se_shield_consumed +# witnesses: cards_se_shield_consumed # id: check_destruction_feeds_prereqs # witnesses: cards_destruction_feeds_prereqs # id: check_courts_flip @@ -69,6 +71,21 @@ def test_check_se_doubles_at_low_m(self): if s.get("name") == "THE PULPITS"][0] self.assertEqual(pulpits["perm_debuff"], expect) + def test_check_se_shield_consumed(self): + card = {"id": "T", "name": "T", "s": 0, + "se": {"a_se": 0, "kind": "perm_debuff", + "target": "THE PULPITS", "amount": 1}} + m = WeimarMachine([card]) + st = opening() + st.tallies["se_shield"] = 1 + st.in_play_statics.append({"name": "THE PULPITS", "passive_r": 1}) + m.resolve_se(m.next_card(st), st) + pulpits = [s for s in st.in_play_statics + if s.get("name") == "THE PULPITS"][0] + self.assertEqual(pulpits.get("perm_debuff", 0), 0) + self.assertEqual(st.tallies["se_shield"], 0) + self.assertIn(("se_shielded", "T"), st.log) + def test_check_destruction_feeds_prereqs(self): m = WeimarMachine(wd.MACHINE_SCRIPT) m.destroy("M35") # Marburg never spoken @@ -115,4 +132,4 @@ def test_check_full_match_runs(self): if __name__ == "__main__": unittest.main(verbosity=2) -# ratios: loc_comments=83:18 imports_exports=6:2 calls_definitions=54:9 +# ratios: loc_comments=97:20 imports_exports=6:2 calls_definitions=62:10 From 0cae9c458d2b4e0e229b760e57f0049e440577bb Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sun, 6 Sep 2026 12:01:52 -0700 Subject: [PATCH 11/17] fix: expose human reflex and arcana play on the table --- render/serve.py | 240 ++++++++++++++++++++++++++++++++++-------------- 1 file changed, 173 insertions(+), 67 deletions(-) diff --git a/render/serve.py b/render/serve.py index ee41886..f139b61 100644 --- a/render/serve.py +++ b/render/serve.py @@ -1,10 +1,11 @@ -# ratios: loc_comments=206:51 imports_exports=15:4 calls_definitions=97:15 +# ratios: loc_comments=298:61 imports_exports=15:6 calls_definitions=133:17 """serve — the table. Top-down playable board for POLITICS, stdlib only. Layout per Erin's spec: the population at center (the inertial vector field drawn live — every person a dot, leaning as they lean), the card field ringed around it, machine and player avatars around that, your -hand along the bottom. Real card art from expansions/.../cards/. +hand along the bottom. Fifty-Three Days art is loaded from the base-game +set namespace. No tips, no tricks: the interface enforces legality by refusal and states what you MAY do, never what you should. The tutorial is the game @@ -15,33 +16,39 @@ -------------- cd && python3 render/serve.py # port 5300 # phone browser -> http://localhost:5300 -Solo seat + two null co-seats by default (POLITICS_SEATS=3 to change). +Solo seat + two noisy co-seats by default (POLITICS_SEATS=3 to change). Match: Fifty-Three Days, hand of 5, full mechanics. Refresh-safe: state -lives server-side; the page polls. Ctrl-C ends the republic early. +lives server-side; the page polls. Human reflex windows and the dealt +arcanum are playable from the browser. Ctrl-C ends the republic early. # === MODULE_BUILD === # id: render_serve_v01 # purpose: playable top-down table over the real engine; art live # surfaces: HTTP / (board), /state, /act, /react; HumanSeat # boundaries: no game logic (engine/rules own truth); no advice; -# blocking human input via queue, match on a worker thread -# tests: test_serve.py (state contract; no browser needed) +# blocking human input via queues, match on a worker thread +# tests: test_serve.py (state and request contracts; no browser needed) # rollout: first sit-down vehicle; tutorial == this, per ruling 6 # rollback: delete render/; engine untouched -# hmmm: reaction-window UI is prompt-only v1; multiplayer seats wait -# on matchmaking phase; agenda card shown, arcana button minimal +# hmmm: multiplayer seats wait on matchmaking phase; richer targeting +# waits on a real multiplayer table rather than being guessed here # === END MODULE_BUILD === # === CONTRACTS === # id: serve_state_reports_truth -# behavior: /state returns tracks, board, hand, awaiting flag, and log -# tail straight from the live GameState, nothing derived or advised +# behavior: /state returns tracks, board, hand, identity, reaction, +# awaiting flag, and log tail straight from live state # id: serve_act_enforces_hand_law -# behavior: /act with a card index outside the hand is refused without -# explanation +# behavior: invalid card/burn indexes are refused; valid burns are +# passed as actual card objects for runner hand-law enforcement +# id: serve_reaction_window +# behavior: when a legal human reflex exists, the worker pauses until +# the player chooses a legal reflex+burn pair or explicitly passes +# id: serve_arcana_playable +# behavior: the human seat sees and may play only its dealt arcanum # id: serve_match_thread_completes -# behavior: with null input the worker thread ends the match on the -# null clock +# behavior: with null human input/reactions the worker ends the match +# on the null clock # === END CONTRACTS === """ @@ -57,18 +64,21 @@ import politics_runner as pr # noqa: E402 import weimar_data as wd # noqa: E402 from harness_v1 import NoisyCoopPlayer # noqa: E402 -from arcana_agendas_v1 import ArcanaModule, deal_agendas # noqa: E402 +from arcana_agendas_v1 import (ArcanaModule, deal_agendas, deal_arcana) # noqa: E402 from cards_v1 import WeimarMachine, build_response_pile # noqa: E402 from inertial_engine import InertialEngine, weimar_seed # noqa: E402 from rules_v1 import RulesV1 # noqa: E402 class HumanSeat: - """Blocks the match thread until the browser posts a turn.""" + """Blocks the match thread only when the human actually has a choice.""" - def __init__(self): + def __init__(self, rules): + self.rules = rules self.inbox = queue.Queue() + self.reaction_inbox = queue.Queue() self.awaiting = False + self.reaction = None def take_turn(self, state, pid): self.awaiting = True @@ -77,17 +87,39 @@ def take_turn(self, state, pid): return plays def react(self, state, pid, mcard): - return None # v1: no human reflex UI + hand = state.hands[pid] if state.hands else [] + candidates = [i for i, card in enumerate(hand) + if card.get("reflex") + and self.rules.reflex_legal(state, card, mcard)] + candidates = [i for i in candidates + if any(j != i for j in range(len(hand)))] + if not candidates: + return None + self.reaction = {"incoming": dict(mcard), "candidates": candidates} + choice = self.reaction_inbox.get() + reaction = self.reaction + self.reaction = None + if choice is None: + return None + card_i, burn_i = choice + if (card_i not in reaction["candidates"] or + burn_i == card_i or + not (0 <= burn_i < len(hand))): + return None + return hand[card_i], hand[burn_i] class Table: def __init__(self, seats=3, seed=53): - self.human = HumanSeat() + self.rules = RulesV1() + self.human = HumanSeat(self.rules) st = pr.GameState(**wd.WEIMAR_OPENING) st.in_play_statics.extend(dict(s) for s in wd.SETUP_STATICS) st.draw_pile = build_response_pile(seed) import random - deal_agendas(st, seats, random.Random(seed)) + rng = random.Random(seed) + deal_agendas(st, seats, rng) + deal_arcana(st, seats, rng) self.state = st self.engine = InertialEngine(weimar_seed(100, seed)) import random as _rnd @@ -101,7 +133,7 @@ def __init__(self, seats=3, seed=53): self.runner = pr.MatchRunner( self.engine, WeimarMachine(wd.MACHINE_SCRIPT, reserve=wd.RESERVE_PILE), - players, st, rules=RulesV1(), hand_size=5, + players, st, rules=self.rules, hand_size=5, arcana=ArcanaModule()) self.result = None self.thread = threading.Thread(target=self._run, daemon=True) @@ -113,6 +145,7 @@ def _run(self): def snapshot(self): st = self.state hand = st.hands[0] if st.hands else [] + arcana = (st.tallies.get("arcana") or [{}])[0] return { "tracks": {"population": st.population, "e": round(st.e, 2), "m": st.m, "beat": st.machine_beats}, @@ -129,14 +162,71 @@ def snapshot(self): if e2[0] == "machine_id"), None)} for ev in reversed(st.log) if ev[0] == "machine"), None), "agenda": (st.tallies.get("agendas") or [{}])[0], + "arcana": arcana, + "arcana_used": 0 in self.runner.arcana.used, "awaiting": self.human.awaiting, - "log": [list(map(str, ev)) for ev in st.log[-8:]], + "reaction": self.human.reaction, + "log": [list(map(str, ev)) for ev in st.log[-10:]], "done": None if self.result is None else { "outcome": self.result.outcome, "agendas": self.result.agenda_outcomes}, } +def turn_from_request(table, req): + """Translate browser indexes into owned card objects; reject bad identity.""" + hand = table.state.hands[0] if table.state.hands else [] + kind = req.get("kind") + idx = req.get("card") + if kind in {"static", "action"}: + if not isinstance(idx, int) or not 0 <= idx < len(hand): + return None + card = hand[idx] + else: + card = None + burn_indexes = req.get("burns", []) + if not isinstance(burn_indexes, list): + return None + if len(set(burn_indexes)) != len(burn_indexes): + return None + if any(not isinstance(i, int) or not 0 <= i < len(hand) + for i in burn_indexes): + return None + if idx in burn_indexes: + return None + burns = [hand[i] for i in burn_indexes] + plays = pr.TurnPlays(discard_cards=burns) + if kind == "static": + card["side"] = "player" + plays.static = card + elif kind == "action": + card["declared_target"] = "shared" + plays.actions = [card] + elif kind == "arcana": + dealt = table.state.tallies.get("arcana") or [] + if not dealt: + return None + plays.arcana = dealt[0] + elif kind != "pass": + return None + return plays + + +def reaction_from_request(table, req): + """Return a validated (reflex-index, burn-index) pair or explicit pass.""" + pending = table.human.reaction + if pending is None: + return False, None + if req.get("pass"): + return True, None + card_i, burn_i = req.get("card"), req.get("burn") + hand = table.state.hands[0] if table.state.hands else [] + valid = (isinstance(card_i, int) and isinstance(burn_i, int) + and card_i in pending["candidates"] and burn_i != card_i + and 0 <= burn_i < len(hand)) + return (True, (card_i, burn_i)) if valid else (False, None) + + TABLE = None PAGE = """
-
+
-
+ + + +
""" @@ -237,41 +341,43 @@ def _art(self, ident): ident = ident.replace(".png", "").lower() if not ident.replace("-", "").isalnum(): self.send_response(404); self.end_headers(); return - base = os.path.join(os.path.dirname(__file__), "..", "expansions", - "scared-sacred", "cards") - for deck in ("response", "machine", "setup", "reserve"): + base = os.path.join(os.path.dirname(__file__), "..", "base-game", + "sets", "fifty-three-days", "cards") + for deck in ("response", "machine", "setup", "reserve", "arcana", + "agendas"): png = os.path.join(base, deck, ident + ".png") if os.path.exists(png): self.send_response(200) self.send_header("content-type", "image/png") self.end_headers() - self.wfile.write(open(png, "rb").read()) + with open(png, "rb") as fh: + self.wfile.write(fh.read()) return self.send_response(404); self.end_headers() def do_POST(self): - if self.path != "/act": - self.send_response(404); self.end_headers(); return n = int(self.headers.get("content-length", 0)) req = json.loads(self.rfile.read(n) or "{}") - hand = TABLE.state.hands[0] if TABLE.state.hands else [] - idx = req.get("card") - card = hand[idx] if isinstance(idx, int) and 0 <= idx < len(hand) \ - else None - burns = [hand[b] for b in req.get("burns", []) - if isinstance(b, int) and 0 <= b < len(hand)] - plays = pr.TurnPlays(discards=len(burns)) - if req.get("kind") == "static" and card is not None: - card["side"] = "player" - plays.static = card - elif req.get("kind") == "action" and card is not None: - card["declared_target"] = "shared" - plays.actions = [card] - if TABLE.human.awaiting: + if self.path == "/act": + if not TABLE.human.awaiting or TABLE.human.reaction is not None: + self._json({"ok": False}, 409) + return + plays = turn_from_request(TABLE, req) + if plays is None: + self._json({"ok": False}, 400) + return TABLE.human.inbox.put(plays) self._json({"ok": True}) - else: - self._json({"ok": False}, 409) # not your beat; no explanation + return + if self.path == "/react": + ok, choice = reaction_from_request(TABLE, req) + if not ok: + self._json({"ok": False}, 400 if TABLE.human.reaction else 409) + return + TABLE.human.reaction_inbox.put(choice) + self._json({"ok": True}) + return + self.send_response(404); self.end_headers() def main(): @@ -285,4 +391,4 @@ def main(): if __name__ == "__main__": main() -# ratios: loc_comments=206:51 imports_exports=15:4 calls_definitions=97:15 +# ratios: loc_comments=298:61 imports_exports=15:6 calls_definitions=133:17 From 86608b312081b606649cb2b431a149fed6d8eeaa Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sun, 6 Sep 2026 12:02:34 -0700 Subject: [PATCH 12/17] test: replace false-green table assertions --- render/test_serve.py | 55 +++++++++++++++++++++++++++++++++----------- 1 file changed, 41 insertions(+), 14 deletions(-) diff --git a/render/test_serve.py b/render/test_serve.py index 573952c..a3e7160 100644 --- a/render/test_serve.py +++ b/render/test_serve.py @@ -1,11 +1,13 @@ -# ratios: loc_comments=34:11 imports_exports=4:1 calls_definitions=21:4 +# ratios: loc_comments=59:12 imports_exports=4:1 calls_definitions=34:5 """Checks for the table server (no browser required). # === CHECKS === # id: check_state_reports_truth -# witnesses: serve_state_reports_truth +# witnesses: serve_state_reports_truth, serve_arcana_playable # id: check_act_enforces_hand_law # witnesses: serve_act_enforces_hand_law +# id: check_reaction_request_validation +# witnesses: serve_reaction_window # id: check_match_thread_completes # witnesses: serve_match_thread_completes # === END CHECKS === @@ -25,23 +27,48 @@ def test_check_state_reports_truth(self): s = t.snapshot() self.assertEqual(len(s["field"]), 100) self.assertIn("population", s["tracks"]) - self.assertTrue(s["awaiting"]) # human seat blocks beat one self.assertEqual(len(s["hand"]), 5) - t.human.inbox.put(pr.TurnPlays()) # release the thread + self.assertTrue(s["arcana"].get("name")) + self.assertIn("reaction", s) + if t.human.reaction is not None: + t.human.reaction_inbox.put(None) + if t.human.awaiting: + t.human.inbox.put(pr.TurnPlays()) def test_check_act_enforces_hand_law(self): - t = serve.Table(seats=2) - time.sleep(0.1) - hand_before = list(t.state.hands[0]) - # out-of-range index -> pass turn, nothing leaves the hand - t.human.inbox.put(pr.TurnPlays()) - time.sleep(0.1) - self.assertTrue(all(c in t.state.hands[0] or True - for c in hand_before)) + fake = type("FakeTable", (), {})() + fake.state = pr.GameState( + hands=[[{"name": "A"}, {"name": "B"}, {"name": "C"}]], + tallies={"arcana": [{"name": "THE WAYSEER"}]}) + self.assertIsNone(serve.turn_from_request( + fake, {"kind": "action", "card": 99, "burns": []})) + self.assertIsNone(serve.turn_from_request( + fake, {"kind": "action", "card": 0, "burns": [0]})) + turn = serve.turn_from_request( + fake, {"kind": "action", "card": 0, "burns": [1]}) + self.assertIs(turn.actions[0], fake.state.hands[0][0]) + self.assertEqual(turn.discard_cards, [fake.state.hands[0][1]]) + + def test_check_reaction_request_validation(self): + fake = type("FakeTable", (), {})() + fake.state = pr.GameState(hands=[[{"name": "RX"}, {"name": "BURN"}]]) + fake.human = type("FakeHuman", (), {})() + fake.human.reaction = {"incoming": {"id": "M15"}, "candidates": [0]} + self.assertEqual( + serve.reaction_from_request(fake, {"card": 0, "burn": 1}), + (True, (0, 1))) + self.assertEqual( + serve.reaction_from_request(fake, {"card": 0, "burn": 0}), + (False, None)) + self.assertEqual( + serve.reaction_from_request(fake, {"pass": True}), + (True, None)) def test_check_match_thread_completes(self): t = serve.Table(seats=2) - for _ in range(200): + for _ in range(400): + if t.human.reaction is not None: + t.human.reaction_inbox.put(None) if t.human.awaiting: t.human.inbox.put(pr.TurnPlays()) if t.result is not None: @@ -53,4 +80,4 @@ def test_check_match_thread_completes(self): if __name__ == "__main__": unittest.main(verbosity=2) -# ratios: loc_comments=34:11 imports_exports=4:1 calls_definitions=21:4 +# ratios: loc_comments=59:12 imports_exports=4:1 calls_definitions=34:5 From b19fdedbcf07f463b8992c5911a28be73ba81e16 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sun, 6 Sep 2026 12:03:22 -0700 Subject: [PATCH 13/17] chore: reseal cards exporter ratios --- engine/cards_export.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/engine/cards_export.py b/engine/cards_export.py index 88f97b3..4dff939 100644 --- a/engine/cards_export.py +++ b/engine/cards_export.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=38:26 imports_exports=5:1 calls_definitions=23:2 +# ratios: loc_comments=40:28 imports_exports=5:1 calls_definitions=24:3 """cards_export — emit every Fifty-Three Days card as a repo-resident JSON asset. Single source of truth stays weimar_data.py / arcana_agendas_v1.py; this @@ -80,4 +80,4 @@ def export(root=".."): if __name__ == "__main__": idx = export(sys.argv[1] if len(sys.argv) > 1 else "..") print(f"exported {len(idx['cards'])} cards") -# ratios: loc_comments=38:26 imports_exports=5:1 calls_definitions=23:2 +# ratios: loc_comments=40:28 imports_exports=5:1 calls_definitions=24:3 From 102d110bb57ea8646bfd21f2690c16f485717bb9 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sun, 6 Sep 2026 12:03:45 -0700 Subject: [PATCH 14/17] chore: remove tracked Python bytecode --- .../arcana_agendas_v1.cpython-312.pyc | Bin 11011 -> 0 bytes .../arcana_agendas_v1.cpython-314.pyc | Bin 11935 -> 0 bytes engine/__pycache__/cards_v1.cpython-314.pyc | Bin 17074 -> 0 bytes .../__pycache__/inertial_engine.cpython-314.pyc | Bin 8957 -> 0 bytes .../__pycache__/politics_runner.cpython-314.pyc | Bin 22229 -> 0 bytes engine/__pycache__/rules_v1.cpython-314.pyc | Bin 11432 -> 0 bytes engine/__pycache__/weimar_data.cpython-312.pyc | Bin 8505 -> 0 bytes engine/__pycache__/weimar_data.cpython-314.pyc | Bin 9367 -> 0 bytes 8 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 engine/__pycache__/arcana_agendas_v1.cpython-312.pyc delete mode 100644 engine/__pycache__/arcana_agendas_v1.cpython-314.pyc delete mode 100644 engine/__pycache__/cards_v1.cpython-314.pyc delete mode 100644 engine/__pycache__/inertial_engine.cpython-314.pyc delete mode 100644 engine/__pycache__/politics_runner.cpython-314.pyc delete mode 100644 engine/__pycache__/rules_v1.cpython-314.pyc delete mode 100644 engine/__pycache__/weimar_data.cpython-312.pyc delete mode 100644 engine/__pycache__/weimar_data.cpython-314.pyc diff --git a/engine/__pycache__/arcana_agendas_v1.cpython-312.pyc b/engine/__pycache__/arcana_agendas_v1.cpython-312.pyc deleted file mode 100644 index 69acaa2e336b23a3b7edebb8925104e68c24060a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11011 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 ZnyK4#TCIlJy#J{PhW*V4yUh1BagpzD< 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 R7A+rF_prTCY;vbO{T~r&rdj|1 diff --git a/engine/__pycache__/cards_v1.cpython-314.pyc b/engine/__pycache__/cards_v1.cpython-314.pyc deleted file mode 100644 index 1629bfa2468750fe4f482986e3d6fe2160bded96..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17074 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 diff --git a/engine/__pycache__/inertial_engine.cpython-314.pyc b/engine/__pycache__/inertial_engine.cpython-314.pyc deleted file mode 100644 index 25e2288fd2b5fe80916ee8dcb86b88d91f403cf8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8957 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 diff --git a/engine/__pycache__/weimar_data.cpython-312.pyc b/engine/__pycache__/weimar_data.cpython-312.pyc deleted file mode 100644 index 0da8b6554d9dc14cd96073fea39eed7b4880e2ab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8505 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 IpTQ9Of7v!5TL1t6 diff --git a/engine/__pycache__/weimar_data.cpython-314.pyc b/engine/__pycache__/weimar_data.cpython-314.pyc deleted file mode 100644 index 045bc4a661e609d5332634e8e76b12b5f75ac64b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9367 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 KEPM)A#Qy_UQ+byF From 817c5084ce22140de882b283567a270a3157ac5c Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sun, 6 Sep 2026 12:03:58 -0700 Subject: [PATCH 15/17] chore: ignore generated Python state --- .gitignore | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..26b5282 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +__pycache__/ +*.py[cod] +.pytest_cache/ +.coverage +htmlcov/ +.venv/ +venv/ From 5e3940d5840c7269854e2682981b940065264f08 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sun, 6 Sep 2026 12:04:17 -0700 Subject: [PATCH 16/17] test: guard Fifty-Three Days namespace --- engine/test_cards_export.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 engine/test_cards_export.py diff --git a/engine/test_cards_export.py b/engine/test_cards_export.py new file mode 100644 index 0000000..e258b4d --- /dev/null +++ b/engine/test_cards_export.py @@ -0,0 +1,31 @@ +# ratios: loc_comments=16:6 imports_exports=4:1 calls_definitions=7:2 +"""Checks that generated Fifty-Three Days assets stay in the base-game set. + +# === CHECKS === +# id: check_export_namespace +# witnesses: cards_export_v01 +# === END CHECKS === +""" +import tempfile +import unittest +from pathlib import Path + +import cards_export + + +class Checks(unittest.TestCase): + + def test_check_export_namespace(self): + with tempfile.TemporaryDirectory() as td: + root = Path(td) + idx = cards_export.export(str(root)) + base = root / "base-game" / "sets" / "fifty-three-days" / "cards" + wrong = root / "expansions" / "scared-sacred" / "cards" + self.assertEqual(len(idx["cards"]), 87) + self.assertTrue((base / "index.json").is_file()) + self.assertFalse(wrong.exists()) + + +if __name__ == "__main__": + unittest.main(verbosity=2) +# ratios: loc_comments=16:6 imports_exports=4:1 calls_definitions=7:2 From 1da762feabee3eaeb69a65cecfcd0d4ac7d78fd2 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sun, 6 Sep 2026 12:04:34 -0700 Subject: [PATCH 17/17] ci: enforce engine and table regression gates --- .github/workflows/test.yml | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..c562ec8 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,31 @@ +name: repository-gates + +on: + pull_request: + push: + branches: [main] + workflow_dispatch: + +permissions: + contents: read + +jobs: + test: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v5 + - uses: actions/setup-python@v6 + with: + python-version: '3.12' + - name: Compile source + run: python -m compileall -q engine render + - name: Engine tests + working-directory: engine + run: python -m unittest discover -v -p 'test_*.py' + - name: Table tests + working-directory: render + env: + PYTHONPATH: ../engine + POLITICS_BOTS: null + run: python -m unittest discover -v -p 'test_*.py'