🎚️ 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_satisfy gate 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):

CardTerm count advertisedTrue capacity for the default requestCould be played
Surprise me679350yes
General Knowledge2711yes
Geography3110yes
History316no
Science & Nature117no
Animals162no
Art162no
Music162no
Mythology122no
Politics122no
Sports162no

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:

CallsiteSurfaceGate beforeGate now
SubjectTaxonomy::builder_cards() mains loopplayerterm count ≥ 1term count ≥ 1 and can_satisfy
SubjectTaxonomy::builder_cards() parentless leavesplayerterm count ≥ 1term count ≥ 1 and can_satisfy
SubjectTaxonomy::builder_cards() show-subs leavesplayerterm count ≥ 1term count ≥ 1 and can_satisfy
SubjectTaxonomy::builder_cards() Surprise me cardplayeralways emittedemitted only when the whole enabled catalog can satisfy
SubjectTaxonomy::pool_breakdown()operatorcount only, no gateunchanged
SubjectTaxonomy::admin_tree() children and rowsoperatorcount only, no gateunchanged — operators always see everything with its state
Rest\GenerateController::pool_count()player, post-selectionalready 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, any means the whole enabled catalog.
  • Clones the request, swaps in those categories, and asks QuestionSelector::capacity() for the answer.
  • Returns true for a null request, 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_generate for 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_tree remains complete, including zero-pool subjects.
  • Pool numbers stay on the cards. They are still informative; they are simply no longer the gate.

Proof

ClaimEvidence
Eleven cards before, three afterbuilder_cards() with no request → 11 ids; with the default request → any general-knowledge geography
The rendered page no longer offers Animalsdo_shortcode( '[quizwizz_craft]' ) → 19,173 bytes, contains geography and general-knowledge, does not contain animals
Sufficiency, not existencecan_satisfy( 'animals', … ) = false while the term holds 16 published questions
Operators still see itadmin_tree() still lists animals
Regression-defendedWave 6 PHP assertion 14, “subject that cannot fill the request is omitted from builder cards and still visible in admin tree”
Nothing else movedFull 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.