Skip to content

Repair base-set boundaries and playable table contracts - #3

Merged
erinepshovel-code merged 17 commits into
mainfrom
audit-repair-shipaton
Sep 6, 2026
Merged

erinepshovel-code merged 17 commits into
mainfrom
audit-repair-shipaton

Conversation

@erinepshovel-code

Copy link
Copy Markdown
Collaborator

Achieved

  • move POLITICS / Fifty-Three Days cards and decks out of expansions/scared-sacred/ into base-game/sets/fifty-three-days/ without rewriting their blobs
  • reserve expansions/scared-sacred/ for the actual SCARED SACRED expansion
  • make hand-mode discard costs consume actual cards
  • log resolved post-habituation R and score THE ORGANIZER from that evidence
  • deal arcana, enforce dealt ownership, and expose human arcanum play
  • make FIRST-TIME VOTER's SE shield executable, including delayed effects
  • expose human reflex windows with explicit pass support
  • replace the false-green renderer hand-law test
  • remove tracked __pycache__ bytecode and ignore generated Python state
  • add a namespace-regression test and PR CI
  • reconcile stale README status

Remaining hmmm

  • M10 noop_extra_card, M35 noop_window, Camarilla rotation buff, and Book Fires choice targeting remain explicitly incomplete in existing canon/code; this PR does not invent their missing rulings
  • card art files are still absent from the current Git tree even though historical commit prose claimed 60 validated images; metadata therefore honestly remains awaiting
  • mobile store packaging and RevenueCat integration are downstream of this repository repair

Gates

CI runs source compilation plus all engine and table unittest suites on Python 3.12.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 6, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-06T19:13:40.380972Z 1da762f PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@erinepshovel-code
erinepshovel-code merged commit 14dfae4 into main Sep 6, 2026
2 checks passed
Comment thread render/serve.py
self.send_header("content-type", "image/png")
self.end_headers()
self.wfile.write(open(png, "rb").read())
with open(png, "rb") as fh:

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

https://github.com/The-Interdependency/scared-sacred/blob/1da762feabee3eaeb69a65cecfcd0d4ac7d78fd2/engine/arcana_agendas_v1.py#L196-L197
P1 Badge Expire the First-Time Voter shield after one round

The card is defined as granting immunity for one round (base-game/arcana_v02.md:15-16), but adding players charges makes immunity last until that many secret effects happen. If the following round contains fewer secret effects, the shield persists into later rounds; conversely, multiple delayed effects becoming eligible together can consume several charges in one beat. Track the protected machine beats or expire the shield at the rotation boundary instead of counting effects.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

working-directory: render
env:
PYTHONPATH: ../engine
POLITICS_BOTS: null

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Quote the null bot selector

In YAML, this unquoted value is a null scalar rather than the literal string "null", while Table selects NullPlayer only when os.environ.get("POLITICS_BOTS") == "null". Consequently the table job either receives an empty value and runs the noisy-bot branch or fails workflow validation, so it does not exercise the intended deterministic null-bot context.

Useful? React with 👍 / 👎.

Comment thread render/serve.py
Comment on lines +372 to +377
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)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Accept only one decision per reaction window

Because this is served by ThreadingHTTPServer, two rapid or concurrent /react requests can both observe the same non-None reaction and enqueue choices before the worker clears it. The first choice resolves the current window, but the second remains in reaction_inbox and is consumed automatically by the next reaction window, potentially passing or playing and burning cards without a new user decision. Claim or clear the pending window atomically before enqueueing.

Useful? React with 👍 / 👎.

Comment thread render/serve.py
Comment on lines +197 to +204
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]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Allocate browser burns to reach

The request records physical burn cards but never assigns their count to the selected action's burn field, which is the only value RulesV1.legal uses to lower activation energy. Since the browser submits at most one action, these burns cannot be tempo payments either; every card marked through the UI is therefore discarded without affecting legality, and an action that required the advertised reach discount is still rejected.

Useful? React with 👍 / 👎.

Comment on lines 89 to +91
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Include reflex repair in Organizer totals

The newly playable reflex path contributes reflex_card["r"] to the engine but logs a reflex event rather than an action event (politics_runner.py:244-247). This verifier scans only actions, so a player holding THE ORGANIZER receives no credit for repair supplied through reflexes and can incorrectly lose the agenda despite having the greatest attributed R.

Useful? React with 👍 / 👎.

Comment on lines 43 to +46
# 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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Regenerate the msdmd collection after contract changes

This commit adds and changes module-local contracts and checks, but the committed canonical collection point scared-sacred_msdmd.ts was not regenerated: it omits arcana_dealt_ownership, cards_se_shield_consumed, runner_attributed_r_log, the new renderer contracts and their checks, and still contains the previous Organizer behavior. Running the repository's msdmd.collect generator shows those missing declarations and edges, so collection consumers and visualizers receive stale contract coverage.

Useful? React with 👍 / 👎.

Comment thread render/serve.py
Comment on lines +91 to +93
candidates = [i for i, card in enumerate(hand)
if card.get("reflex")
and self.rules.reflex_legal(state, card, mcard)]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Resolve Wels' reflex before the M15 hinge

The new reaction UI offers WELS' SPEECH on M15, but WeimarMachine.next_card evaluates the M15 hinge before the runner opens _reaction_window, and that window only adds the card's R without applying the m_bonus handled by RulesV1.interference. Consequently the card's documented M+2 is omitted and the hinge threshold has already been decided, so a human reflex cannot provide its intended last-moment effect on the Enabling Act.

Useful? React with 👍 / 👎.

Comment thread render/serve.py
Comment on lines +187 to +193
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):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Validate burn element types before deduplicating

The /act payload is untrusted JSON, but set(burn_indexes) runs before verifying that every entry is an integer. A request such as {"burns":[[]]} therefore raises TypeError in the request thread instead of returning the contract's 400 response for an invalid burn index. Check element types first or deduplicate only after validation.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants