🎯 Play and print answers

Historical record β€” 2026-08-18

Preserved as observed, including the 2026-08-19 additions and both retractions. Current truth: the standing ruling this page produced is stated at Answer visibility policy β€” D005.

Verdict

Split the mechanism. Keep the default control simple. A player run needs a timing choice β€” instantly, at the end, or never. A PDF needs an answer sheet that can exist even when the player stays silent. One shared on/off is a fine surface. It is a broken engine if that one bit also mutes review, explanations, and the site-wide Forced key.

This page is bench planning and dated proof, not a release claim. The rule it settled is standing policy and lives at Answer visibility policy β€” D005. Related: admin IA strategy, open-work ledger, Now.

Owner words, echoed

Recorded 2026-08-18, unedited:

  1. this is difficult for me to overstand but i know it should be possible for the user to simply set this to show the answeres in different places, differen ways.
  2. the quiz game run could show answers instantly for example or at the end or never, more important is pdf where the answers can be included always on an answer sheet.
  3. the big question in my head is also if it makes sense to split it or keep it as a simple answers on/off for both player and pdf alike and do the same for themeing - a user themes a quiz that could be toggled to apply to the quiz itself as well when shared via a link the simple quiz ui would look as user intended and some options can be just shared and crazy complexity can be further developped when more quiz(type) or game modes arise.
  4. what ever the verdict and core knowledge about this is: be sure to document it masterfully condensed relevant and solution based into wiki including espacially an echoed list of THIS exact user response.

What landed

First repair is on the bench. Player reveal no longer reads qw_include_answers. Print still owns that meta. Site Forced no longer has one simple answer: the REST/player payload honours per-quiz sheet off, while the PDF builder path still clamps the sheet on through the presentation matrix.

Measured after the change, 2026-08-18:

  • Quiz 413 (qw_include_answers=0) start: key_allowed true. Finish: 13/58, review length 58.
  • Quiz 1284 (sheet meta 1) PDF HTML still contains the answer-key block.
  • ./scripts/quizwizz-probe.sh --wave all = 1..46, assertions=46 failures=0.
  • Debug log line count unchanged across the install and the probe.

Before: 8 of 13 published quizzes muted player review through the print flag. That coupling is gone. Remaining work is the Answers UI (one switch, expand for timing vs sheet), not another engine rewrite.

What the build did

includes/Rest/QuizController.php used to compute one boolean, key_allowed, as: site key and experience include_answers and post meta qw_include_answers empty or 1. That boolean then gated instant feedback, explanations, and the finish review array (includes/AttemptService.php). Site key.st = forced could not override the post-meta veto.

Measured before the repair, 2026-08-18:

  • 8 of 13 published quizzes failed key_allowed (all non-fixture quizzes).
  • 269 of 292 published-quiz questions finished as a bare score.
  • Quiz 413 start payload: key_enabled, instant_feedback, explanations_enabled all false. Finish: 13/58 (22%), no review.
  • Fixtures 1284–1288 were the only published quizzes with the full teach loop on.

The operator could believe Answers were Forced, print a sheet, and still ship a player that never said which radio was wrong.

The simple model

Two independent facts. One later cousin.

FactValuesServes
Player revealinstant / end / neverThe run
Print answer sheeton / offThe PDF
Theme follows shareon / offShared player chrome

Default UI can still be one Answers switch:

  • On β†’ player end + sheet on
  • Off β†’ player never + sheet off
  • An expand reveals the three timings and the sheet toggle

Retracted/refined, 2026-08-19

The 2026-08-18 correction above was too broad. The 2026-08-19 wp eval-file /tmp/qw-forced-matrix.php matrix on quiz 413 measured key.st=forced, qw_include_answers=0: REST/player sheet_allowed=false and payload presentation.include_answers=false, but the direct PDF path PresentationDocument::pdf_args() still returned include_answers=true. So Off β†’ no sheet does hold for the player payload sheet flag, but does not hold for the PDF builder path while key.st=forced.

The engine must store the two facts separately even when the switch sets both. Theme-follows-share is the same pattern: one optional toggle, not a second theme system. Richer game modes may grow more later; do not invent them now.

Existing knobs already almost are this: instant_feedback is instant vs end; allow_feedback off is never; qw_include_answers belongs on the print path only.

First repair

Done on the bench, 2026-08-18.

  1. Stopped reading qw_include_answers when deciding player reveal (includes/QuizService.php experience(), includes/Rest/QuizController.php).
  2. Left the print key on that meta / print path (sheet_allowed).
  3. Proved on quiz 413: finish JSON has review length 58. Quiz 1284 PDF still has a key.
  4. Falsifier not triggered: no never path was flipped on, and the sheet did not vanish because the player was set to end.
  5. Fixtures, craft, and theme were not retuned.

Open follow-on, not this repair:

  • The simple Answers switch with its expand. Collapsed On/Off writes both facts; the expand exposes player timing and the sheet toggle; a mixed pair shows as Custom. No data-path may change.
  • Whether a Forced site should honour a per-quiz sheet off consistently: decided and landed 2026-08-19 as D005 (see the section below). The split is gone; both paths honour the per-quiz setting.
  • Craft cards that advertise subjects they cannot fill: 2 of 10 named guest subjects can supply the default 10-question English progressive quiz, and every card looks identical until after the click.

Browser proof, 2026-08-19 β€” bet B001 closed

The 2026-08-18 evidence above was in-process only (REST payload inspection). Closed here with an end-to-end anonymous-guest browser playthrough. No bet id B001 exists anywhere in the vault (grep -rn "B001" ~/dev/wrdp/q5vault/ β†’ no matches, 2026-08-19), so this dated note is the closure record; state: verified.

ReceiptValueHow
Quiz URLhttps://wrdp.loca.zone/?qw_quiz=world-quiz-english (post 413, publish)wp post list --post_type=qw_quiz --post__in=413 --fields=ID,post_status,post_title,url
Guest sessionno wordpress_logged_in cookie, no #wpadminbartab.evaluate on the loaded page
Answeredall 58 questions, first choice each (correctness not the contract)browser loop: pick radio β†’ .qwizz-player__submit β†’ Next question
Result screenQuiz complete, score 13 / 58 (22%)section.qwizz-player__result h2 + .qwizz-player__score
Answer review58 review items (13 .is-correct, 45 .is-wrong)document.querySelector('section.qwizz-player__result').querySelectorAll('.qwizz-player__review-item').length β†’ 58
Console errors0 (console error + pageerror listeners attached before navigation)browser run, whole playthrough
Screenshot2026-08-19-quiz-413-playthrough-review.webp (local ~/dev/qwizz/03 EVIDENCE/)tab.screenshot({fullPage:true})

The player REST start route is POST quizwizz/v1/quiz/413/start (GET returns rest_no_route); the page autostarts via data-autostart="1".

Still open, unchanged by this proof: the Answers UI split (one switch plus expand) and whether a Forced site may honour a per-quiz sheet off.

Independent re-proof, 2026-08-19 (second run, fresh Chromium --incognito)

The closure above was re-measured from scratch in a separate spawned-Chromium guest session (not the relay browser, which carried a logged-in body class and #wpadminbar). State stays verified; every number reproduced.

ReceiptValueHow
Quiz URL / statushttps://wrdp.loca.zone/?qw_quiz=world-quiz-english, post 413 publish, slug world-quiz-englishwp post list --post_type=qw_quiz --post__in=413 --fields=ID,post_status,post_title,post_name; wp post url 413
Guest sessiondocument.cookie empty string, #wpadminbar absent, body class has no logged-intab.evaluate after tab.goto
Answeredall 58 questions, first radio each; 115 submit/next clicks totalbrowser loop: .qwizz-player__answers input[type=radio] β†’ .qwizz-player__submit (Check answer β†’ Next question)
Result screen<h2 tabindex="-1">Quiz complete</h2>, score 13 / 58 (22%)section.qwizz-player__result h2, .qwizz-player__score
Answer review58 items β€” 13 .is-correct, 45 .is-wrong, header 13 Correct Β· 45 Incorrectdocument.querySelectorAll('.qwizz-player__review-item').length β†’ 58; section.qwizz-player__result matched 1 node, tagName === 'SECTION'
Console errors0 (console type error + pageerror listeners attached before tab.goto)browser run across the whole playthrough
Screenshot2026-08-19-quiz413-browser-playthrough.png (local ~/dev/qwizz/03 EVIDENCE/, 191,774 bytes, 1656Γ—18030)tab.screenshot({fullPage:true}) β†’ sips -s format png

Selector note: the plan’s guessed section.qwizz-player__result is correct on the live markup, and the review nodes are .qwizz-player__review-item inside .qwizz-player__review-list. Nothing drifted.

Discovered contract, 2026-08-19 β€” two metas, no reveal enum

Read directly from the deployed plugin source on this bench; state: verified.

BitStorageDefault when absentAnchor
Player answer review / keyper-quiz meta array qw_experience, boolean key include_answerstrueincludes/QuizService.php:682-687
PDF answer sheetlegacy per-quiz meta qw_include_answers ('1'/'0')'' treated as allowedincludes/QuizService.php:205, includes/Rest/QuizController.php:174-176

Both bits are still derived from one persist-path input include_answers (includes/QuizService.php:120-123,199-206) β€” that single input is what the Answers UI split has to separate.

Retracted, 2026-08-19

The instant / end / never player reveal enum written into β€œThe simple model” above is retracted: it does not exist in the deployed source. Checked in wp/wp-content/plugins/quizwizz with grep -rncE "qw_reveal|reveal_timing|'never'" includes/ assets/js/ β†’ no matching file (exit 1), and grep -rnE "'instant'|reveal" includes/ assets/js/ returns only revealDecodedImage helpers in assets/js/qwizz-admin-settings.js:639,659,683. Timing is expressed by the existing instant_feedback / allow_feedback player settings, not by a three-value reveal meta. The engine contract to build against is the two booleans in the table above.

Forced policy answer-sheet matrix, 2026-08-19

Command: ssh loca@178.18.250.170 "cd ~/dev/wrdp && sudo -n docker compose --profile cli run --rm -v /tmp/qw-forced-matrix.php:/tmp/qw-forced-matrix.php wpcli wp eval-file /tmp/qw-forced-matrix.php". The harness used quiz 413, saved/restored qw_exposure_settings, qw_include_answers, and qw_experience, and printed IDENTICAL=true after restoration.

key.stqw_include_answersqw_experience.include_answersformula sheet_allowedformula key_allowedpayload key_enabledpayload presentation.include_answersPDF args include_answers
forced0truefalsetruetruefalsetrue
forced0falsefalsetruetruefalsetrue
forced1truetruetruetruetruetrue
forced1falsetruetruetruetruetrue
visitor0truefalsefalsefalsefalsefalse
visitor0falsefalsefalsefalsefalsefalse
visitor1truefalsefalsefalsefalsetrue
visitor1falsefalsefalsefalsefalsetrue

Formula anchors: REST/player computes sheet_allowed = effective_bool('key') && (meta empty or '1') and key_allowed = effective_bool('key') && experience.include_answers at includes/Rest/QuizController.php:174-177, then emits key_enabled at :226 and payload presentation.include_answers at :243. The PDF builder path feeds posted include_answers into PresentationDocument::build() at includes/Generation/GenerationService.php:316-337; PresentationDocument clamps that through ExposureSettings::clamp_bool_from_matrix('key', ...) at includes/ExposureSettings.php:350-353, whose forced branch returns true at :581-582, and the renderer includes the answer sheet when presentation['include_answers'] is truthy at includes/Pdf/PdfGenerator.php:225.

The Forced-vs-per-quiz-off question is now an owner row: UX-ANS-01 in 2026-08-17 owner decision intake; state answered 2026-08-19 β€” see D005 below.

D005, landed 2026-08-19 β€” the per-quiz answer sheet wins under Forced

Standing statement of this ruling, with the code anchors re-verified against the deployed plugin and the line drift since 2026-08-19 recorded: Answer visibility policy β€” D005. What follows is the dated proof as measured.

Owner ruling: whatever the admin decides for the quiz wins, explanations included. This supersedes D004.

The rule now lives at the one place a concrete quiz id is in scope for a PDF build, includes/Generation/GenerationService.php in pdf_args(), immediately after PresentationDocument::pdf_args():

$quiz_id = absint( $params['quiz_id'] ?? 0 );
if ( $quiz_id > 0 ) {
    $key_meta = (string) get_post_meta( $quiz_id, 'qw_include_answers', true );
    $sheet    = ExposureSettings::effective_bool( 'key', $settings ) && ( '' === $key_meta || '1' === $key_meta );
    $args['include_answers']      = $sheet;
    $args['answer_key_questions'] = $sheet && ! empty( $args['answer_key_questions'] );
    $args['include_explanations'] = $sheet && ! empty( $args['include_explanations'] );
}

It mirrors includes/Rest/QuizController.php:176 exactly, and clamp_bool_from_matrix() / presentation_schema() are untouched, so settings-save and quiz-less callers (craft preview, generation defaults) keep the old forced behaviour.

Measured on the bench, site key.st = forced, quiz 413, first 12 questions, one wp eval-file run:

qw_include_answersargs include_answersanswer_key_questionsinclude_explanationsrendered pagesbytes
0falsefalsefalse471,729
1truetruetrue597,502

The meta was restored to 0 in the same run. Receipts: ~/dev/qwizz/03 EVIDENCE/2026-08-19-d005-per-quiz-sheet/.

The editor no longer lies about it: in includes/Admin/views/quiz-editor.php the Answer sheet in PDF checkbox is enabled under Forced and reflects the stored meta, described as Per-quiz setting wins; site Forced only sets the default. Previously it was checked-and-disabled, so a disabled checkbox posted nothing and the save path (Admin/MetaBoxes.php:320-325, which never clamped the sheet) stored off while the screen showed on. The hidden policy branch is unchanged: hidden still means no sheet. The player-review control keeps its forced treatment; D005 governs the sheet only.

Probe-defended: Wave 6 PHP assertion 15, D005 per-quiz answer sheet decides the quiz PDF under a Forced site key while quiz-less PDFs stay forced; full run 1..46, assertions=46 failures=0.

Falsifier: a quiz whose qw_include_answers is 0 renders an answer key in its PDF while the site key is Forced, or a quiz-less craft PDF loses its forced key.

Open follow-on is tracked in the open-work ledger.