Skip to content

The solver follows the key shape rules (D64, step 4) - #38

Merged
twilde merged 2 commits into
mainfrom
shape-rules-solver
Oct 7, 2026
Merged

twilde merged 2 commits into
mainfrom
shape-rules-solver

Conversation

@twilde

@twilde twilde commented Oct 6, 2026

Copy link
Copy Markdown
Owner

What and why

Step 4 of the key shape rules design (D64): every key
solve_system chooses follows the file's shape rules (Config.shape, from step 2). A master is
a key in the masters of any core. This is the step that changes what the solver produces
for existing files.

  • Single-key search. Candidates are drawn, and neighbours walked to, only among bittings that
    follow the rules (Problem.shaped_candidates, neighbors(cuts, key)).
  • Pinning, keys built together. The draws from the exact pinnable set are filtered the same
    way, and a set small enough to list is filtered whole. The printed line says so
    (... scored 300 drawn from them that follow the shape rules; ... 12 follow the shape rules; scored all of those).
  • Never relaxed. If the rules turn down 20,000 draws (and at least 50 for each candidate asked
    for) with no bitting for a key, that key is left null in the written file, NOT SOLVED: no bitting for K follows the shape rules (draws broke ...) is printed before Wrote ... and again
    last, and the full check is skipped, because it would reject the null. Exit status stays 0.
  • The score is unchanged (D6). README, docs/design.md and the design document describe the step,
    and the README has the shape row of the system file table.

Never include real key data

None: the tests use the fake clean and pinning fixtures; the measurements below are of fake
systems.

Checklist

  • I have not included real bittings, real system files, or anything that identifies a real building.
  • One logical change (refactors are in their own commits).
  • Tests added or updated, and pytest passes. A bug fix has a regression test.
  • README updated if behavior changed; a design decision gets a short entry in docs/decisions.md and, if it changes how things are, an edit to docs/design.md (and the feature's document in docs/designs/, if it has one). The decision is D64.
  • No new runtime dependencies; randomness defaults (secrets, random.SystemRandom) unchanged.

Behavior changes and AI assistance

Seeded solver output changes for files without shape, because they now get the default
rules. That is the deliberate change the design describes (D6 asks for a stated reason: the
solver was producing the bias the design measured). With every rule off the output is
byte-identical to before
: I captured stdout and written files from main for four fake systems
(the clean fixture with two blank keys, a parity system with every key blank, the pinning system
with six blank keys, the pinning system with the unit master and a control key blank) at three
seeds each, and the new code with a shape object that sets every rule to null gave the same
stdout and the same keys in all 12.

Measured with the defaults on, on the fake systems of the design's evidence section (the same
scripts as before):

before after uniform draw
parity pattern (60 seeds, 420 keys): a pair of equal adjacent cuts 64% 0% 26%
depth used 4+ times 8.1% 0% 1.4%
mean total variation 12.1 14.8 15.0
top master's mean cut about 1 4.2
pinning, no pattern (40 seeds, 240 keys): equal pair 62% 0% 54%
depth used 4+ times 6.2% 0% 4.4%
mean total variation 13.3 16.7

No key broke a rule. The solver is slower: one seeded run took 4.7 s before and 5.1 s after on
the parity system, and 15.3 s before and 19.8 s after on the pinning one.

A thin pinnable set can leave no key that follows the rules. One existing test builds a unit
master with seven random decoded units, which leaves 24 pinnable masters, and all 24 end in two
equal cuts. With the defaults that is now NO pinnable bitting follows the shape rules and NOT SOLVED (tested); the test that is about listing every pinnable master sets the rules off. This
is the interaction the design listed as unknown. It is reported, not repaired, and the way out is
the shape object or a known key.

Tests run: ci_changes.py says scanner=false, so the whole suite in a venv with only .[test]:
1019 passed, 16 skipped.

Written with Claude Code (Co-Authored-By trailer on the commit).

🤖 Generated with Claude Code

…64, step 4)

The single-key search draws its candidates, and walks to its neighbours, only
among bittings that follow the file's shape rules; in the pinning construction
the draws from the exact pinnable set are filtered the same way, and a set
small enough to list is filtered whole. A master is a key in the masters of
any core. The score is unchanged (D6).

The rules are never relaxed. If the rules turn down 20,000 draws (and at least
50 for each candidate asked for) without a bitting for a key, that key is left
null in the written file, a NOT SOLVED line says which rules turned the draws
down, before "Wrote" and again last, and the full check, which would reject
the null, is skipped. A thin pinnable set can leave no key that follows the
rules (a unit master among many decoded units); that is reported, not
repaired.

With every rule off the same seeded draws are made as before, so the output is
byte-identical: checked on four fake systems and three seeds each. The README,
design.md and the design document describe the step.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

@twilde twilde left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Changes requested

One Should fix (a crash), one Optional.

What I ran (head a1e32f3, which is current main plus one commit, so merging it into main changes nothing):

  • Whole suite in a venv with only .[test], as ci_changes.py says scanner=false: 1019 passed, 16 skipped.
  • check_no_stray_data.py --history: clean.
  • CI on the head is green (all test jobs, No stray data files, CodeQL).
  • Byte-identical claim: I ran the solver on main and on the head with every rule set to null, on two fake systems (the clean fixture with a null unit master, and the pinning fixture with null unit_master, area_d and control_b) at two seeds each. Stdout and the written keys were identical in all four runs.
  • Probes of tight but satisfiable rules (master_min_span 8 and 9, with and without min_total_variation), with several sets of unknown keys in the pinning system. The jointly built paths behave as described and print the "scored N drawn ... that follow the shape rules" line. The one failure is the Should fix below.

Privacy: nothing found. The seven-digit strings in the tests are either in tests/fixtures/pinning.json or are computed from the fake fixtures. The commit author is the noreply address and it has the Co-Authored-By trailer. The PR text states only general scenarios.

Docs: README, docs/design.md and the feature design agree with the code. D64 already exists and covers this step, so no new log entry is needed.

Should fix (1): unit_pair_summary can raise an uncaught NoShapedBitting after a successful search, so the run exits 1 with a traceback and writes no file (inline comment, with a reproduction).

Optional (1): a bad key in a jointly built group nulls the whole group, and the tally mixes the group's keys (inline comment).

Everything else reads well: the filtering sits in one place, the RNG streams are unchanged when the rules are off, and the tests are independent of ShapeRules.

Review written with AI assistance (Claude).


Generated by Claude Code

Comment thread sfic_solver/solve_system.py Outdated
Comment thread sfic_solver/solve_system.py Outdated
…a group

Review feedback on #38. The summary of chance cross-operation drew 300 typical
masters with a bounded search each; with a tight but satisfiable rule set one
of them could give up and raise, after a successful search, so the run exited 1
with a traceback and no file. With the rules on it now draws the typical
masters once for each master, reuses them in turn when few are found, and
leaves the summary out when there are none; with every rule off it draws what
it always did, so seeded output is unchanged (checked again on 12 runs).

For keys built together, the NOT SOLVED reason named every key and mixed their
tallies. A key is now blamed only if it broke a rule in at least 80% of the
draws; the others say they were built together with it.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

@twilde twilde left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Approved

Re-review of head 216312a (one new commit on top of a1e32f3). Nothing blocks.

Earlier threads

  • Should fix, crash in unit_pair_summary: closed. I re-ran my reproduction (fake clean fixture, null unit_master, shape: {master_min_span: 9, min_total_variation: 26}) at seeds 1 to 6. Every run finishes with exit 0, no traceback, and the file written. The fix draws the typical masters once per master with shaped_candidates, reuses them in turn when few are found, and leaves the summary out when none are found. The new regression tests cover the tight-rule case (3 seeds), the empty pool and the reused pool.
  • Optional, blame in a jointly built group: closed. The new 80% rule blames only a key that broke a rule in most draws, and the others get "built together with X, which has none". I checked the unit_master plus control_b case end to end, and the new tests cover the three tally cases.

What I ran

  • Whole suite in a venv with only .[test] (scanner=false), on the head, which is current main plus the PR's two commits: 1030 passed, 16 skipped.
  • check_no_stray_data.py --history: clean.
  • CI on 216312a is green (all test jobs, No stray data files, CodeQL).
  • Rules-off byte-identity against main after the change: the clean fixture and the pinning fixture (null unit_master, area_d and control_b), 3 seeds each. Stdout and written keys were identical in all 6 runs, so the summary change did not disturb the unfiltered draw order.
  • A pinning-system run with tight rules (master_min_span 9, min_total_variation 24), 2 seeds. Both runs finish OK with the summary line printed.

Privacy: nothing new found. The commit is authored and committed as the noreply address, with the Co-Authored-By trailer. The test values are fake.

Docs: docs/design.md now describes the summary draws and the 80% rule, and it still agrees with the code. No new decision-log entry is needed, as D64 covers this.

No new inline comments.

Review written with AI assistance (Claude).


Generated by Claude Code

@twilde
twilde merged commit b3772a1 into main Oct 7, 2026
12 checks passed
@twilde
twilde deleted the shape-rules-solver branch October 7, 2026 00:30
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.

1 participant