Skip to content

Model engine refusals as first-class simulation outcomes - #13011

Merged
briansrls merged 2 commits into
mainfrom
claude/optimistic-mayer-b38hap
Oct 2, 2026
Merged

briansrls merged 2 commits into
mainfrom
claude/optimistic-mayer-b38hap

Conversation

@briansrls

@briansrls briansrls commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Summary

The onboarding brief (gunbc.plans.blackjack_onboarding, projected to docs/plans/blackjack-onboarding.md) specified play_round -> SimulatedRound. That type can only hold a completed round, but every engine call play_round drives returns RoundStep and may be RoundRefused { cause }. A learner had to either fabricate a round or drop it silently. This PR changes the brief's §4.5 so that refusals are typed outcomes.

Key changes (brief §4.5 and its consumers)

  • Refusal is a defect, so the line stops. Inside a simulation, every RoundRefusal cause is a driver bug. Each round uses a fresh 52-card shoe, and the driver orders the engine calls itself.
    • play_round returns RoundPlay = RoundPlayed { round } | RoundPlayRefused { cause }.
    • simulate returns BlackjackSimulation = SimulationCompleted { rounds } | SimulationRefused { round_index, seed, cause }, stopping at the first refusal with what replays it.
  • The refusal route can execute. simulate(plan) = simulate_shoes(seeded_shoes(plan), rules, strategy), with SeededShoe { seed, shoe } supplied as values.
    • The prescribed test hands simulate_shoes three shoes whose second is short. It asserts the whole SimulationRefused { round_index: 1, seed, cause: ShoeExhausted }.
    • The real-route test asserts that simulate(plan) completes exactly plan.rounds rounds.
    • (Review finding: the first revision tested only play_round_from_shoe, which left simulate's refusal arm unexercised.)
  • Summary. rounds replaces the rounds_requested / rounds_completed pair, because a run either completes every round or refuses. summarize takes only a completed run's rounds, and the reconciliation check is player_wins + dealer_wins + pushes == rounds.
  • Nominal events are transitions, not refusals. A later multi-round shoe would make running low ordinary, so it must be a lawful reshuffle transition rather than a RoundRefused.
  • Acceptance bar, §6 (replay), §7, the data-flow diagram, and the dag/examples/blackjack/simulation.dag marker annotation are updated to match.

Validation

  • The projection was regenerated through gunbc.plan plan_generation, and the diff contains only the authored changes.
  • The simulation.dag marker compiles with gunbc compile --target rust, 0 diagnostics.

https://claude.ai/code/session_019kAKvQXHWK1YxuJaKnhQg2

🤖 Generated with Claude Code

play_round was specified as -> SimulatedRound, a type that can only hold a
completed round, while every engine call it drives returns RoundStep and may
be RoundRefused. A learner had to fabricate a round or drop it silently.

Inside a simulation every RoundRefusal cause is a driver defect (fresh
52-card shoe per round, the driver orders the calls), so a refusal is not a
statistic: play_round returns RoundPlay = RoundPlayed | RoundPlayRefused, and
simulate returns BlackjackSimulation = SimulationCompleted { rounds } |
SimulationRefused { round_index, seed, cause }, stopping at the first
refusal with what replays it. The summary loses the requested/completed
pair (a run completes every round or refuses) and summarizes only a
completed run. play_round_from_shoe is split out so the refusal route can
execute in a test (short shoe -> ShoeExhausted). Acceptance bar and the
simulation.dag marker updated; projection regenerated from expected_plan_md
through gunbc.plan plan_generation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019kAKvQXHWK1YxuJaKnhQg2
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 2, 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-10-02T20:20:16.754037Z c4ad450 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.

@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

Here are some automated review suggestions for this pull request.

Reviewed commit: c4ad450e45

ℹ️ 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".

Comment thread docs/plans/blackjack-onboarding.md Outdated
The short-shoe test drove play_round_from_shoe directly, so the conversion
from RoundPlayRefused to SimulationRefused inside simulate never executed:
from a plan every shoe is a full deck and no round can refuse, so a
simulate that fabricated a round, carried on, or attached the wrong
index/seed passed every specified test.

simulate is now simulate_shoes(seeded_shoes(plan), rules, strategy), with
SeededShoe { seed, shoe } supplied as values. The prescribed test hands
simulate_shoes three shoes whose second is short and asserts the whole
SimulationRefused { round_index: 1, seed, cause: ShoeExhausted }; the
real-route claim is that simulate(plan) completes exactly plan.rounds
rounds. Projection regenerated through gunbc.plan plan_generation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019kAKvQXHWK1YxuJaKnhQg2
@briansrls
briansrls merged commit 6deed5a into main Oct 2, 2026
4 checks passed
@briansrls
briansrls deleted the claude/optimistic-mayer-b38hap branch October 2, 2026 21:11
AlexRasevych pushed a commit that referenced this pull request Oct 3, 2026
* blackjack brief: simulate stops the line on an engine refusal

play_round was specified as -> SimulatedRound, a type that can only hold a
completed round, while every engine call it drives returns RoundStep and may
be RoundRefused. A learner had to fabricate a round or drop it silently.

Inside a simulation every RoundRefusal cause is a driver defect (fresh
52-card shoe per round, the driver orders the calls), so a refusal is not a
statistic: play_round returns RoundPlay = RoundPlayed | RoundPlayRefused, and
simulate returns BlackjackSimulation = SimulationCompleted { rounds } |
SimulationRefused { round_index, seed, cause }, stopping at the first
refusal with what replays it. The summary loses the requested/completed
pair (a run completes every round or refuses) and summarizes only a
completed run. play_round_from_shoe is split out so the refusal route can
execute in a test (short shoe -> ShoeExhausted). Acceptance bar and the
simulation.dag marker updated; projection regenerated from expected_plan_md
through gunbc.plan plan_generation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019kAKvQXHWK1YxuJaKnhQg2

* blackjack brief: run the SimulationRefused arm through simulate itself

The short-shoe test drove play_round_from_shoe directly, so the conversion
from RoundPlayRefused to SimulationRefused inside simulate never executed:
from a plan every shoe is a full deck and no round can refuse, so a
simulate that fabricated a round, carried on, or attached the wrong
index/seed passed every specified test.

simulate is now simulate_shoes(seeded_shoes(plan), rules, strategy), with
SeededShoe { seed, shoe } supplied as values. The prescribed test hands
simulate_shoes three shoes whose second is short and asserts the whole
SimulationRefused { round_index: 1, seed, cause: ShoeExhausted }; the
real-route claim is that simulate(plan) completes exactly plan.rounds
rounds. Projection regenerated through gunbc.plan plan_generation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019kAKvQXHWK1YxuJaKnhQg2

---------

Co-authored-by: Claude <noreply@anthropic.com>
briansrls added a commit that referenced this pull request Oct 12, 2026
* Work In Progress - cards and hand are implemented but need evaluation

* added test file for blackjack hand evaluation

* Milestone 3 complete - implemented round and test file

* Milestone 4 complete - implemented shuffle and test file

* Milestone 5 Progress - finished simulation.dag file

* Model engine refusals as first-class simulation outcomes (#13011)

* blackjack brief: simulate stops the line on an engine refusal

play_round was specified as -> SimulatedRound, a type that can only hold a
completed round, while every engine call it drives returns RoundStep and may
be RoundRefused. A learner had to fabricate a round or drop it silently.

Inside a simulation every RoundRefusal cause is a driver defect (fresh
52-card shoe per round, the driver orders the calls), so a refusal is not a
statistic: play_round returns RoundPlay = RoundPlayed | RoundPlayRefused, and
simulate returns BlackjackSimulation = SimulationCompleted { rounds } |
SimulationRefused { round_index, seed, cause }, stopping at the first
refusal with what replays it. The summary loses the requested/completed
pair (a run completes every round or refuses) and summarizes only a
completed run. play_round_from_shoe is split out so the refusal route can
execute in a test (short shoe -> ShoeExhausted). Acceptance bar and the
simulation.dag marker updated; projection regenerated from expected_plan_md
through gunbc.plan plan_generation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019kAKvQXHWK1YxuJaKnhQg2

* blackjack brief: run the SimulationRefused arm through simulate itself

The short-shoe test drove play_round_from_shoe directly, so the conversion
from RoundPlayRefused to SimulationRefused inside simulate never executed:
from a plan every shoe is a full deck and no round can refuse, so a
simulate that fabricated a round, carried on, or attached the wrong
index/seed passed every specified test.

simulate is now simulate_shoes(seeded_shoes(plan), rules, strategy), with
SeededShoe { seed, shoe } supplied as values. The prescribed test hands
simulate_shoes three shoes whose second is short and asserts the whole
SimulationRefused { round_index: 1, seed, cause: ShoeExhausted }; the
real-route claim is that simulate(plan) completes exactly plan.rounds
rounds. Projection regenerated through gunbc.plan plan_generation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019kAKvQXHWK1YxuJaKnhQg2

---------

Co-authored-by: Claude <noreply@anthropic.com>

* Complete blackjack simulation milestone

* Add blackjack project README

* Run blackjack entropy witness on wet lane

---------

Co-authored-by: Brian Searls <11205878+briansrls@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
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