Engine: GatheredScan/Card Cost-Model Cleanup — Range-Leaf Family - #1071
Draft
jbylund wants to merge 44 commits into
Draft
Engine: GatheredScan/Card Cost-Model Cleanup — Range-Leaf Family#1071jbylund wants to merge 44 commits into
jbylund wants to merge 44 commits into
Conversation
… 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).
…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.
…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).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Cost-model and routing accuracy work targeting
GatheredScan/unique=cardand theGatheredScan/StreamedSelect/PrintingComposeplan-ordering triangle. Two phases, 13 rounds, tracked across three docs:Results:
GatheredScan/cardcrossed from FAIL to PASS onbench_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 viabench_pairwise_ordering.py.This branch also carries five earlier commits already on it before this work (
34466eb2through97dc30c8, plus an unrelatedis:hybrid/is:phyrexianmana-symbol parsing fix and smalladmin_resource.py/test_upsert_cards.pychanges) — 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:COMPOSE_CANDIDATE_SPAN_BIASrefit 2.1→0.7).COMPOSE_RANGE_AND_CLUSTER_BIAS = 1.1) for cross-index range ANDs, since the existing bias was fit for a different population.scan_unitsscales (COMPOSE_RANGE_AND_BROAD_SCAN_SCALE = 0.7,COMPOSE_BARE_RANGE_BROAD_SCALE = 0.43,COMPOSE_SAME_RANGE_BROAD_SCAN_SCALE = 0.52) for therange_too_broad_to_narrowguard's full-corpus reset, in the specific shapes/acquire-branches it fires for — never a new exemption to the guard itself.GATHER_FIXED_COST_ZERO_MATCH_NS = 42.0vs. the existing169.6) —GatheredScan's cost formula, not a feature bug, under-priced by ~4x.domain_cardswas proven mathematically incapable of fixing an undercount. Logged, not shipped.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.PrintingCompose)cost.rs)Phase 2 — regret/pairwise-ordering-driven, and two "don't build this" findings worth having on record
A fresh
bench_regret_matrix.py --mode realisticrun showed Phase 1's later rounds (8-10) had drifted onto a small real-impact population (candidatesacquire, 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:#852'sGatheredScan/StreamedSelectpair against the accumulated Phase 1 fixes — resolved (97%/0.82µs, see Summary). Surfaced a seemingly large new lever:GatheredScan vs PrintingComposeandPrintingCompose vs StreamedSelect, worst pairs in the engine underplane-acquire, up to 27µs mean regret.Planeacquire branch never setsPrintingCompose's build-cost features) and found it's provably inert —PrintingComposeis structurally excluded from ever winning underPlane-acquire scope (CandidatePlan::of(PrintingCompose) => None), confirmed empirically (applicable in 71% of sampled queries, picked in 0). The apparent 27µs lever was abench_pairwise_ordering.pymeasurement artifact — it scores every pair regardless of whether both plans are reachable in that acquire's scope.PrintingComposea real executor underPlaneacquire) would be worth doing, given real 142-149µs margins Round 12 measured againstGatheredScan/StreamedSelect. Fresh measurement against the actual incumbent winner (PlanePopcountOrder, not those two) foundPrintingComposenever wins — 0/3,209 realexplain_analyzerows, even at extreme limit sweeps (closest: still 1.43x slower atlimit=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.acquire_plan_features'snothing_to_verifycheck ORed two whole-query claims (plane_leaves_nothing_to_verifyandcompose_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 ararity/borderplane that still needed real per-printing checking. Concretely:cmc>=1 cmc<=5 border:black(unique=card) was routed toGatheredScan(predicted 326,263ns, real 1.02-1.29ms) overPrintingCompose(real 478-511µs) — routing now flips correctly, a ~2.3x real win. Shipped fix was initially scoped to a newrarity/border-specific helper.rarity/border) instead of the underlying behavior (does this existential plane's semantics require per-printing re-verification underMode::Card?) left an identical gap for a divergent-legality-only plane. Confirmed live before fixing:f:oldschool(unique=card) predictedGatheredScanat 10,358ns against a measured 29,083ns (2.8x under),f:oldschool cmc>=1 cmc<=5at 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 underMode::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'sPLANE_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 test173/173 (3 new property tests + 1 regression test),clippyclean, 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
GatheredScanfor 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_cardsitself — is wrong for this population, not the rate being fit.Round 21 (investigation only) root-caused it with a falsifiable test:
compose_printing_estimate'sAndarm only computes an exact card-domain intersection (best_other) when an existential leaf (border/rarity/divergentlegality) 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 makeseval_domainexact.Round 22: fixed the gate (
else if !card_invariant.is_empty()→ plainelse). Verified exact (ratio 1.000, not just improved) on every reproducer tested:cmc=1 border:black0.624→1.000,cmc=1 border:white0.113→1.000,cmc=1 r=mythic0.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 test173/173 (including a new regression test verified to actually catch the bug on revert),clippyclean, 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% ofMode::Cardqueries 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_totalsalready computes exact disjoint-value-pair counts forborder/rarity/frame/legalityand is already wired into the same code path — it was just never extended tocmc/power/toughness, the same "answer already exists, not reached for this shape" pattern as Round 21's finding.Round 24: extended
PairTotalstocmc/power/toughness(mirroring the existingraritydimension), 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 test177/177 debug, 176/176 release,clippyclean, full confirmation pass clean. A further generalization (the same disjoint-sum trick for two arith fields ranged together, e.g.powerandtoughnesssimultaneously) 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_unitsconfound 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_domainspans 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
is:vanilla-shaped queries) and anOr/negation population invisible to the current benchmark sampling — both noted in Phase 1's doc, neither pursued further after Round 10's discard.matches == 0also fires forGatheredScancosted under RANGE_ACQUIRES branches for a different reason (unset default, not real emptiness).r=special) is a separate, already-documentedcard_invariant_domain_exactwidth-invariance bug, not this fix's territory.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.GatheredScancost-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'sscan_unitsprint-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 commitcargo clippy --manifest-path card_engine/Cargo.toml --all-targets -- -D warningsclean on every kept commitbench_regret_matrix.py+bench_query_latency_ab.pywith a same-build canary on every kept roundbench_pairwise_ordering.pyre-verification of theGatheredScan/StreamedSelectpair (Round 11) and thePrintingCompose-under-Plane-acquire investigations (Rounds 12-13)GatheredScan/cardcell after Phase 1 (not just each round's self-reported numbers)make test/integration suite (not yet run against the accumulated branch tip)