-
Notifications
You must be signed in to change notification settings - Fork 0
Repair base-set boundaries and playable table contracts #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
492b217
901af00
f335488
24736ba
3720f97
b62666a
17f19e4
0fa4e60
f7ac12a
5633159
0cae9c4
86608b3
b19fded
102d110
817c508
5e3940d
1da762f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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' | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| __pycache__/ | ||
| *.py[cod] | ||
| .pytest_cache/ | ||
| .coverage | ||
| htmlcov/ | ||
| .venv/ | ||
| venv/ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 | ||
|
Comment on lines
43
to
+46
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This commit adds and changes module-local contracts and checks, but the committed canonical collection point Useful? React with 👍 / 👎. |
||
| # 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 | ||
|
Comment on lines
89
to
+91
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The newly playable reflex path contributes Useful? React with 👍 / 👎. |
||
| 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 | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In YAML, this unquoted value is a null scalar rather than the literal string
"null", whileTableselectsNullPlayeronly whenos.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 👍 / 👎.