Repository navigation
Model engine refusals as first-class simulation outcomes - #13011
Merged
Merged
Conversation
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
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 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".
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
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The onboarding brief (
gunbc.plans.blackjack_onboarding, projected todocs/plans/blackjack-onboarding.md) specifiedplay_round -> SimulatedRound. That type can only hold a completed round, but every engine callplay_rounddrives returnsRoundStepand may beRoundRefused { 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)
RoundRefusalcause is a driver bug. Each round uses a fresh 52-card shoe, and the driver orders the engine calls itself.play_roundreturnsRoundPlay = RoundPlayed { round } | RoundPlayRefused { cause }.simulatereturnsBlackjackSimulation = SimulationCompleted { rounds } | SimulationRefused { round_index, seed, cause }, stopping at the first refusal with what replays it.simulate(plan)=simulate_shoes(seeded_shoes(plan), rules, strategy), withSeededShoe { seed, shoe }supplied as values.simulate_shoesthree shoes whose second is short. It asserts the wholeSimulationRefused { round_index: 1, seed, cause: ShoeExhausted }.simulate(plan)completes exactlyplan.roundsrounds.play_round_from_shoe, which leftsimulate's refusal arm unexercised.)roundsreplaces therounds_requested/rounds_completedpair, because a run either completes every round or refuses.summarizetakes only a completed run's rounds, and the reconciliation check isplayer_wins + dealer_wins + pushes == rounds.RoundRefused.dag/examples/blackjack/simulation.dagmarker annotation are updated to match.Validation
gunbc.planplan_generation, and the diff contains only the authored changes.simulation.dagmarker compiles withgunbc compile --target rust, 0 diagnostics.https://claude.ai/code/session_019kAKvQXHWK1YxuJaKnhQg2
🤖 Generated with Claude Code