🎚️ Subject sufficiency census
Superseded — 2026-08-19
The eleven-card capacity table below was measured 2026-08-18 against the 679-question inventory and is superseded by the 2026-08-19 imports recorded in the legacy WXR import, which closed at 9,069 questions and 483 subject terms. The
can_satisfygate this page records is still the deployed behaviour (wp/wp-content/plugins/quizwizz/includes/SubjectTaxonomy.php:429, applied at:478,:493,:514,:537); only its measured effect is dated. Preserved as observed; current state: 📍 Now. Corpus overview: 📚 Question corpus.
One sentence
The guest builder used to offer eleven subject cards while only three could fill the request it renders with by default; card visibility now asks can this subject satisfy the request, and the eight that cannot are gone from the player surface while remaining fully visible to the operator.
The defect, measured
Measured on the bench 2026-08-18 with SubjectTaxonomy::builder_cards() and QuestionSelector::capacity() against the live settings (count.default = 10, content language en, only prog difficulty enabled, only multiple question type enabled):
| Card | Term count advertised | True capacity for the default request | Could be played |
|---|---|---|---|
| Surprise me | 679 | 350 | yes |
| General Knowledge | 27 | 11 | yes |
| Geography | 31 | 10 | yes |
| History | 31 | 6 | no |
| Science & Nature | 11 | 7 | no |
| Animals | 16 | 2 | no |
| Art | 16 | 2 | no |
| Music | 16 | 2 | no |
| Mythology | 12 | 2 | no |
| Politics | 12 | 2 | no |
| Sports | 16 | 2 | no |
Eight of eleven cards advertised a subject that could not produce a playable quiz. The progressive curve is why the gap is so wide: capacity is 2 × min(easy, harder) + carry, so a subject holding sixteen questions with one easy question yields two.
The failure was only ever reported after the click, by the availability request against quizwizz/v1/pool-count.
Gate census
Every pool_count() gate in the installed plugin, classified:
| Callsite | Surface | Gate before | Gate now |
|---|---|---|---|
SubjectTaxonomy::builder_cards() mains loop | player | term count ≥ 1 | term count ≥ 1 and can_satisfy |
SubjectTaxonomy::builder_cards() parentless leaves | player | term count ≥ 1 | term count ≥ 1 and can_satisfy |
SubjectTaxonomy::builder_cards() show-subs leaves | player | term count ≥ 1 | term count ≥ 1 and can_satisfy |
SubjectTaxonomy::builder_cards() Surprise me card | player | always emitted | emitted only when the whole enabled catalog can satisfy |
SubjectTaxonomy::pool_breakdown() | operator | count only, no gate | unchanged |
SubjectTaxonomy::admin_tree() children and rows | operator | count only, no gate | unchanged — operators always see everything with its state |
Rest\GenerateController::pool_count() | player, post-selection | already request-aware through GenerationService::pool_count() | unchanged |
The resolver
No new capacity mathematics were written. QuestionSelector::capacity() was already index-backed and curve-aware; the builder simply never asked it. The new gate is one method:
SubjectTaxonomy::can_satisfy( string $slug, ?GenerationCriteria $criteria, array $settings ): bool
- Resolves the subject the same way a real query would — a main folds to its enabled children, a leaf respects its parent’s switch,
anymeans the whole enabled catalog. - Clones the request, swaps in those categories, and asks
QuestionSelector::capacity()for the answer. - Returns
truefor anullrequest, so operator callers are unaffected by construction. - Treats an index storage error as
false: a subject that cannot be proven sufficient is not advertised to a player.
The guest builder builds that request from what it actually renders with — resolved language, difficulty, question format and question count — so the cards match the quiz the visitor would get by pressing play immediately.
Decisions recorded
- One card list per default request. A visitor who then raises the question count or changes difficulty is still answered by the existing live availability check, which reports
can_generatefor the current selection. Per-card live re-filtering was considered and deliberately not built in this pass. - Operator surfaces keep everything. An operator who cannot see a subject cannot fix its pool.
admin_treeremains complete, including zero-pool subjects. - Pool numbers stay on the cards. They are still informative; they are simply no longer the gate.
Proof
| Claim | Evidence |
|---|---|
| Eleven cards before, three after | builder_cards() with no request → 11 ids; with the default request → any general-knowledge geography |
| The rendered page no longer offers Animals | do_shortcode( '[quizwizz_craft]' ) → 19,173 bytes, contains geography and general-knowledge, does not contain animals |
| Sufficiency, not existence | can_satisfy( 'animals', … ) = false while the term holds 16 published questions |
| Operators still see it | admin_tree() still lists animals |
| Regression-defended | Wave 6 PHP assertion 14, “subject that cannot fill the request is omitted from builder cards and still visible in admin tree” |
| Nothing else moved | Full probe 1..46, assertions=46 failures=0, Wave 6 PHP assertions=17 failures=0; debug log unchanged at 38,433 lines across the run |
The probe assertion provisions itself: it picks the enabled subject with the smallest non-empty pool and asks for one more question than that subject holds, so it stays true as content changes.