Skip to content

Engine: GatheredScan/Card Cost-Model Cleanup — Range-Leaf Family - #1071

Draft
jbylund wants to merge 44 commits into
mainfrom
engine-cost-model-cleanup
Draft

Engine: GatheredScan/Card Cost-Model Cleanup — Range-Leaf Family#1071
jbylund wants to merge 44 commits into
mainfrom
engine-cost-model-cleanup

Conversation

@jbylund

@jbylund jbylund commented Aug 29, 2026

Copy link
Copy Markdown
Owner

Summary

Cost-model and routing accuracy work targeting GatheredScan/unique=card and the GatheredScan/StreamedSelect/PrintingCompose plan-ordering triangle. Two phases, 13 rounds, tracked across three docs:

Results: GatheredScan/card crossed from FAIL to PASS on bench_cost_model_agreement.py (median 0.67→0.81, 16%→26% within 25%). The engine's previously-largest routing error, GatheredScan vs StreamedSelect (tracked since #852, historical 87% ordered-right / 4.29µs mean regret), is now 97% ordered-right / 0.82µs mean regret — resolved as a side effect of the range-leaf feature fixes below, confirmed via bench_pairwise_ordering.py.

This branch also carries five earlier commits already on it before this work (34466eb2 through 97dc30c8, plus an unrelated is:hybrid/is:phyrexian mana-symbol parsing fix and small admin_resource.py/test_upsert_cards.py changes) — those predate and are separate from everything below.

Phase 1 — range-leaf feature estimation (lib.rs) and cost formula (cost.rs)

An autonomous round-by-round loop: propose one idea, implement it in an isolated worktree/build, validate with a genuine calibration/held-out split, gate on cargo test + cargo clippy -D warnings, confirm no latency regression via a same-build canary. Six kept fixes, one clean rejection:

  • Match-density depth proxy for the printing-varying-leaf scan-depth fallback (COMPOSE_CANDIDATE_SPAN_BIAS refit 2.1→0.7).
  • A second clustering-bias constant (COMPOSE_RANGE_AND_CLUSTER_BIAS = 1.1) for cross-index range ANDs, since the existing bias was fit for a different population.
  • Three downward scan_units scales (COMPOSE_RANGE_AND_BROAD_SCAN_SCALE = 0.7, COMPOSE_BARE_RANGE_BROAD_SCALE = 0.43, COMPOSE_SAME_RANGE_BROAD_SCAN_SCALE = 0.52) for the range_too_broad_to_narrow guard's full-corpus reset, in the specific shapes/acquire-branches it fires for — never a new exemption to the guard itself.
  • A lower fixed cost for zero-match rounds (GATHER_FIXED_COST_ZERO_MATCH_NS = 42.0 vs. the existing 169.6) — GatheredScan's cost formula, not a feature bug, under-priced by ~4x.
  • One idea rejected: an independence-product combination for domain_cards was proven mathematically incapable of fixing an undercount. Logged, not shipped.
  • One round discarded post-hoc (costcell/10-card-residual, kept as an unmerged branch): weak held-out signal plus a confirmed small real-impact ceiling (7% of real routing regret) didn't justify the added complexity.
Round Fix Held-out result
1 Match-density depth proxy 908 improved / 36 regressed, within-25% 7.5%→36.7% on the affected shape
3 Cross-index-range clustering bias 433/117, 8.60M→8.02M abs error
4 Broad-guard scale (cross-index range) 166/15/0 tied, 5.40M→1.90M
6 Broad-guard scale (bare single range) 1,704/31, 93.3M→16.0M
7 Broad-guard scale (bare/fused range, PrintingCompose) 6,422/33, 304.8M→57.6M
9 Zero-match fixed cost (cost.rs) 4,577/369, 530,256→103,110 ns error (5.1x)

Phase 2 — regret/pairwise-ordering-driven, and two "don't build this" findings worth having on record

A fresh bench_regret_matrix.py --mode realistic run showed Phase 1's later rounds (8-10) had drifted onto a small real-impact population (candidates acquire, 11% of routing regret) versus the range-leaf family's 64%+ share — a methodology gap (uniform-sampled error magnitude finds errors; it doesn't rank real-traffic cost). Phase 2 corrected course:

  • Round 11: re-verified #852's GatheredScan/StreamedSelect pair against the accumulated Phase 1 fixes — resolved (97%/0.82µs, see Summary). Surfaced a seemingly large new lever: GatheredScan vs PrintingCompose and PrintingCompose vs StreamedSelect, worst pairs in the engine under plane-acquire, up to 27µs mean regret.
  • Round 12: traced the mechanism (the Plane acquire branch never sets PrintingCompose's build-cost features) and found it's provably inertPrintingCompose is structurally excluded from ever winning under Plane-acquire scope (CandidatePlan::of(PrintingCompose) => None), confirmed empirically (applicable in 71% of sampled queries, picked in 0). The apparent 27µs lever was a bench_pairwise_ordering.py measurement artifact — it scores every pair regardless of whether both plans are reachable in that acquire's scope.
  • Round 13: investigated whether widening that scope (giving PrintingCompose a real executor under Plane acquire) would be worth doing, given real 142-149µs margins Round 12 measured against GatheredScan/StreamedSelect. Fresh measurement against the actual incumbent winner (PlanePopcountOrder, not those two) found PrintingCompose never wins — 0/3,209 real explain_analyze rows, even at extreme limit sweeps (closest: still 1.43x slower at limit=1,000,000). A full design doc was still written for a future implementer, but the recommendation is not to build it — there's no value on the table.
  • Round 15: found via a spot check on the highest-latency real queries in a 211k-query sample — a genuine correctness bug, not a calibration gap. acquire_plan_features's nothing_to_verify check ORed two whole-query claims (plane_leaves_nothing_to_verify and compose_leaf_nothing_to_verify) instead of ANDing each half's own independent claim, so a residual that was itself safe (a bare collection leaf) silently inherited a "nothing to verify" verdict from a rarity/border plane that still needed real per-printing checking. Concretely: cmc>=1 cmc<=5 border:black (unique=card) was routed to GatheredScan (predicted 326,263ns, real 1.02-1.29ms) over PrintingCompose (real 478-511µs) — routing now flips correctly, a ~2.3x real win. Shipped fix was initially scoped to a new rarity/border-specific helper.
  • Round 16: that scoping was itself an instance of the same bug shape, just narrower — keying on field identity (rarity/border) instead of the underlying behavior (does this existential plane's semantics require per-printing re-verification under Mode::Card?) left an identical gap for a divergent-legality-only plane. Confirmed live before fixing: f:oldschool (unique=card) predicted GatheredScan at 10,358ns against a measured 29,083ns (2.8x under), f:oldschool cmc>=1 cmc<=5 at 18,519ns against 58,625ns (3.16x). Traced the real executor (existential_plane_for) and found it grants no per-family carveout at all — "needs re-verification under Mode::Card" and "is this a printing-level property" are the same fact, not two concepts. Fix deletes the field-specific helper entirely: the check is now !plane_expr_is_existential(...), reusing the existing family-keyed classification (planes.rs's PLANE_BLOCKS) with no per-field branch anywhere in the router's cost code — a future existential field gets correct treatment automatically. Verified 6 property combinations directly (rarity/border/legality alone and paired). cargo test 173/173 (3 new property tests + 1 regression test), clippy clean, full confirmation pass flat/noise. Flagged, not touched: two other scattered card-vs-printing classifiers elsewhere in the codebase (estimator.rs, filter.rs) built for unrelated purposes — a candidate for a future unification, not part of this PR.

Phase 3 — a second, deeper bug found chasing Round 16's residual gap, and fixed

Rounds 17 and 19 both tried to close a remaining ~1.5-2x cost miscalibration on GatheredScan for compound existential-plane queries (cmc>=1 cmc<=5 border:black) via rate/calibration approaches (depth-based, leaf-count-based) and both correctly discarded their own attempts after failing held-out validation. Round 20's joint-refit attempt then found the actual reason all three failed: the ground truth they were fitting against — eval_domain/domain_cards itself — is wrong for this population, not the rate being fit.

  • Round 21 (investigation only) root-caused it with a falsifiable test: compose_printing_estimate's And arm only computes an exact card-domain intersection (best_other) when an existential leaf (border/rarity/divergent legality) has a card-invariant partner — a lone existential leaf ANDed only with an excluded arith-tuple leaf (cmc/power/toughness) falls through the gate entirely, even though it has a perfectly good exact popcount available on its own. Proof: adding any card-invariant leaf, even a near-vacuous 99.8%-selective one, flips the gate and makes eval_domain exact.

  • Round 22: fixed the gate (else if !card_invariant.is_empty() → plain else). Verified exact (ratio 1.000, not just improved) on every reproducer tested: cmc=1 border:black 0.624→1.000, cmc=1 border:white 0.113→1.000, cmc=1 r=mythic 0.021→1.000, and more. A broad sweep (3 fields × 13 widths × 11 leaf values) went from 5.8% clean to 89.3% clean. Declined to layer in two further "free" ingredients Round 21 had sketched — one doesn't cleanly apply given what the exact bits are still needed for, the other was found to be a previously-tried-and-reverted idea for a retired field (re-adding it risked resurrecting that exact bug) — both documented rather than forced through. cargo test 173/173 (including a new regression test verified to actually catch the bug on revert), clippy clean, full agreement table improved (no cell regressed), pairwise-ordering flat/improved, regret-matrix within noise, latency A/B with same-build canary consistent with a small, expected, real acquire-time cost for the ~1% of Mode::Card queries this population represents (real work that was wrongly being skipped before).

  • Round 23 (investigation only): the fix's own acquire-time tax (median +17.6µs on the newly-covered population) prompted a look for a cheaper alternative. A min()-of-independent-exact-counts approximation was tested and correctly rejected — it changes the routing pick on 16.3% of a broad sweep, and 79% of those changes are genuine regressions (real measured time worse). But it found something better while looking: indexes.pair_totals already computes exact disjoint-value-pair counts for border/rarity/frame/legality and is already wired into the same code path — it was just never extended to cmc/power/toughness, the same "answer already exists, not reached for this shape" pattern as Round 21's finding.

  • Round 24: extended PairTotals to cmc/power/toughness (mirroring the existing rarity dimension), added a bounded range-sum (a card's single cmc/power/toughness value makes any range over it a disjoint sum, so an arith range ANDed with one existential leaf is answerable by summing ≤14 precomputed cells — no query-time scan at all), and wired it ahead of Round 22's exact-but-O(matching_ids) fallback, which stays as-is for whatever the new path can't cover. Verified 429/429 exact agreement with Round 22's own answer on the same sweep. Covers 55.6% of the applicable population with the cheap path (100% at range width ≤6, tapering to 0% by width 9+ as rarer values get pruned from the table by its existing selectivity floor) — the rest still get Round 22's exact fallback unchanged, so ground-truth cleanliness is undiminished. Acquire time on the named reproducers: 10.6-17.4x faster. Store-build cost: +1.6% time (within noise), +0.046% size. cargo test 177/177 debug, 176/176 release, clippy clean, full confirmation pass clean. A further generalization (the same disjoint-sum trick for two arith fields ranged together, e.g. power and toughness simultaneously) was scoped out as a well-defined follow-up rather than folded in — confirmed cheap in principle (10-14 surviving joint values) but needs comparable new plumbing for a narrower population, and correctness for that shape is already covered by Round 22's existing fallback either way.

  • Round 25: re-attempted the joint refit against clean ground truth. Independently re-confirmed Rounds 22/24's fix on a fresh sweep (98.4% clean). Correctly handled a separate, still-open scan_units confound in the bare-existential-leaf population (Round 17's finding — print-era clustering breaks a uniform-search-depth assumption) by substituting the realized counter rather than letting it corrupt the fit. The refit itself still fails, for a genuinely different reason than Rounds 17/19/20: not corrupted data this time, a real nonlinearity — eval_domain spans 50-100x across this population, and a single flat linear rate can't serve both ends. The held-out pooled average improves, but the four highest-selectivity leaves (including the flagship reproducer) get worse on 100% of held-out rows while five minority leaves improve — net 2.67x worse total absolute error for the population that matters most. Correctly discarded rather than shipped on a misleading pooled win. Concrete next step identified: a saturating/selectivity-banded rate rather than a flat linear one — not attempted, out of this round's scope.

What's still open

  • Round 8's card-mode residual-selectivity mechanism (is:vanilla-shaped queries) and an Or/negation population invisible to the current benchmark sampling — both noted in Phase 1's doc, neither pursued further after Round 10's discard.
  • A latent (currently harmless, flagged) aliasing risk in Round 9's zero-match gate, where matches == 0 also fires for GatheredScan costed under RANGE_ACQUIRES branches for a different reason (unset default, not real emptiness).
  • Both Phase 2 design docs (Rounds 12, 13) are written to the standard where a future session could reopen them if new data changes the picture — but the current recommendation on both is "don't build this."
  • One remaining outlier in Round 22's sweep (r=special) is a separate, already-documented card_invariant_domain_exact width-invariance bug, not this fix's territory.
  • Round 24's multi-arith-field generalization (two arith fields ranged together, e.g. power+toughness) is scoped out as a well-defined follow-up — correctness is unaffected either way, it's a pure speed opportunity for a narrower population.
  • The residual GatheredScan cost-formula miscalibration for compound existential-plane queries (~1.5-2x, no longer a routing-correctness issue since Round 22 fixed the actual bug — this is now purely a magnitude-precision question on a plan that's already being picked correctly) remains open after four rounds (17, 19, 20, 25) of correctly-diagnosed-and-discarded attempts. A saturating/banded-rate model is the identified but unattempted next approach. Round 17's scan_units print-era-clustering confound for bare existential leaves is also still open, separately.

Test plan

  • cargo test --manifest-path card_engine/Cargo.toml (169 passed) on every kept commit
  • cargo clippy --manifest-path card_engine/Cargo.toml --all-targets -- -D warnings clean on every kept commit
  • Held-out calibration/validation split for every fitted constant
  • bench_regret_matrix.py + bench_query_latency_ab.py with a same-build canary on every kept round
  • bench_pairwise_ordering.py re-verification of the GatheredScan/StreamedSelect pair (Round 11) and the PrintingCompose-under-Plane-acquire investigations (Rounds 12-13)
  • Independent re-measurement of the pooled GatheredScan/card cell after Phase 1 (not just each round's self-reported numbers)
  • Broader make test/integration suite (not yet run against the accumulated branch tip)

jbylund added 15 commits August 28, 2026 15:53
… From a Range's Scatter

Follow-up to the previous commit -- WalkCheckpoints fixed printings_walked
exactly (predicted 1,998 against a realized 1,942 for otag:triggered-ability)
but barely moved predicted_ns, because the walk term is a small share of
PrintingCompose's total and something else was dominating the miss.

Found it: compose_printing_estimate's CollectionCmp arm charges a card-space
collection leaf's build at COMPOSE_SCATTER_PER_PRINTING_NS, whose own doc
comment says it is calibrated for "range-slice scatter into the printing
bitmap (cheap: no card cursor)". A card-space leaf's actual build is
ids_of() + broadcast_card_ids_to_printings -- which needs exactly the card
cursor that comment says this rate assumes away: a lookup into offsets[c]/
offsets[c+1] per id, then a variable-width fill, against a range's single
contiguous write. Printing-space collection leaves (art_tags/is_tags) were
never mis-priced this way -- their build is a contiguous bits() copy, the
same shape a range's scatter is, which is exactly why arttag:-shaped queries
never showed this error anywhere this session.

Backed a rate out of the three otag: queries already measured, with
printings_walked now correct and every other term computed from measured
features: residual scaled cleanly with scatter_printings (not a flat offset)
at 1.41 / 1.30 / 1.31 ns/printing -- tight enough (2.7-2.9x the range rate,
all three within 8% of each other) to be real, though from 3 points on one
corpus size rather than the corpus-scaling sweep the surrounding rates were
fit with. Added a separate ComposeEstimate/PlanFeatures field
(collection_broadcast_printings) rather than just changing the rate
scatter_printings rides, so ranges keep their own, unaffected, correctly-
calibrated rate.

Verified against the real corpus, all three queries: `unique=printing`
ordered by EDHREC now routes to the genuinely-faster StreamedSelect, both
plans predicted within 0.90-1.08x of measured.

| query | predicted (SS / PC) | measured (SS / PC) | picked |
|---|---:|---:|---|
| otag:triggered-ability | 44.5k / 58.6k | 47.1k / 64.8k | StreamedSelect (was PrintingCompose) |
| otag:cycle | 25.4k / 45.0k | 25.4k / 41.7k | StreamedSelect (was PrintingCompose) |
| otag:activated-ability | 29.8k / 49.2k | 29.0k / 50.0k | StreamedSelect (was PrintingCompose) |

This is the routing fix the previous commit's walk-length correction alone
could not deliver -- the two together close both the feature-accuracy gap and
the routing decision for this query class.

plan_cost_model_matches_gold: 90.9%/92.0% across two runs (down from a
94.3%/93.2% baseline), but re-running confirmed the misses are the exact same
pre-existing population present since before ANY of this session's changes
(o:annihilator, t:creature usd<5, cmc>=15 -- same queries, same ratios,
fluctuating between MISS/near-tie/pass run to run on borderline timing, none
touching a card-space collection leaf). kw:Flying, the one collection-field
query the gold set covers, stays GOLD in both runs. No stable regression;
the fixed 88-query set still has nothing dense enough in this dimension to
positively exercise what changed here, same caveat #1005's gold-test gap
noted originally.

cargo test -p card_engine: 156 passed, 0 failed. Clippy clean.
The field landed in PlanFeatures/plan_cost but was never wired into
acquire_facts_to_pydict, so explain()/explain_analyze() (and every
Python-side cost-model tool built on them) had no way to see the new
feature this PR introduces. Found while grading this PR's own accuracy
impact with bench_cost_model_agreement.py/bench_cost_error_percentiles.py.
…g_estimate

Both found investigating GatheredScan[printing_compose]'s worst-in-survey
cost-model spread (bench_cost_error_percentiles.py, p90/p10 up to 11-13x)
and scan_units[printing_compose]/card's flagged OVER-COUNTS cell
(bench_feature_accuracy.py, up to 344x).

1. `domain_hint` is a CARD count -- its one consumer, acquire_plan_features's
   domain_cards, feeds it straight into eval_domain. Its "2+ card-invariant
   planes intersect" branch scaled the exact card popcount by
   n_printings/n_cards before storing it as exact_domain/domain_hint -- the
   conversion result's own (correctly PRINTING-space) tightening needs,
   reused unconverted for a card-space consumer. Confirmed live:
   `devotion:w c:u usd>5` predicted eval_domain: 1705 against a realized
   cards_visited of 108 (15.8x over); fixed, eval_domain: 553 (the exact,
   unscaled 2-leaf intersection -- 3.08x tighter, matching n_printings/n_cards
   on this corpus exactly; still not perfect since usd>5 isn't folded into
   this tightening).

   Paired per-query diff (same seed, before/after, 30,446 shared queries):
   601 queries (2.0%) had eval_domain change. Of those, 601/601 improved
   against realized cards_visited, 0 regressed -- several now land exactly
   on the true count (e.g. `f:timeless r>=rare`: 17793 -> 5854, real 5854).

   The OTHER input to domain_hint's fold -- each child's own `.result` --
   is ALSO printing-space for every leaf type here (ColorCmp calls
   color_cmp_value_total(..., Mode::Printing) explicitly), so it was
   handing the same wrong-space value to domain_cards. Dropped rather than
   converted: none of these leaves currently expose a matching CARD count
   on ComposeEstimate to fold in its place (the values exist internally --
   color_cmp_value_total, bare_numeric_field_count, the legality/devotion
   popcounts before their own printing-space scaling -- but adding a real
   `card: Option<usize>` slot per leaf, so each is unambiguous about which
   space it's in rather than inferred from calling convention, is its own
   change). Falls back to the already-correct calibrated_balls_into_bins
   estimate instead, which is strictly safer than a confirmed unit mismatch.

2. scan_units (the printing SPAN under the candidate cards) fell back to
   `domain_cards * printings_per_card * COMPOSE_CANDIDATE_SPAN_BIAS` -- a
   second, lossy statistical conversion stacked on top of an already-exact
   card count whenever exact_result_total(composed, Mode::Card) answered
   it. Added exact_result_total(composed, Mode::Printing) as a preferred
   source, gated on est.candidate == est.result (nothing tightened away
   from the exact leaf/pair value) so it only applies where it's provably
   the same domain scan_units needs.

Test plan:
- cargo test --release: 166/166 passing (165 + 1 new regression test,
  domain_hint_is_card_space_not_printing_scaled), including every
  differential fuzz test (fuzz_row_identity_matches_reference,
  force_plan_differential_agreement, routed_agrees_with_gathered_scan_across_page_sizes)
  -- this changes cost ESTIMATES only, never which rows a query returns.
- cargo clippy --release --all-targets: clean (one pre-existing unrelated
  dead-code warning).
- Paired per-query diff against the pre-fix build, same seed: 601/601
  changed predictions improved, 0 regressed.
…verloaded Space

Commit 2 of the card/printing/artwork estimate cleanup (follows the
domain_hint unit-mismatch fix). Adds card: Option<usize> and
artwork: Option<usize> to ComposeEstimate, populated wherever a leaf has
one for free (ColorCmp/Legality/CollectionCmp/border via ValueTotals,
cmc/power/toughness/devotion via their own already-computed card
popcounts), and uses card to retire the old domain_hint field entirely --
one unambiguous card-space slot instead of a value whose space had to be
inferred from calling convention.

acquire_plan_features's domain_cards now consults est.card in two places
it previously couldn't reach:

- Unconditionally as a final tightening (not just when est.candidate !=
  est.result): found live, `id:g border:white` took the est.candidate ==
  est.result branch (both 5131, border:white's own printing count), which
  uses est_cards -- exact_result_total's OWN, narrower 2-child-pair-table
  check, which doesn't cover a ColorCmp+TextExact(Border) combination and
  fell back to calibrated_balls_into_bins's 2,756 guess despite
  compose_printing_estimate's own And arm already knowing the exact
  answer, 576. Scoped to genuine And filters only (`is_and`), not bare
  leaves -- see below.
- As a range_too_broad_to_narrow exemption, the same fix #1066 already
  made twice for compose_leaf_nothing_to_verify/plane_leaves_nothing_to_
  verify: an exact card count in hand shouldn't be thrown away for a
  full-corpus fallback tuned for when there isn't one.

Two real, pre-existing bugs surfaced and were scoped around rather than
fixed blind, each checked against real data before landing on the fix:

1. RangeCardCounts::distinct_cards/distinct_artworks (read by
   exact_result_total's bare-range and rarity Mode::Card/Artwork arms)
   gives wrong-by-a-wide-margin answers for broad ranges (`eur>0.16` real
   31,724 cards, that path 19,992). Response: never wired bare-range or
   rarity leaves' card/artwork into ComposeEstimate at all -- .card stays
   None for both, matching the original conservative behavior. A
   separate bug worth its own investigation, not something this pass
   fixes.

2. Folding each child's OWN card count into the And's card via `min`
   (mirroring how domain_hint's retired children_min fold worked) is
   unsound for a leaf narrow_rec would decline as too broad to narrow
   with (border:black at 87%, already documented elsewhere in this file
   as exactly this failure mode) -- a leaf's own card count individually
   correct, but not a valid stand-in for what narrow_rec would actually
   narrow to. Response: And's own .card comes only from the verified-safe
   joint intersection (best_other/the arith-tuple merge, a real
   eval_planes AND, never a per-leaf bound), never from folding
   individual children's own counts.

Verified with a paired per-query diff, same seed, before (pre-domain_hint-
fix baseline) vs after, 30,416 shared queries: 671 changed (2.2%), 667
improved against realized cards_visited, 4 regressed (all modest misses on
near-universal 5-color-identity checks, not wild ones) -- down from 809
and 250 during earlier, more broadly-scoped attempts at this same fix that
were checked against real data and walked back.

Test plan:
- cargo test --release: 166/166 passing, including every differential
  fuzz test (fuzz_row_identity_matches_reference,
  force_plan_differential_agreement,
  routed_agrees_with_gathered_scan_across_page_sizes) -- this changes
  cost ESTIMATES only, never which rows a query returns.
- cargo clippy --release --all-targets: clean (one pre-existing unrelated
  dead-code warning).
- Paired per-query diff against the domain_hint-fix baseline, same seed:
  667/671 changed predictions improved, 4 regressed (documented above).
… artwork} Triples

Commit 3 of the card/printing/artwork estimate cleanup. Introduces
SpaceEstimate { printing: usize, card: Option<usize>, artwork: Option<usize> }
and makes both result and candidate this type, instead of bare printing-space
usizes with card/artwork bolted on as separate fields playing an ambiguous,
single role.

This retires the standalone card/artwork fields from commit 2 in favor of
result.card/result.artwork, and gives candidate the same card/artwork
companions for free via the existing min-fold (SpaceEstimate::min/::add
replace four hand-written parallel min/sum chains with one operation
reused for And and Or). Directly motivated by the next fix in the queue
(scan_units's card-mode over-counting, still unresolved -- see
bench_feature_accuracy.py's scan_units[printing_compose]/card cell,
unchanged by every commit so far): that fix needs a printing/card pair
guaranteed to describe the SAME underlying set, and a bare usize field
next to an unrelated Option<usize> can't express that guarantee -- a
SpaceEstimate can.

No behavior change: verified via the same paired per-query diff used to
check commit 2, same seed, against the same pre-domain_hint-fix baseline
-- 671/30,418 changed, 667 improved, 4 regressed, an EXACT reproduction
of commit 2's numbers. This commit is a pure reshaping of the same
values into a type that states which space each one is in, not a
behavior change.

Test plan:
- cargo test --release: 166/166 passing, including every differential
  fuzz test.
- cargo clippy --release --all-targets: clean (one pre-existing
  unrelated dead-code warning).
- Paired per-query diff against the domain_hint-fix baseline, same seed:
  byte-identical to commit 2 (667 improved, 4 regressed, 0 new
  divergence from the reshaping itself).
…Model Error

Commit 4 of the card/printing/artwork estimate cleanup. scan_units[printing_
compose]/card was flagged OVER-COUNTS by bench_feature_accuracy.py (p100
344.36x) and was UNCHANGED by every prior commit in this stack -- it's a
separate quantity from domain_cards/eval_domain, computed by re-deriving
the printing span from a card count via a statistical average
(domain_cards * printings_per_card * COMPOSE_CANDIDATE_SPAN_BIAS) instead
of reading it directly. Two real fixes and two real mistakes found and
corrected against live data before landing:

1. Added `exact_domain: Option<SpaceEstimate>` to ComposeEstimate: the
   And arm's best_other/arith-tuple-merge intersection, captured BEFORE
   `result` could be tightened any further by pair_bounded_min, so
   (unlike `result`) its printing/card/artwork fields are guaranteed to
   describe the same set. `scan_all` uses `exact_domain.printing`
   directly when `est.exact_domain.card` is the side that actually won
   `domain_cards`' tightening (`exact_domain_won`) -- exact, not
   average-case, since `card_bits_span_total` (already built for
   commit 2) sums each candidate card's FULL printing range, which is
   the actual quantity `printings_examined` measures.

2. Restored (from an earlier revert) a leaf-level `exact_printing_span`
   fix for BARE leaves, but gated on `composed_card_invariant`: for a
   card-invariant field, a leaf's exact MATCH count and the matching
   cards' full printing span are the same number (every printing of a
   matching card matches too), but for a printing-VARYING field
   (border/rarity/legality/year/date/price/cn) they are not -- a card
   can have non-matching printings that still get walked. Applied
   unconditionally first, checked directly against real data, found to
   regress 1,077 queries, every one printing-varying; correctly scoped,
   it improves 772 with only 79 residual regressions.

3. Tried `.min()`-ing the `exact_domain` branch against the statistical
   estimate as a safety net, on the (wrong) assumption that "smaller is
   safer" the way it provably is for `domain_cards`. Checked against
   real data: it made things worse both by row count (732/119 vs
   772/79) and by total magnitude, because the true value is sometimes
   BIGGER than both candidates -- taking the min just doubles down on
   whichever side under-shoots. Reverted; `exact_domain`, unlike
   `est.card` for `domain_cards`, has no mathematical upper-bound
   guarantee (a sibling `best_other`/the arith-merge didn't capture can
   narrow the real candidate set further without it knowing), so there
   is no general "take the smaller number" safety net available here.

4. The remaining 79/1,077 regressions were first measured by ROW COUNT
   alone, which overstates how mixed this is: summed by MAGNITUDE
   instead, the 79 residual regressions add 134,494 units of error
   against 6,805,680 shed by the 772 improved rows (worst single
   regression 6,235; best single improvement 74,418, an exact match).
   A raw count conflates a 74,418-unit fix with a 3->4-against-a-true-1
   noise swing.

Verified with the same paired per-query diff methodology as the rest of
this stack, but reading `scan_units`/`printings_examined` instead of
`eval_domain`/`cards_visited`: 851/37,521 changed, 772 improved, 79
regressed, total absolute error across the whole affected population
8,008,106 -> 1,197,076 (an 85% cut). `eval_domain`'s own numbers,
re-verified unaffected by this commit: 667/671 improved, total error
1,531,310 -> 64,260 (96% cut).

Test plan:
- cargo test --release: 166/166 passing, including every differential
  fuzz test.
- cargo clippy --release --all-targets: clean (one pre-existing
  unrelated dead-code warning).
- Paired per-query diff on scan_units, same seed: 772 improved / 79
  regressed by row count, 85% total-error reduction by magnitude.
- Paired per-query diff on eval_domain, same seed, re-run to confirm no
  interaction: unchanged at 667/671 improved, 96% total-error reduction.
…can Depth

card mode's push_card_matches settles a card-invariant field (cmc/power/toughness/
color identity/legality/border/collection) in exactly one printing under the default
prefer: the plane pass already proved every printing of a matching card matches, so
the first printing checked is the pick, with no rescan. The scan_units fallback still
priced these as domain_cards * printings_per_card * BIAS, as if a full reprint history
had to be walked -- a structural gap, not a selectivity artifact, that pinned bare
cmc/power/toughness/color-identity queries at a median 3.08x over-prediction (exactly
the corpus's average printings-per-card) across their whole population, saturating or
not.

Two separate downstream overrides were independently discarding the corrected value
back to a full-corpus estimate, so the fix needed three coordinated changes: a fourth
exemption on the existing range_too_broad_to_narrow guard (alongside the three already
there), the scan_all fallback itself, and a prefer-aware split on the nothing_to_verify
override (which used to always charge the full printing-space match count, a hedge that
only actually applies to non-default prefer, since a non-default prefer must score every
printing to find the best one).

All three gates share one condition, card_invariant_domain_exact, deliberately scoped to
bare leaves whose card count comes from a verified-exact source (not folded, not a range/
rarity estimate with a known undercount bug) -- checked directly against paired real data
at each step: an earlier, looser version of the nothing_to_verify split regressed an
all-arith-tuple-eligible And (cmc>=2 cmc<=3 pow>2, best_other's exact-intersection
mechanism structurally excludes arith-tuple fields) before being tightened to the same
proof the other two gates already require.

Paired diff against pre-fix (30020-query sample, unique=card only): 897 improved / 2
regressed, total absolute scan_units error 91.5M -> 84.7M (7.4% cut pooled across every
plan/acquire; far larger, ~85%+, isolated to the bare card-invariant population this
targets). The one regression (f:oldschool) is oldschool's known, pre-existing per-printing
legality divergence, already tracked separately via legal_divergent -- out of scope here.
Item 1 of the cost-model cleanup punch list stops being a single measure-
then-implement pass and becomes an ongoing autonomous-loop target, so it
needs its own doc rather than growing a multi-round ledger inside the
4-item punch list. Records Round 0 baseline (16% within 25%, measured
against an isolated release build of 97dc30c) as the starting point for
the loop.
…sity

The GatheredScan/card scan_all fallback for printing-varying leaves (price_usd/
eur/tix, collector_number_int, released_at, or an And of them) priced every
candidate card at the corpus-wide average reprint depth (printings_per_card *
COMPOSE_CANDIDATE_SPAN_BIAS), regardless of how selective the query's own
predicate is at the printing level. Replaced with a match-density depth proxy:
expected_depth = (printings_per_card + 1) / (density + 1), where density =
printing_matches / domain_cards is the query's own average matching-printings-
per-matching-card, modelled as the expected position of the first match among
printings_per_card uniformly-random slots. Both inputs (printing_matches =
est.result.printing, domain_cards) are already computed earlier in the same
acquire branch -- no new index probe, no per-query scan, cost independent of
match/printing/candidate count, so this does not repeat the historical 23.6x
acquire-time regression from unconditionally scanning a newly-admitted
population (see the parent doc's "Explicitly considered and rejected" link).

COMPOSE_CANDIDATE_SPAN_BIAS, now applied on top of a selectivity-aware term
instead of a bare average, was refit by a manual sweep (captured one build's
raw domain_cards * expected_depth with the constant pinned to 1.0, then swept
the multiplier in Python against measured printings_examined -- the cap at
n_printings never binds at 1.0, so this is equivalent to rebuilding per
candidate) over 1,500 and2/and3 queries on the RANGE_FAMILIES fields,
unique=card: 0.7 minimized total absolute scan_units error.

Paired-diff against the old flat formula, same 1,500-query sample: 946
improved / 544 regressed, 29.6M -> 9.86M total absolute scan_units error.
Restricted to rows where domain_cards is not already an under-estimate of the
true candidate span (a separate, pre-existing bug this fix cannot reach --
~37% of the sample, see the doc's Round 1 notes): 908 improved / 36 regressed,
within-25% 7.5% -> 36.7%. The full bench_cost_model_agreement.py table moves
the GatheredScan/card cell only 16% -> 17% within a single uncontrolled run --
confirmed to be noise, not signal, since untouched cells move by comparable or
larger amounts between the same two runs.

cargo test (167 passed), cargo clippy -D warnings, and the full pytest suite
(3,376 passed, docker-integration and the pre-existing unrelated shared_cache
failures excluded) all pass unchanged. bench_regret_matrix.py shows no
anomalous transition. bench_query_latency_ab.py read +0.4us "B is SLOWER"
(CI [+0.2, +0.6]) against baseline, but a same-build canary run with the
identical protocol and zero code change produced -0.3us "B is FASTER"
(CI [-0.4, -0.2]) -- the same sign-and-magnitude swing from run-order drift
alone, so the real diff is not distinguishable from that noise floor.

See docs/issues/local-engine-gathered-scan-card-printing-varying-depth.md's
Round 1 entry for the full paired-diff breakdown and the domain_cards-ceiling
finding that bounds how far this fix (or any depth-side fix) can go.
Both variants tried (printing-space and card-space independence product for
And of 2+ different-index range leaves) cleared the pre-computation
self-check but failed the paired-diff: any probability-product combination
is bounded above by its smallest factor, so it can only shrink domain_cards,
never raise it -- and Round 1 found this population's domain_cards is a
floor that undershoots truth, not a ceiling that overshoots it. No code
change; reverted before commit.
`compose_printing_estimate`'s est_cards fallback (calibrated_balls_into_bins) used
COMPOSE_CARD_ESTIMATE_BIAS (1.78) for every population, including an And of 2+
different-index printing-varying range leaves (price/cn/released_at combos) where
exact_cards declines -- a population that constant was never fit against, and one
Round 2 proved is a floor undercount rather than the saturating overcount 1.78
corrects. Adds COMPOSE_RANGE_AND_CLUSTER_BIAS (1.1) and is_cross_index_range_and to
route that specific shape to its own bias, reusing balls_into_bins_effective unchanged.

Calibration/held-out split (hash of query string, 1,500 and2/and3 RANGE_FAMILIES
queries, unique=card, same population as Rounds 1-2):

  calibration half (n=758): total abs scan_units error 8.70M (1.78) -> 8.31M (1.1)
  held-out half    (n=742): total abs scan_units error 8.60M (1.78) -> 8.02M (1.1)
                             433 improved / 117 regressed / 192 tied

Held-out price-triple subset (usd/eur/tix, 2+ of them, 363 rows) improves
proportionally in line with the whole population (213/68/82) -- no sign of the
correlation risk flagged in Round 2, since this is a flat rescaling of the existing
ball-count math, not a combination formula across leaves.

Correctness gates green (cargo test, cargo clippy -D warnings). Regret matrix and
latency A/B show nothing beyond the documented same-build canary noise floor.
…nd Guard

The range_too_broad_to_narrow guard in acquire_plan_features's PrintingCompose arm
resets both eval_domain and scan_units to the full corpus whenever a cross-index
range-And's min-folded printing_matches is too broad a fraction of n_printings to
trust domain_cards (found by Round 3 mid-investigation, ~24-25% of the and2/and3
RANGE_FAMILIES population). Re-derived that split fresh: 372/1,500 (24.8%) guard-fired.
Measured the real GatheredScan counters on those rows: eval_domain (n_cards) is EXACT
(0 total absolute error against real cards_visited, all 372 rows) because real
card-space narrowing gives up at the same threshold, but scan_units's n_printings
ceiling is real but loose, at a stable ~0.70 (mean 0.697, median 0.713) -- confirming
Round 3's ~69% finding independently.

Adds COMPOSE_RANGE_AND_BROAD_SCAN_SCALE (0.7), applied to scan_units alone (eval_domain
is left untouched, since scaling an exact number down would reintroduce the under-charge
the guard exists to prevent) whenever is_cross_index_range_and holds -- reused unchanged
from Round 3. This scales what the guard's unconditional reset resets scan_units TO; it
is not a 5th exemption to the guard's own disjunction.

Calibration/held-out split (hash of query string, 372 guard-fired and2/and3 RANGE_FAMILIES
rows, unique=card, same population and precedent as Rounds 1-3):

  calibration half (n=191): total abs scan_units error 5.64M (1.0) -> 1.92M (0.7)
  held-out half    (n=181): total abs scan_units error 5.40M (1.0) -> 1.90M (0.7)
                             166 improved / 15 regressed / 0 tied
  held-out price-triple subset (n=71): 1.82M -> 0.75M, 62 improved / 9 regressed

Verified against the real rebuilt engine, not just the Python re-derivation: identical
numbers on the same 1,500-query sample.

Correctness gates green (cargo test, cargo clippy -D warnings). Regret matrix and
latency A/B show nothing beyond the documented same-build canary noise floor.
…Shape

No code changes. Fresh magnitude-weighted bucketing (n=30,892, isolated
release build against costcell/trunk) finds Rounds 1-4's target shape
(is_cross_index_range_and) dropped to 2.3% of pooled scan_units error at
median ratio 1.00, confirming the shipped fixes worked as intended. The
dominant remaining term (74.9% of pooled error) is the same
range_too_broad_to_narrow broad-guard reset firing outside
is_cross_index_range_and -- a population Round 4's own inline comment
already flagged as deliberately left unscaled, split across PrintingCompose's
mixed-leaf rows and the sibling CardRangePopcount/PrintingRangeScan arms'
single-bare-range-leaf rows (the largest single bucket in the table).
The CardRangePopcount arm's own range_too_broad_to_narrow guard resets both
eval_domain and scan_units to the full corpus whenever a bare single range
leaf under unique=card (e.g. usd>=0.24 alone, no And) is too broad a
fraction of its index to trust card_est on -- a completely separate acquire
branch from Rounds 3/4's PrintingCompose target, and Round 5's diagnostic
found it the single largest bucket in the whole pooled GatheredScan/card
error table (53.7% of pooled scan_units error, median ratio 0.64).

Re-derived the population fresh: 52-54% of card_range_popcount rows have
the guard fire (two independent 3,500-row samples). Measured the real
GatheredScan counters on those rows: eval_domain (n_cards) reads exactly
1.0 on 96.6% of rows (mean 0.975 -- a real but small tail from price-field
null-exclusion at extreme thresholds, not chased), while scan_units's
n_printings ceiling is real but loose, at a stable ~43-45% across all five
RANGE_FAMILIES (median 0.41-0.48 per field).

Adds COMPOSE_BARE_RANGE_BROAD_SCALE (0.43), applied to scan_units alone
(eval_domain left untouched, same reasoning as
COMPOSE_RANGE_AND_BROAD_SCAN_SCALE but independently verified rather than
assumed to transfer).

Calibration/held-out split (hash of query string, 3,500 guard-fired
bare-single-range unique=card queries):

  calibration half (n=1,765): total abs scan_units error 96.0M -> 15.4M
  held-out half    (n=1,735): total abs scan_units error 93.3M -> 16.0M
                               1,704 improved / 31 regressed

Verified against the real build (not just the Python-side sweep): scan_units
matched round(0.43 * n_printings) exactly on all 3,500 rows, and confirmed
no routing-decision change on the target population (CardRangePopcount still
wins 500/500 sampled queries in both builds; bench_regret_matrix.py's
card_range_popcount row is unchanged, 0.00 mean / 0% miss in both builds).

Also flags (not fixes) a second miscalibration found while checking the
arm's own doc comment: the sibling narrow-subset branch's scan_units =
card_est undershoots the real printings_examined by ~3x (median ratio
0.25-0.37 by field), contradicting that comment's "this makes both exact"
claim for the scan_units side specifically. Out of this round's assigned
blast radius; logged as a follow-up in the ledger doc.
…e Reset

Round 5's "single:range" bucket (53.7% of pooled GatheredScan/card scan_units
error) was only partly covered by Round 6's CardRangePopcount fix (~4.7% of
the pooled cell). Traced the rest: a bare range leaf falls through to
PrintingCompose whenever no sort permutation exists for the query's
orderby/direction, and a fused two-sided bound (eur>=a eur<=b) never reaches
CardRangePopcount at all regardless of permutation. Both land in
PrintingCompose's own range_too_broad_to_narrow reset, which is_cross_index_
range_and's own comment already flagged as unscaled for every shape but its
own.

Adds is_same_index_range_only (a bare range leaf, or an And of range leaves
sharing one printing-value index) and COMPOSE_SAME_RANGE_BROAD_SCAN_SCALE
(0.52) to scale scan_units alone when this shape fires the guard. eval_domain
is left untouched (measured exact, mean/median 1.000).

Held-out paired-diff (13,053 guard-fired rows, hash-of-query split): 6,598
calibration / 6,455 held-out. scan_units total abs error 310.8M -> 60.2M
(calibration, scale 0.52) and 304.8M -> 57.6M (held-out), 6,422 improved / 33
regressed on the held-out half. Verified against the real build: scan_units
matched round(0.52 * n_printings) exactly on all 13,053 replayed rows, 0
mismatches.

cargo test: 167 passed, 0 failed, 56 ignored. cargo clippy --all-targets -D
warnings: clean. bench_regret_matrix unchanged (95% printing_compose share
both builds). bench_query_latency_ab real diff +0.7us vs a same-build canary
of +0.5us, comparable magnitude and sign -- no detectable effect.
bench_cost_model_agreement GatheredScan/card 17% -> 18% (noise, expected --
this is a small slice of a much larger pooled cell).
@github-actions github-actions Bot added rust Pull requests that update rust code docs Improvements or additions to documentation card_engine Changes to the Rust query engine (card_engine) size/XL 1000-3162 changed lines labels Aug 29, 2026
jbylund added 10 commits August 29, 2026 09:05
…rd Error by Shape

Pivots off the printing-range-index family Rounds 1-7 fixed onto Prep::Candidates, the
other acquire branch feeding the same pooled GatheredScan/card cell. Finds eval_domain
exact and scan_units near-exact-to-under-predicting -- the opposite direction from the
pooled ns-space over-cost -- so neither size feature is the mismatched one. Isolates two
concrete GATHER_* rate/fixed-constant mechanisms (a ~4x-too-high GATHER_FIXED_COST_NS for
zero-match rounds, and card-mode's unconditional feats.matches=count ignoring residual
selectivity at low match rates) plus a third, structurally invisible Or/negation/nested-
paren population the existing flat-conjunction sampler cannot see at all.
…ounds

PhysicalPlan::GatheredScan's cost.rs arm now charges GATHER_FIXED_COST_ZERO_MATCH_NS
(42.0) instead of GATHER_FIXED_COST_NS (169.6) whenever matches == 0, gated the same
way the arm's own tier_ns > 0.0 neighbor already is. A Prep::Candidates-acquired
zero-match round collapses every OTHER term in the arm to zero (eval_domain,
scan_units, page_span/page_rows, artwork_seen_printings all derive from an empty
candidate list -- confirmed on a fresh sample, not assumed), so the whole prediction
used to read as GATHER_FIXED_COST_NS alone, a ~4x over-charge.

Held-out paired-diff (hash-of-query split, 9,890 zero-match GatheredScan/candidates
rows): calibration half (n=4,944) sets the constant to its median measured
plan_self_ns, 42.0; held-out half (n=4,946) reads 4,577 improved / 369 regressed / 0
tied, total absolute ns error 530,256 -> 103,110 (5.1x), median ratio 0.248 -> 1.000,
within-25% 0.1% -> 57.7%. GatheredScan/candidates agreement-gate cell moves 11% ->
30% within [0.8, 1.25] (median 0.57 -> 0.77) -- the largest single-round movement of
that number since baseline; the by-unique GatheredScan/card cell flips FAIL (0.69)
to PASS (0.80).

PlanFeatures carries no mode field, so this is one pooled constant across card/
printing/artwork; artwork's real zero-match cost reads a flat ~2x higher and is left
for a future round that can add a feature for it. Also found and checked a gate-
precision risk: the same shared PlanFeatures also zeroes for GatheredScan costed
under RANGE_ACQUIRES acquire branches, where eval_domain == 0 is an unset default
rather than a real empty candidate list and dispatch can pay a real, unmodeled
prepare_candidates rebuild -- pre-existing and not introduced here, verified
immaterial via bench_regret_matrix.py (total regret unchanged) and
bench_cost_model_agreement.py (no other cell moved), but noted as a correlated
proxy needing a lib.rs-side fix in a future round.
Independently re-measured (fresh isolated build, same protocol as every prior
checkpoint) against the accumulated 9-round state: GatheredScan/card crosses
the [0.8, 1.25] median bar for the first time (0.67 -> 0.81, 16% -> 26% within
25%), after six kept fixes and one clean rejection.
bench_pairwise_ordering.py shows GatheredScan vs StreamedSelect at 97%
ordered right / 0.82us mean regret (realistic), up from #852's own 87% /
4.29us baseline -- the printing-varying-depth loop's Rounds 1/3/4/6/7 closed
this pair's #1 remaining item (eval_domain accuracy in the narrowed regime)
without the connection being recognized at the time.

Re-verification surfaced a new, bigger, previously-unexamined problem:
GatheredScan vs PrintingCompose and PrintingCompose vs StreamedSelect are now
the two worst pairs in the engine, concentrated in the plane-acquire branch
(up to 27.21us mean regret, uniform). Traced to acquire_plan_features's Plane
branch never setting PrintingCompose's build-cost features, unlike every
other branch -- same class of bug as a historically-fixed one in this same
doc for a different acquire branch. New tracking doc opened:
local-engine-plane-acquire-compose-costing.md.
…bly Inert

Traced the root cause this doc named (Plane branch never sets PrintingCompose's
build-cost fields) and confirmed it's real, but found PlanScope::Plane (#829)
structurally excludes PrintingCompose from ever winning the argmin under a Plane
acquire regardless of how it's costed -- confirmed against real data (0/2,145
picked or cheapest-by-predicted across a 3,023-query sample) and empirically
(PrintingCompose does genuinely beat GatheredScan/StreamedSelect 10.7%/6.7% of
the time in reality, so the diagnostic gap bench_pairwise_ordering.py flags is
real, just unreachable by production routing). Fixing the costing would add
real per-query computation to acquire_plan_features -- which runs on every
live query, not just diagnostics -- to price a plan that can never be selected
here either way. No code changed; discarded per this round's own instructions
for a corrected finding.
…r Plane Acquire

Designs the PlanScope-widening fix Round 12 left open, then measures it against the
plan actually holding the argmin under Plane acquire (PlanePopcountOrder), which
Round 12 never checked. 0/3,209 real wins across four sampling regimes. Recommends
against implementing the widening; the design is recorded for the record.
…ax, Not a Probe

limit=1,000,000 is api_resource.py's own literal substitute for an uncapped
query (limit=None), and the engine clamps any limit to the true printing/card
cardinality regardless -- 97,206 printings, 31,508 cards in this corpus. So
the limit sweep's largest point is not a large sample of an open-ended range,
it's the range's actual maximum, reached exactly. Replaces the doc's inductive
hedge ("could a different regime hide past 1,000,000") with the closed
argument: there is no larger limit a real query could ever issue. The
remaining open question is predicate SHAPE, not limit magnitude.
…Available

Directly checked whether PlanePopcountOrder is ever inapplicable under Plane
acquire (the one scenario where Round 12's real 149us/142us margins over
GatheredScan/StreamedSelect could represent a genuine routing gap Round 13's
PlanePopcountOrder-relative measurement wouldn't see): 0/14,291 real
plane-acquire queries, fresh sample. Combined with #852's pairwise data
(PlanePopcountOrder already wins both comparisons 100% of the time, 0.00us
regret), confirms no ~100us+ improvement is on the table via routing here.
Found checking whether the router picked the best plan on the highest-
latency real queries in the 211k-query uniform sample. GatheredScan was
routed at 1,155,375ns for `cmc>=1 cmc<=5 border:black` (unique=card) when
PrintingCompose measured only 581,708ns -- a ~2x miss. Reconstructed both
plans' predicted_ns term-by-term: PrintingCompose's estimate is reasonably
calibrated (~1.13-1.2x), GatheredScan's is the actual bug (~3.1-4.0x under,
326k predicted vs 1.02-1.29M measured). residual_tier_ns100=0 ("nothing to
verify") is the likely culprit -- charging the formula's own floor for "there
is something to verify" would close most of the gap. Not fixed; this doc is
the starting point.
…f Rarity/Border

`residual_tier_ns100` was pinned to 0 ("nothing to verify") for every plane under
Mode::Card in the PrintingCompose-acquire branch of acquire_plan_features, on the
theory that Mode::Card only needs existence -- true for legality's #667 carveout,
but border and rarity are unconditionally printing-varying (needs_printing_verification:
"for rarity and border that is every leaf"), so the executor's existential_plane_for
still walks printings one by one via eval_plane_expr_for_printing regardless of what
the tier charged. GatheredScan predicted 326,263ns for cmc>=1 cmc<=5 border:black
(unique=card) against a measured 1.0-1.3ms, routing to the ~2.3x slower plan.

Root-caused two independent instances of the same conflation: the plane-side check
(plane_leaves_nothing_to_verify's blanket Mode::Card bypass) and the filter-side check
(compose_leaf_nothing_to_verify firing on a safe bare residual while ignoring what the
plane alongside it contains). Fixed by ANDing a filter-half check and a new
plane-half check (cost_plane_nothing_to_verify, gated by plane_touches_rarity_or_border)
instead of ORing two whole-query claims. Scoped to this one call site only --
prepare_candidates' own all_match_known is untouched (harmless there: the real
per-printing work runs through the separate existential_plane_for mechanism regardless).

Phase A: a bare cmc range alone routes correctly (GatheredScan really is faster,
184us vs 542us) -- the And with border is load-bearing. Realistic-mode sampling
found rarity/border combined with something else is a common query shape (61/2,424
candidates in 60s), but the large-regret sub-case (a broad card-invariant range
driving a large candidate domain) is a narrower slice -- most border/rarity queries
have small domains where the fix's added tier charge doesn't change the pick.

Before/after (real corpus): GatheredScan 326,263ns predicted (picked) / 1,155,375-
1,290,166ns measured -> PrintingCompose 514,272ns predicted (picked) / 478,333-
511,166ns measured, a ~2.3x real latency win on the reported query.

cargo test: 168/168 (167 pre-existing + one new regression test). cargo clippy
--all-targets -D warnings: clean. bench_pairwise_ordering.py (GatheredScan vs
PrintingCompose, printing_compose acquire): flat both directions (uniform 86%->86%,
realistic 90%->90%), consistent with the population being real but narrow.
bench_cost_model_agreement.py GatheredScan/card: unchanged (0.81 median, 25% within
25%). bench_regret_matrix.py --mode realistic: 47.2ms -> 45.2ms total regret, no
regression. bench_query_latency_ab.py --mode realistic with a same-build canary:
both within noise (canary -0.6us, fix +0.1us, 95% CIs both include zero) -- no
detectable change to general latency, as expected given the shape's rarity.
@github-actions github-actions Bot removed the size/XL 1000-3162 changed lines label Aug 30, 2026
@github-actions github-actions Bot added the size/XXL 3163+ changed lines - consider splitting label Aug 30, 2026
jbylund added 17 commits August 30, 2026 08:54
…dentity

Round 15 scoped `cost_plane_nothing_to_verify`'s Mode::Card bypass with
`plane_touches_rarity_or_border`, a plane-index-range check -- a field-identity
special case where a behavioral one already existed. That check reproduced the
exact bug Round 15 fixed, just for a different field: a plane that is
existential ONLY via a DIVERGENT legality leaf (no rarity, no border) still hit
the unscoped first disjunct (`Mode::Card && !touches_rarity_or_border`, true
whenever there's no rarity/border leaf) and read `residual_tier_ns100 == 0`.

Confirmed by reproduction on the real corpus (`oldschool` is the corpus's one
divergent format) before touching code:

  f:oldschool, unique=card:            predicted 10,358ns vs measured 29,083ns  (2.8x under)
  f:oldschool cmc>=1 cmc<=5, card:     predicted 18,519ns vs measured 58,625ns  (3.16x under)

printings_examined (6,037) over cards_visited (961) on the first query proves
the per-printing walk is real, matching Round 15's border finding exactly.

Traced why: `existential_plane_for` (lib.rs) grants NO per-family carveout at
all -- it forces `push_card_matches`'s per-printing walk for ANY existential
plane (rarity, border, or divergent legality) under Mode::Card, identically.
"Needs per-printing re-verification under Mode::Card" and "is this a
printing-level property" are the same fact, not two concepts to reconcile.
`plane_expr_is_existential`/`needs_printing_verification` (planes.rs) already
compute this per plane index via the family-keyed `PLANE_BLOCKS` table --
statically true for rarity/border, dynamically gated on `divergent_formats`
for legality. `cost_plane_nothing_to_verify` never needed a new field-specific
check; it needed to stop re-deriving a narrower, wrong version of a fact
`planes.rs` already had right.

Deleted `plane_touches_rarity_or_border`. `cost_plane_nothing_to_verify` is now
`plane.is_none_or(|expr| !plane_expr_is_existential(expr, divergent_formats))`
-- no `mode` parameter (provably redundant: `split_planes` only ever folds an
existential leaf into `plane` under `unique_is_card`, so existential implies
Mode::Card already), no per-family branch. A future printing-varying field is
handled correctly by which `PLANE_BLOCKS` entry it lands in, with no new arm
needed here. Net cheaper than Round 15's shape: one tree walk instead of two.

planes.rs gains doc-comment clarifications (no logic change) making the
"needs-reverification IS printing-level" framing explicit, plus 3 property
tests asserting the PLANE_BLOCKS family invariant holds for every plane index
across representative divergent_formats masks -- a future field wired into the
wrong family would fail these without any hardcoded index list. tests.rs gains
one concrete regression test mirroring Round 15's border fixture with legality,
asserting both directions (divergent format + range must charge; non-divergent
format + range must stay free).

After the fix: f:oldschool predicted 31,394ns vs measured 25,917-29,083ns
(ratio 1.08-1.21); f:oldschool cmc>=1 cmc<=5 predicted 51,276ns vs measured
56,959-58,625ns (ratio 0.90-0.91). Round 15's own rarity/border combinations
are unchanged (plane_expr_is_existential agrees with the old check whenever
rarity/border is present).

cargo test: 172/172 passed (168 + 3 property + 1 regression). cargo clippy
--all-targets -D warnings: clean.

bench_pairwise_ordering --seconds 300 (printing_compose acquire, both modes):
flat within noise (realistic 90%/3.06us -> 90%/3.01us; uniform 86%/4.94us ->
86%/5.06us). bench_cost_model_agreement --seconds 300: GatheredScan/card
unchanged (median 0.79, 25% within 25%). bench_regret_matrix --seconds 120
realistic: 41.4ms -> 41.6ms over ~56k queries, flat. bench_query_latency_ab
--sample 800 realistic, interleaved A/B/A with a same-build canary: canary
-0.8us [-0.9,-0.6], fix -0.7us [-0.9,-0.5] -- indistinguishable, no detectable
regression.

Found while checking for a duplicate property classifier before extending
planes.rs: estimator.rs's has_printing_varying_leaf and filter.rs's
printing_dependent/leaf_compares_printing_field each carry their own
documented, one-directional disagreement with the canonical table on legality
(for cardinality-estimation conservatism and verify-order heuristics
respectively) -- neither is this bug's shape, but a single canonical property
table both could read from instead is flagged as a candidate follow-up doc, not
attempted here.
Confirmed the reproducer's flagship gap is not a depth problem (real depth
~1.009 -- an early-exit walk that essentially never proceeds past the first
printing), correcting this doc's own framing: the actual driver is
eval_plane_expr_for_printing costing 3-4x more per call for a compound plane
(arith range AND existential leaf) than the flat GATHER_CARD_PASS_NS/
GATHER_RESIDUAL_FLOOR_NS charge assumes, not walk depth.

Depth does genuinely drive under/over-costing elsewhere in the broader
existential-plane population (2,564+4,431-row uniform/realistic sample,
corr(log depth, log ratio) ~0.4-0.5), so a real fix was built:
PlanFeatures::existential_extra_units, computed fresh via Round 1's
expected_depth formula independent of the pre-existing card_invariant_
domain_exact confound (which silently assumes depth 1 for a divergent
legality format via filter.rs::touches_printing_field's documented
Legality-is-card-invariant bias). Held-out calibration (hash-of-query
split) showed the fit doesn't hold up: Round 1's uniform-random-position
assumption itself underestimates depth for existential leaves whose match
position correlates with print era (border:borderless: model ~1.51 vs
real 6.09), and the fitted rate (0.036, from ~2 distinct repeated queries)
made held-out total absolute error WORSE, not better.

Reverted; card_engine/src/{cost.rs,lib.rs,tests.rs} are back to
costcell/trunk (git diff --stat costcell/trunk shows only this doc).
cargo test --release: 172/172 passed, unchanged from Round 16.
Cross-references the four card-vs-printing-level classifiers in card_engine
(planes.rs's PLANE_BLOCKS, estimator.rs's has_printing_varying_leaf,
filter.rs's printing_dependent, lib.rs's is_broadcast_leaf_shape) field by
field, resolves the one documented legality disagreement between the
estimator and the verify-order heuristic as deliberate (not a bug), and
sketches a canonical Locality table the estimator and filter-ordering
classifiers could share via thin ANY/ALL adapters, with a staged migration
order by risk and existing test coverage. No functional code change.
Confirmed the compound-plane per-printing evaluation cost mechanism Round 17 flagged but did not
chase, via two independent measurements: a confound-free kernel micro-benchmark
(eval_plane_expr_for_printing called directly on a real corpus witness, 2-4ns/call bare existential
leaf vs 30-33ns/call ANDed with a cmc/power/toughness range) and a confound-controlled real-corpus
matched-eval_domain paired-diff (79 pairs, hash-split: calibration half median 2.92 ns/leaf, held-out
half median 2.64 ns/leaf, 43% lower held-out delta-prediction error than baseline). Also corrected this
doc's own earlier paraphrase of the compiled plane shape: a two-sided range ANDed with an existential
leaf compiles to And([Or(<=14), Or(<=14), existential leaf]), not the narrower Or(width) previously
described.

Built the designed fix (count_plane_leaves in planes.rs, PlanFeatures::plane_extra_eval_leaves, a new
rate constant wired into both GatheredScan's and StreamedSelect's arms) and a passing regression test,
then ran the mandatory held-out check on the metric that actually drives routing -- held-out
predicted-vs-measured, not the paired-delta above -- and it fails: applying the calibrated rate moves
the flagship reproducer from under-predicting (0.5-0.65x) to over-predicting (~1.5x) the same measured
range, and held-out median |log ratio| gets worse (0.417 -> 1.145), not better. Root cause: the
existing GATHER_RESIDUAL_FLOOR_NS/GATHER_CARD_PASS_NS floor already absorbs an uneven,
population-dependent share of the compound-leaf effect (implied rate is negative at low leaf counts,
positive at high ones within the same leaf family) -- a single additive linear term cannot fix a floor
that is itself already leaf-count-dependent in an uncontrolled way.

Discarded. cost.rs/lib.rs/planes.rs/tests.rs are back to costcell/trunk -- git diff --stat reads empty.
Both independent measurements and the full negative-result diagnosis are recorded for whoever picks
this up next, including the recommendation to jointly refit the floor and a leaf-count term together
rather than patching the existing constant.
…imation Confound

Attempted the joint refit Round 19's own outcome section asked for (GATHER_CARD_PASS_NS,
GATHER_RESIDUAL_FLOOR_NS, and a new plane-leaf-count rate, fit together against a broad sample instead
of anchoring the floor). Building that sample surfaced why it can't be validated: eval_domain badly
overestimates the true candidate count (0.02-1.22x measured) for every existential leaf value except
the corpus-dominant one the flagship reproducer happens to use, and that confound is produced by the
exact PrintingCompose-acquire branch that also computes the new leaf-count feature -- the one acquire
path with a trustworthy eval_domain (orderby=name) never sets leaf count at all. No sample from the
current architecture can offer both at once, so a fit against this population is mostly fitting
eval_domain noise. Discarded; cost.rs/lib.rs/planes.rs/tests.rs revert cleanly to costcell/trunk.
Investigation only, no engine change. Round 20 found eval_domain/domain_cards is
wrong by up to 14x for an And of an arith-tuple range (cmc/power/toughness) and
an existential leaf (border/rarity/divergent legality) under Mode::Card, and that
this corrupted every rate fit measured against it. This round traces the exact
mechanism: compose_printing_estimate's And arm only computes an exact joint card
count via best_other, which requires a card-invariant partner leaf alongside the
existential one -- a lone existential leaf with no such partner (the flagship
reproducer's own minimal shape) never reaches that path, confirmed directly via
temporary instrumentation (reverted). Confirmed with a natural experiment: adding
any card-invariant leaf, vacuous or not, flips the gate and makes the estimate
exact. Also found border:black's own "clean" reading for the wider reproducer is
a second, unrelated coincidence (an untightened min-fold happening to discard a
near-100%-selective side), not evidence the shape is handled -- border:black at
width 1 is equally broken until the same gate fires.
Round 21 root-caused domain_cards/eval_domain being badly wrong (up to 47x)
for an And of an arith-tuple range (cmc/power/toughness) and exactly one
existential leaf (border/rarity/divergent legality) with no OTHER
card-invariant leaf present: compose_printing_estimate's best_other
computation required a card-invariant partner before running the
existential leaf's own exact popcount, so this shape's best_other/
exact_domain_cards stayed None for the whole function even though
popcount_with_bits(Some(e)) already handles an empty card_invariant vec
correctly.

Fix: drop the `!card_invariant.is_empty()` requirement -- a lone
existential leaf now runs the same popcount_with_bits loop a
card-invariant-paired one already used.

Reproducers (real corpus, isolated release wheels):
  cmc=1 border:black         eval_domain 4,893 -> 3,052  (ratio 0.624 -> 1.000)
  cmc=1 border:white         eval_domain 2,756 -> 311    (ratio 0.113 -> 1.000)
  cmc=1 r=mythic              eval_domain 4,710 -> 101    (ratio 0.021 -> 1.000)
  cmc>=1 cmc<=5 border:white  eval_domain 2,756 -> 1,766  (ratio 0.641 -> 1.000)
  cmc>=1 cmc<=5 r=mythic      eval_domain 4,710 -> 1,842  (ratio 0.391 -> 1.000)
  cmc>=1 cmc<=5 border:black  unchanged (24,734, ratio 1.088) -- already
                              tightened via a separate mechanism (arith_tuple_count)

Broad sweep (3 arith fields x 13 widths x 11 existential leaf values, 393
rows): 89.3% now land within 15% of real cards_visited, up from Round 20's
5.8% (13/223) on the same construction. r=special (0.012 bare selectivity)
is the one remaining outlier -- a separate, already-documented
card_invariant_domain_exact width-invariance gap (Round 17/19/20), not this
gate.

Two complementary ingredients from Round 21's sketch were investigated and
NOT shipped: (2) a cheaper precomputed count in place of eval_planes+popcount
doesn't cleanly apply, since the bits are still needed for the arith-ID-probe
merge that produces the TRUE joint for this exact population; (3) folding
folded.result.card in as a free .min() floor was tried once before for the
retired domain_hint field and reverted (a broad leaf's own count is not a
safe domain_cards substitute unless narrow_rec would actually use it to
narrow) -- reintroducing it here without an equivalent breadth guard risks
the same bug, so it was left out. Rarity's own leaf-arm .card blanking
(RangeCardCounts::distinct_cards bug) is untouched by this change.

Cost: the fix reuses already-existing machinery (popcount_with_bits, the
arith-ID-probe merge) for a population it was previously gated off for --
real, measured added acquire-time cost for that narrow population (e.g.
cmc=1 border:black: 709ns -> 42,083ns; cmc>=1 cmc<=5 border:black:
4,959ns -> 92,791ns), scaling with the arith range's own selectivity via
the merge's id-list materialization. Confirmed scoped to the newly-covered
population: four unrelated queries that already had a card-invariant
partner show no measurable acquire-time change.

Correctness gate: cargo test --release 173/173 passed (172 pre-existing +
1 new regression test, compose_and_arm_tightens_lone_existential_leaf_with_
no_card_invariant_partner, confirmed to actually catch the reverted-gate
regression). cargo test (debug, debug-assert tripwires) 173/173. cargo
clippy --all-targets -- -D warnings clean.

Broader regression check: bench_cost_model_agreement.py full table
improves (11/17 -> 12/17 cells within [0.8,1.25], no PASS->FAIL flips).
bench_pairwise_ordering.py (both modes): GatheredScan vs PrintingCompose's
gap meas/pred moves 0.91 -> 1.01 in realistic mode; all other pairs flat.
bench_regret_matrix.py: 38.8ms -> 42.9ms total regret (comparable to prior
rounds' own sample-to-sample noise). bench_query_latency_ab.py --sample 400
--seed 7 with a same-build canary: canary +0.4us [+0.2,+0.5], fix vs
baseline +1.0us [+0.6,+1.7] -- close but not fully overlapping, consistent
with the narrow affected population's rarity (~1% of Mode::Card queries).

Step 5 (the GATHER_CARD_PASS_NS/GATHER_RESIDUAL_FLOOR_NS/leaf-count joint
refit) was not attempted this round -- the broad sweep confirms its
eval_domain blocker is now cleared, but rebuilding Round 19/20's plumbing
and fitting script plus the same three-population held-out validation is
its own round's worth of work, deferred rather than compressed in here.

docs/issues/local-engine-domain-cards-existential-arith-and.md updated in
place with the fix, the sweep, and the deferred-refit rationale.
…joint-Sum Alternative Found

Investigation only, no engine code changed. Characterized Round 22's taxed population across the
full 3x13x11 sweep (median +17.6us acquire delta, concentrated on high-selectivity leaves and wider
ranges), then evaluated the proposed min(arith_count, exist_count) cheap upper bound against the
exact intersection: never exact, overestimates by a median 2x (up to 27.6x on narrow arith values
against rarity, a real if mild anti-correlation effect), and -- checked against the real candidate
plan set via explain()/explain_analyze -- flips the GatheredScan/PrintingCompose argmin on 16.3% of
the sweep, 79% of those flips costing real measured wall time (median +51.6us). Absolute magnitude
of the error, not the ratio, is what predicts routing damage: the flips concentrate entirely on
r=rare/common/uncommon's large bare counts, not the leaves with the worst ratios.

Found a better alternative than the cheap bound: cmc/power/toughness are single-valued per card, so
summing exact per-arith-value joint counts over a range is EXACT (no independence assumption, no
anti-correlation risk) -- validated 429/429 against real corpus data. Also found the existing
`pair_totals`/`pair_leaf_id`/`pair_bounded_min` machinery already implements this same disjoint-sum
pattern for border/rarity/frame/legality pairs, already wired into the And arm, but never extended
to recognize cmc/power/toughness -- the same "existing exact answer, unreached for this AST shape"
story Round 21 found for value_totals.border. Recommend keeping Round 22's fix unconditional and
scoping a follow-up round around extending pair_totals (small, immediate, covers the Eq/width-1
case for free) and a RangeCardCounts-style cumulative table (bigger, covers the ranged case too).
Round 22 fixed eval_domain correctness for a lone existential leaf ANDed with an
arith-tuple leaf (cmc/power/toughness), but paid a real O(matching_ids) acquire-time
tax to do it. Round 23 found the fix: indexes.pair_totals already stores exact
per-value-pair totals for border/rarity/frame/legality, and the same argument
applies to cmc/power/toughness -- each is single-valued per card, so summing the
per-value pair-total over a range's admitted values reproduces the true joint
exactly (a disjoint partition, not an independence assumption).

This round builds and ships that extension: three new dimension maps on
PairTotals (cmc/power/toughness, mirroring rarity's existing shape) plus their
own pruning-safety `_seen` lists, a new pair_range_sum summing over a range for
one field, and three new pair_leaf_id Eq arms feeding the existing pair_bounded_min
call site for free. Wired into compose_printing_estimate's And arm as the
preferred path ahead of Round 22's popcount+ID-probe-merge fallback, which stays
unchanged for every shape the new path declines (a card-invariant leaf present,
2+ existential leaves, 2+ distinct arith fields, or a value pruned by the
selectivity floor).

Exactness: 429/429 agreement with Round 22's exact fallback on the same 3-field x
13-width x 11-leaf sweep (0 mismatches in eval_domain or true_intersection).
Coverage: of the 324 rows where the shape applies, 55.6% get the new cheap path
(100% at width <=6, 33% at width 7-8 for cmc only, 0% at width 9+ -- exactly where
PAIR_MIN_PRINTINGS prunes each field's rare values); the rest fall back to Round
22's exact (pricier) path, unchanged.

Acquire-time: every one of Round 22's own named reproducers now answers via the
new path -- cmc=1 border:black 50,146ns -> 4,708ns (10.6x), cmc>=1 cmc<=5
border:black 92,875ns -> 5,333ns (17.4x). Store-build-time cost: +40ms (+1.6%,
within run-to-run noise) and +33KB (+0.046%) on the real corpus.

Confirmation pass: bench_pairwise_ordering/bench_cost_model_agreement unchanged
(12/17 and 10/12 cells, no flips), bench_regret_matrix improved 4.2% (41.1ms vs
42.9ms, same misroute categories), bench_query_latency_ab's real diff (+0.5us,
CI [0.0, 1.0]) smaller than its own same-build canary (+1.3us, CI [0.9, 1.8]) --
no detectable regression.

The multi-arith-field generalization (e.g. power+toughness ranged together) is
real and checked against the corpus (13-14 surviving joint values per pair,
10 for the full triple) but needs a compacted joint key plus its own pruning-
safety list and a cross-product-aware summing function -- scoped out as a
well-defined follow-up rather than folded in here; correctness for that shape is
unaffected either way since Round 22's arith_tuple_ids probe-merge already
answers it exactly, just without this round's speedup.

cargo test: 177/177 (debug), 176/176 (release), 3 new tests exercising
pair_range_sum/pair_leaf_id/single_arith_field directly against a hand-built
PairTotals. cargo clippy --all-targets -D warnings: clean.
…ean Data, Still Fails

Rounds 22/24 fixed the eval_domain confound Round 20 found blocking the GATHER_CARD_PASS_NS/
GATHER_RESIDUAL_FLOOR_NS/leaf-count-rate joint refit. This round rebuilt Round 19/20's plumbing
verbatim, re-verified eval_domain is now clean for population A (98.4% within 15%, fresh 243-row
check), confirmed population B's scan_units confound is real and severe (median 4x under-prediction)
and accounted for it via a counter-check substitution rather than letting it pollute the fit, then ran
the refit against clean data for the first time -- and it still fails held-out validation. The
mechanism is now precise: a flat additive ns/leaf rate multiplied by eval_domain cannot serve a
population whose eval_domain spans 50-100x across leaf values (border:black at 24,734 vs border:gold
at a few hundred), so the fit that improves minority-selectivity leaves overshoots the flagship
reproducer (border:black) by ~2.3x, worse than today's ~1.4-1.5x undercharge. Code changes reverted;
both tracking docs updated with the negative result and the concrete next step (a saturating/bucketed
rate, not a flat linear one).
…t Share

Comprehensive diagnostic sweep (agreement table, regret matrix, pairwise ordering,
feature accuracy) against costcell/trunk, no code changes. Finds essentially all
real routing regret concentrates in printing_compose acquire's Perm/OrderbyWalk
paging branches (already covered by a separate, active "Sigma" effort) and the
already-parked compound-existential-plane GatheredScan miscalibration. The one
open, in-scope, unshipped item found is Round 8's candidates-acquire card-mode
residual-selectivity mechanism (15% of all regret, never shipped after Round 9
fixed its sibling zero-match mechanism).
Two isolated release wheels (main @ ca01641, costcell/trunk @ ddba298), five
harnesses, paired and interleaved where the tooling supports it. Regret fell
41% and the #852 misroute dropped 83% in occurrence, both real -- but #852's
own internal "69%->97%" figure doesn't survive a head-to-head against main
(true number: 80%->90%), scan_units pooled feature accuracy got measurably
worse (1.00 clean -> 0.70 UNDER-COUNTS), pairwise ordering for GatheredScan
vs PrintingCompose regressed under uniform sampling even as it improved under
realistic sampling, and the pooled latency win (-0.4us) sits at the edge of
this machine's own measured noise floor.
Round 27's fresh main-vs-costcell/trunk A/B found a real regression:
bench_feature_accuracy.py's pooled scan_units feature reads clean on
main (median 1.00) but UNDER-COUNTS on this branch (median 0.70).

Bisection (isolated release wheel at every Engine: commit between main
and the branch tip) narrows it to one commit: e1c4046 ("A Broad-Guard
Scale for PrintingCompose's Own Bare/Fused Range Reset", this doc's own
Round 7). Every commit before it reads a clean pooled median (0.94-1.00);
e1c4046 alone drops it to 0.69, and nothing after holds it there.

Mechanism: both e1c4046's COMPOSE_SAME_RANGE_BROAD_SCAN_SCALE (0.52)
and Round 4's sibling COMPOSE_RANGE_AND_BROAD_SCAN_SCALE (0.7) were
fit exclusively against unique=card samples (each round's own doc says
so), but applied unconditionally to Mode::Printing/Mode::Artwork too --
the guard they live in runs after the match-mode block, blind to mode.
Measured directly: printings_examined/n_printings for this exact
guard-fired population reads EXACTLY 1.000 (zero spread) under
Printing/Artwork mode -- those kernels never short-circuit, unlike
Card mode's, which is the property both scales were fit to exploit.
Applying a card-only-derived scale to the other two modes was silently
manufacturing an under-count they never had. This is also why the
pooled metric moved so much despite scan_units[printing_compose]'s own
median barely shifting (0.39 -> 0.39): the guard-fired subset is small
(~4-12% of that acquire's rows) but sat ABOVE 1.0 before this fix
(over-counted, per Round 7's own measurement); moving it down past 1.0
removed rows that were propping up the pooled rank from above, letting
that rank fall into the much larger, separately-tracked "narrow" bucket
Round 7 already named and deferred.

Fix: gate both scale branches on matches!(mode, Mode::Card); Printing/
Artwork fall to the existing else branch (the unscaled n_printings
ceiling, already correct for every other shape reaching this guard).
Zero new computation -- mode is an existing bound local.

Results (bench_feature_accuracy.py --seconds 300 --seed 0, isolated
release wheels, main @ ca01641, branch tip @ 865fb03):

                    pooled scan_units median   verdict
main                        1.00                clean
branch, unfixed             0.70               UNDER-COUNTS
branch, fixed               0.94                clean

Closed: 0.94 sits inside the same [0.8, 1.25] band main's 1.00 does.
The residual gap to main's 1.00 is two other, pre-existing, already-
documented contributors this round did not touch (PrintingCompose's
"narrow"-bucket under-count Round 7 itself deferred, and the era-
correlated existential-leaf confound from Rounds 17/20/25) -- both
rooted in domain_cards's own documented broad-range undercount for
bare ranges, which nine prior rounds already found hard to fix
directly; left open, not a regression from this fix.

Test plan:
- cargo test --manifest-path card_engine/Cargo.toml: 177 passed, 0
  failed (debug); --release: 176 passed, 0 failed.
- cargo clippy --manifest-path card_engine/Cargo.toml --all-targets
  -- -D warnings: clean.
- bench_feature_accuracy.py: see Results above; also re-confirmed at
  60s/300s, both consistent (0.92, 0.94).
- bench_regret_matrix.py --seconds 120 --mode realistic (routed-phases
  builds): mean regret/query 0.94us (unfixed) -> 0.95us (fixed) ->
  0.95us (main), unchanged within noise; picked->best SHARE table
  proportionally identical, no anomalous transition.
- bench_cost_model_agreement.py --seconds 300 --seed 0: 12/17 (unfixed)
  -> 13/17 (fixed) cells inside [0.8, 1.25]; every cell moved <0.05,
  one boundary flip consistent with sampling noise.
- bench_query_latency_ab.py --mode realistic --sample 800 --seed 1,
  two order-alternated rounds plus a same-build canary: +0.9us, -0.3us
  (opposite signs), canary 0.0us [-0.2, +0.3] -- no detectable effect.
- bench_pairwise_ordering.py --seconds 60, realistic and uniform,
  GatheredScan vs PrintingCompose: 89%->89% / 87%->87% overall,
  essentially unchanged in both modes.

git diff --stat costcell/trunk: card_engine/src/lib.rs only (19 lines).

Docs updated: local-engine-gathered-scan-card-printing-varying-depth.md
(new Round 28 section) and reference-engine-cost-model-cleanup-final-ab.md
(Round 27's regression marked fixed in place).
Full main-vs-costcell/trunk sweep (feature accuracy, cost-model
agreement, regret matrix, both pairwise-ordering modes, canary-gated
latency A/B) against the Round 28 fixed tip. No code changes.
stream_scan_units defaulted to inheriting scan_units verbatim, so Round 1's
legitimate downward revision to GatheredScan's printing-varying-leaf depth
estimate rode straight through into StreamedSelect's own feature too, with no
acquire branch ever taught the difference. StreamedSelect's small-total branch
(run_query_streamed, total <= STREAM_MIN_MATCHES) pays a second push_card_matches
pass over every matching card that GatheredScan's single pass never does, and
that pass is invisible to printings_examined (its return value is discarded
there), so no scan_units-shaped feature can see it.

Adds STREAM_SMALL_TOTAL_REDO_BIAS, a lib.rs feature-side constant (not a cost.rs
rate), scaling an additive redo term onto stream_scan_units for the printing-
compose acquire's Mode::Card, no-legality else arm. Gated on the same
STREAM_MIN_MATCHES threshold compose_paging_with_total already predicts against,
capped at feats.limit above it rather than dropped to zero, since the acquire-time
estimate is known to overshoot the threshold for some of the exact queries this
fix targets.

Of 114 reproduced f3f4a01 flip queries, 50 (44%) now correctly route to
GatheredScan again. StreamedSelect -> GatheredScan regret matrix share is down
~7% of traffic / ~12% of regret-ms. Partial, not full closure -- the residual
traces to the acquire-time result_total estimate itself being unreliable near
STREAM_MIN_MATCHES for cross-index-range Ands, a separate cardinality-estimation
gap this doc's own Round 1 section already flagged and deferred, not a cost.rs
rate problem.

cargo test --release: 176 -> 177 passed (new regression test added, reverting
the fix's else-arm makes it fail as expected). cargo test (debug): 177 -> 178.
cargo clippy --all-targets -- -D warnings: clean. #852 (GatheredScan vs
PrintingCompose ordering) and Round 28's scan_units feature-accuracy fix both
confirmed clean against this change.
Round 30 priced run_query_streamed's small-total redo pass by fitting
STREAM_SMALL_TOTAL_REDO_BIAS against a wall-clock residual, because no
structural counter existed for the redo pass's own work -- push_card_matches's
return value was discarded in that loop, same pattern as card_match_count's
(c, examined) that GatheredScan already captures.

Capturing it is free: PhaseStats gets a new redo_examined field (zero
everywhere except this branch, following the set_printings/perm_steps
precedent), surfaced through plan_trial_to_pydict and costbench.py's PLAN_KEYS
exactly like printings_examined. Refitting the bias against this real counter
instead of the wall-clock chain: the pointwise-optimal median (1.0) actually
regresses 9 of Round 30's 52 fixed flip-queries with zero gain, while the
candidate-weighted mean (2.237) regresses none and fixes 12 more (64/118 vs
52/118) -- kept for the live routing outcome, not the lower ns-error metric.

bench_regret_matrix.py: StreamedSelect -> GatheredScan's attributed regret
drops from ~54.8ms to ~20.7ms (62%), well beyond Round 30's own 55.4ms ->
48.5ms (12%). #852 pairwise-ordering and Round 28's scan_units feature-accuracy
guard both read unchanged. cargo test: 178/178 release, 179/179 debug, clippy
clean.
…Column Segment (Round 32)

perm_steps's estimate (page_span * n_cards / matches, capped at n_cards)
assumed matches spread uniformly across the WHOLE corpus, but the real
executor (exec_streamed_select) already starts and ends its walk at
walk_bounds's segment -- the slice the filter's own interval on the sort
column admits. The comment above this formula already showed the bounded
variant regrades better (p90 6.43 -> 5.31) without ever explaining why it
was never shipped to the cost model itself.

It was never infeasible, just never circled back to. walk_bounds is a
cheap, already-existing function (two binary searches, early-return on
unbounded), and its inputs (sort_col, descending, sort_bound) are already
sitting on QueryParams before acquire_plan_features ever runs -- sort_bound
is derived once per query at the PyO3 boundary (bind_and_split_filter) and
attached before run_query_routed is called. The loop-phase-measurement
campaign shipped the EXECUTOR-side bound and used the regrade only to
validate that change; teaching the cost model the same bound was the
natural follow-up nobody picked up across 31 subsequent rounds.

Adds cost::PlanFeatures::perm_walk_span, computed by a new lib.rs helper
that calls the SAME walk_bounds the executor calls, wired into mk_plan_feats
uniformly across all five acquire branches. perm_steps now multiplies by
this segment length instead of n_cards -- a strict generalization that
collapses to the old formula whenever the filter says nothing about the
sort column or no permutation exists.

Held-out validation against current traffic (180s uniform sample, hash-of-
query split, 14,217 walking StreamedSelect rows): mean |log ratio| against
the realized perm_steps counter improves 1.033 -> 1.001 pooled, consistent
on both the calibration (1.046 -> 1.015) and held-out (1.021 -> 0.988)
halves. StreamedSelect/candidates's pooled cost-model-agreement cell is
unchanged (median 0.59 both builds): the correlation this fix targets
(filter bounds the same field the query orders by) is rare under uniform
sampling, a designed-cell phenomenon rather than a common production shape,
so most walking rows fall back to perm_walk_span == n_cards regardless.

#852 (GatheredScan vs PrintingCompose ordering) 88% -> 89%, clean. Round
30/31's own territory (StreamedSelect -> GatheredScan regret matrix slice)
flat, confirmed rather than assumed unaffected -- this term is the OTHER
StreamedSelect branch (walks_permutation, not the small-total gather).
Round 28's scan_units feature-accuracy guard is unreachable by this change.

cargo test --release: 178 -> 179 passed (new regression test added,
reverting perm_walk_span to n_cards unconditionally makes it fail as
expected). cargo test (debug): 179 -> 180. cargo clippy --all-targets -- -D
warnings: clean.
compose_printing_estimate's And arm had no tightening at all for set:X
And'd with a collector_number_int range -- set has no compile_plane arm
and isn't in ValueTotals, and collector_number_int isn't arith-tuple-
eligible and has no compile_plane arm either, so the plain min-fold
picked whichever leaf's own corpus-wide count happened to be smaller,
frequently set:X's own full postings length.

Adds set_collector_ranges, a per-set collector_number_int min/max/count
precomputed once at load time alongside set_codes, and a new And-arm
tightening that scales a density estimate (count / (max-min+1)) by the
query's own overlap with that span. Exact for contiguously-numbered
sets, a large improvement (though not exact) for non-contiguous ones
like Secret Lair Drop.

Held-out validation across 550 real sets and both query shapes: density
estimator pooled median |log ratio| 0.000 (88.8% within 25%) against the
fold's 0.788 (18.0% within 25%). Regret matrix moved 37.4ms -> 33.4ms.
No regression on #852, Round 28's scan_units cell, or Rounds 30/31/32's
flip-query population (51/95 fixed on both builds).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

card_engine Changes to the Rust query engine (card_engine) docs Improvements or additions to documentation python rust Pull requests that update rust code size/XXL 3163+ changed lines - consider splitting

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant