π― 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:
- 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.
- 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.
- 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.
- 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_allowedtrue. Finish:13/58,reviewlength 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_enabledall false. Finish:13/58 (22%), noreview. - 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.
| Fact | Values | Serves |
|---|---|---|
| Player reveal | instant / end / never | The run |
| Print answer sheet | on / off | The PDF |
| Theme follows share | on / off | Shared player chrome |
Default UI can still be one Answers switch:
- On β player
end+ sheeton - Off β player
never+ sheetoff - 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.phpmatrix on quiz 413 measuredkey.st=forced,qw_include_answers=0: REST/playersheet_allowed=falseand payloadpresentation.include_answers=false, but the direct PDF pathPresentationDocument::pdf_args()still returnedinclude_answers=true. SoOff β no sheetdoes hold for the player payload sheet flag, but does not hold for the PDF builder path whilekey.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.
- Stopped reading
qw_include_answerswhen deciding player reveal (includes/QuizService.phpexperience(),includes/Rest/QuizController.php). - Left the print key on that meta / print path (
sheet_allowed). - Proved on quiz 413: finish JSON has
reviewlength 58. Quiz 1284 PDF still has a key. - Falsifier not triggered: no
neverpath was flipped on, and the sheet did not vanish because the player was set toend. - 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-pathmay change. - Whether a Forced site should honour a per-quiz sheet
offconsistently: 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.
| Receipt | Value | How |
|---|---|---|
| Quiz URL | https://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 session | no wordpress_logged_in cookie, no #wpadminbar | tab.evaluate on the loaded page |
| Answered | all 58 questions, first choice each (correctness not the contract) | browser loop: pick radio β .qwizz-player__submit β Next question |
| Result screen | Quiz complete, score 13 / 58 (22%) | section.qwizz-player__result h2 + .qwizz-player__score |
| Answer review | 58 review items (13 .is-correct, 45 .is-wrong) | document.querySelector('section.qwizz-player__result').querySelectorAll('.qwizz-player__review-item').length β 58 |
| Console errors | 0 (console error + pageerror listeners attached before navigation) | browser run, whole playthrough |
| Screenshot | 2026-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.
| Receipt | Value | How |
|---|---|---|
| Quiz URL / status | https://wrdp.loca.zone/?qw_quiz=world-quiz-english, post 413 publish, slug world-quiz-english | wp post list --post_type=qw_quiz --post__in=413 --fields=ID,post_status,post_title,post_name; wp post url 413 |
| Guest session | document.cookie empty string, #wpadminbar absent, body class has no logged-in | tab.evaluate after tab.goto |
| Answered | all 58 questions, first radio each; 115 submit/next clicks total | browser 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 review | 58 items β 13 .is-correct, 45 .is-wrong, header 13 Correct Β· 45 Incorrect | document.querySelectorAll('.qwizz-player__review-item').length β 58; section.qwizz-player__result matched 1 node, tagName === 'SECTION' |
| Console errors | 0 (console type error + pageerror listeners attached before tab.goto) | browser run across the whole playthrough |
| Screenshot | 2026-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.
| Bit | Storage | Default when absent | Anchor |
|---|---|---|---|
| Player answer review / key | per-quiz meta array qw_experience, boolean key include_answers | true | includes/QuizService.php:682-687 |
| PDF answer sheet | legacy per-quiz meta qw_include_answers ('1'/'0') | '' treated as allowed | includes/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/neverplayer reveal enum written into βThe simple modelβ above isretracted: it does not exist in the deployed source. Checked inwp/wp-content/plugins/quizwizzwithgrep -rncE "qw_reveal|reveal_timing|'never'" includes/ assets/js/β no matching file (exit 1), andgrep -rnE "'instant'|reveal" includes/ assets/js/returns onlyrevealDecodedImagehelpers inassets/js/qwizz-admin-settings.js:639,659,683. Timing is expressed by the existinginstant_feedback/allow_feedbackplayer 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.st | qw_include_answers | qw_experience.include_answers | formula sheet_allowed | formula key_allowed | payload key_enabled | payload presentation.include_answers | PDF args include_answers |
|---|---|---|---|---|---|---|---|
| forced | 0 | true | false | true | true | false | true |
| forced | 0 | false | false | true | true | false | true |
| forced | 1 | true | true | true | true | true | true |
| forced | 1 | false | true | true | true | true | true |
| visitor | 0 | true | false | false | false | false | false |
| visitor | 0 | false | false | false | false | false | false |
| visitor | 1 | true | false | false | false | false | true |
| visitor | 1 | false | false | false | false | false | true |
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_answers | args include_answers | answer_key_questions | include_explanations | rendered pages | bytes |
|---|---|---|---|---|---|
0 | false | false | false | 4 | 71,729 |
1 | true | true | true | 5 | 97,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.