Learn from play: design v2.4 (current policy)
This is the single current policy for learning from play. It replaces v2, the v2.1 working copy, v2.2 and v2.3. Older text and review provenance are in Appendix A ("Revision history"); they are history, not policy.
Requested by the operator on 2026-10-19 ("how can we use ANN to learn from the output of these games and be smarter over time"). Binding operator decisions: D48–D52 and D54 (maneet-games/master-mind/review-2026-10-15-platform/FINAL-PLATFORM-AND-IMPROVEMENTS.md, rows 48–52 and 54). Where this file and a decision row disagree, the decision row wins and this file is wrong.
0. Status (as of 2026-10-19)
Approved: Phases 1–2, shadow only (D50). Not approved: anything child-visible. Going live (Phase 3) needs a new operator decision plus the §6.1 evaluation protocol.
Built in the pilot (per the pilot report; in maneet-games/master-mind):
games/_factory/learn.js, a standalone module. Whetstone is wired to it in shadow mode: the fixed ladder still picks every question.ci/l2-spotter.mjsandci/l2-replay-sim.mjs(L2, builder-only). On replay the spotter flagged Whetstone identity 5/5, CMA std-form 5/5, CMA long float 5/5 and CMA prime parity 4/5, with 0 false flags. These are replays of known bugs with small counts, not a general detection rate.ci/learn-gates-test.mjs: 45/45 checks across G-L1..G-L8, against the test's own contract, which its header gives as "design v2.1 §8 + §12". It has no G-L7b. Its G-L1 passes on a realistic Whetstone payload and only logs the admitted-cap worst case without failing on it, so G-L1 as defined in v2.4 (§8) is not established.- The shadow-equivalence digest (the child-visible run) is identical in all arms.
- The operator switch lives in device-only localStorage key
mm-learn-op, bound to the player id and the player generation nonce. Operator identity comes from the hub owner check. - The sealed holdout manifest is
ci/learn-holdout-manifest.json(sealdcf9ddf9…b888). - The built L1 state is per skill
[rating×100, attempts, correct], with only the per-session bound (G-L7). The §4 mastery history and G-L7b are specified, not built.
Known gaps (open; none is hidden by this document):
1. The switch cannot be enabled from a child's own claude.ai account. That needs a hub-side or db-backed operator grant, which is a future decision (§10).
2. Storage-integrity limitation of Phase 2 shadow capture (§2.3, D49, D52). The privileged APIs (operatorEnable, operatorWipe) do check the hub owner. But routine start() trusts the mm-learn-op localStorage record once it is present, and any same-origin script can forge that record. A forged switch can start bounded shadow recording, and the recorded learn key then syncs to the hub cloud copy (§2.2). There is no child-visible effect. Resolving this is an operator choice (§2.3, §10).
3. Wipe is implemented for Whetstone only. Per the pilot report, Whetstone is also the only game wired to learn.js, so no other game holds learning state yet; this has not been independently re-verified. §2.3 makes it a precondition: no further game records until its namespace is in the wipe scope and passes G-L5.
4. Whetstone's pre-existing save caps (its own migrate caps: 200 skills, 400 facts, 200 pb) can produce a progress + .bak payload over 100,000 bytes. That is not caused by learning: such a save already falls back to device-only today. It does mean the v2.4 G-L1 gate, measured at the admitted caps, can fail for Whetstone until those caps are lowered or the claim is narrowed (§8).
5. The §4 mastery history, G-L7b and the §6.1 protocol do not exist yet. All three are Phase 3 entry conditions.
6. The built claims above come from the pilot report. The ANN reviews (seq 8495, 8504) did not audit code or run tests, and neither did this revision.
7. Test-contract drift. ci/learn-gates-test.mjs still identifies its contract as design v2.1, and it implements G-L1..G-L8 only. G-L7b is not counted as implemented until a test exists. The decision row D50 says "G-L1..G-L8 before any live step". This design adds G-L7b as a further condition and does not edit the D50 row.
8. Code TODO (not edited here): the operatorWipe comment in games/_factory/learn.js (:307) cites "§2.3 wipe" from v2.1. It should cite design v2.4 §2.3.
1. Goal
Each game should get better the more children play. The goal is not "more data"; it is three measurable things:
- children learn more, as measured on independent, held-out checks;
- bad content is caught earlier;
- AI judging is honest about uncertainty.
2. Ground rules
2.1 The model suggests; trusted code decides
- A learner returns only an opaque candidate ID or a bounded parameter.
- The caller checks that the ID is in an eligible set it built itself, then gets the content and the answer through the verified-maths module.
- Bot outputs are clamped to bounded ranges.
- The judge learner may only add abstention ("not sure"); it never changes a verdict or a score.
- Saved statistics and imported priors are untrusted input: they are schema-validated, and a failure means the default behaviour.
2.2 Where data lives (D48, D52)
| Where | What | Who can read it | State |
|---|---|---|---|
| Device | learning summaries under a dedicated learn key | any first-party game script on the hub origin (same-origin, see below) | ON only while the operator switch is on for that player |
| Hub cloud copy | the same learn key, synced by MM.store → MM.data to data/users/<uid>/sdk~<game>~<pid> | the signed-in account; artifact owner per db rules | ON together with the device copy when the child is signed in (D48). The SDK has no local-only option, so this is stated, not hidden |
| oldisgold / any aggregate | no child data. oldisgold holds bot-vs-bot self-play only (L4a, D51) | — | child-data export OFF (D48). §7.3 lists what an export would need |
- Sync is newest write per key wins, with no merge, within the 8 KB learning budget (§3).
- Same-origin boundary (D52). The first-party game iframe is same-origin with the hub and unsandboxed, so any game can read origin-wide
localStorage, exactly like today's game saves. Key prefixes are a naming convention, not isolation. This design makes no per-game privacy claim. Mitigation is data minimisation: coarse skill ratings, counts and bounded session ages only, with no timestamps, no free text and no per-answer history beyond the bounded last-four outcomes per level in §4. Real isolation (an opaque-origin game sandbox plus a hub-owned storage broker) is planned separately inproposals/storage-isolation.md. - Cross-game read test: it documents, it does not deny. A regression test reads one game's learn key from another game and is expected to succeed today. It flips to a denial test only if isolation ships.
- Behaviour data without names can still identify a child: timing, items, rare combinations. Nothing is called "non-identifying" just because names are left out.
2.3 Authority and wipe (D49)
- Switch (D49): only the operator may switch learning on and wipe it; GOAT accounts cannot. Absent, corrupt or newer settings mean OFF. The setting is bound to the player id and the current
MM.playergeneration nonce, and it lives outside the learning store (device-onlymm-learn-op), so it never rides the learning cloud path. - What is enforced today, and what is not. The privileged APIs
operatorEnableandoperatorWipecheck the hub owner, so a non-owner cannot use them. Routinestart()then trusts the storedmm-learn-oprecord. Under D52, any same-origin script can forge that record. A forged switch can start bounded shadow recording (coarse ratings and counts, ≤ 8,000 bytes, no child-visible effect), and that state syncs to the hub cloud copy. This is a storage-integrity limitation of Phase 2 shadow capture, not a missing owner check on the privileged APIs. So this design does not claim that D49 is an enforced boundary against same-origin code. - Operator choice (open, §10):
- (A) UI authority. "Operator-only" means only the operator can turn learning on through the product UI and APIs, under the assumption that all first-party game code on the hub is trusted (D52). A forged switch remains a known residual, limited to bounded shadow recording.
- (B) Unforgeable grant (recommended). Before any collection,
start()requires an operator grant that same-origin script cannot forge: a hub-side or db-backed grant, checked by the hub, bound to the player id and generation. This also closes §0 gap 1, since a hub-side grant can cover a child's own claude.ai account. - Until the operator chooses, what is built is (A). It is described as (A), never as (B).
- Known limits: §0, gaps 1–2.
- Wipe scope: wipe covers every game namespace that holds learning state for that child, local and cloud copies. It cancels pending writes, and a durable readback confirms success. A game may not start recording until its namespace is in the wipe scope and passes G-L5. (Today only Whetstone records and is in scope; §0 gap 3.)
- Priors: population priors (if they ever exist) are a separate artifact with their own expiry. A child wipe doesn't reach into them, which is why priors may only be built from aggregates (§7.3), and aggregates are OFF.
2.4 Fail safe
Missing, corrupt or newer state, a new game, too little data, or learning switched off all give exactly today's behaviour: the fixed ladder and the default bot.
2.5 Saves
- Learning uses a dedicated store key, so it never shares a key with game progress.
- Unknown fields are preserved (D42).
- Newer-version state is quarantined and kept, never deleted or overwritten (SB1).
- Preserved unknown fields are never read as trusted policy.
2.6 Measured against a frozen comparator
Nothing reaches children until it is authorised by a new operator decision (D50), and general release needs G-L9 (§8). Otherwise it stays off.
3. What is recorded
Prefer compact sufficient statistics over raw events. For example, keep per-skill counts and a rating plus per-item counts, not an event log. Per learner:
| Learner | State kept | Bounded |
|---|---|---|
| L1 | per skill: rating, attempts, correct; plus the §4 mastery history (a monotone deque of per-session mastery maxima stored as session ages, and per-level last-four outcomes for the current skill-session). No absolute session counter is stored, and no timestamps | ≤ 60 skills, and the whole learn key + .bak ≤ 8,000 bytes (worst case in §4) |
| L2 | per item/template: attempts, wrong, "marked-wrong-but-looked-right" count, distractor counts, response-time bucket histogram | builder-only report, offline; never in child saves |
| L3 | per judge bucket: confident verdicts, overrules (noisy signal) | ≤ 50 buckets |
| L4a bot balance | nothing per child. Balance and fixed level tables are game code, built offline from bot-vs-bot play | not in saves |
| L4b per-child controller (proposal only, §4) | would store, per child, one Elo per game mode | tiny |
Only IDs and enums cross the learner boundary. Display text is always resolved from trusted templates and passes through the D45 ingest and the D40 fail-closed display sink at final render.
Size. The 100,000-byte cloud cap covers the game's whole payload. The learning namespace gets a measured budget of at most 8,000 bytes, including its .bak copy, JSON/key overhead and per-key timestamps (the built learn.js measures it as stored), with:
- trim and overflow rules;
- a test that fails if the combined per-game payload (progress + its
.bak+ learning + its.bak), measured at the admitted caps, reaches 100,000 bytes, with explicit headroom for progress (G-L1, §8); - a recovery path when the budget is hit.
No "N events" promise stands in for a byte guarantee.
Multiple devices. Sync is newest write per key wins, with no merge, so two devices can overwrite each other's learning state. This is an accepted risk, not a recoverability guarantee: the local .bak restores only the last good state on that device, and there is no cross-device restore contract. A stale device's write can erase another device's mastery history, so the §4 floor is a per-device continuity guarantee (G-L7b scope). A merge protocol would be a separate design with its own tests.
4. Learners (separate objectives, separate promotion tests)
Player mastery, the opponent policy and any shared representation are different learned objects. Each gets its own labels, objective and promotion gate.
L2. Bad-question spotter: builder-only, advisory (Phase 1, built)
- Runs offline on synthetic and replayed historical play.
- Flags items whose behaviour is odd for their rating.
- Output is a Build Board report with provenance. It never changes content automatically and is never shown to children.
- Check: it must flag the old Whetstone identity bug and the old Color Monster Academy parity and long-float bugs when replayed (results in §0).
L1. Right-level question picker: SHADOW (Phase 2, wired in Whetstone)
- State: an Elo-style rating per skill or BKT, per child, plus the mastery history below.
- Shadow mode: the fixed ladder still chooses every question. L1 only logs what it would have chosen and what it predicted, so there is no child-visible effect.
- Live mode (only after a new operator decision and the Phase 3 entry conditions in §9):
- target a predicted success of about 0.70–0.85;
- per-session bound: difficulty can't drop more than one step within a session;
- cumulative floor (below);
- a guaranteed floor of mixed review;
- spaced review.
- Accessibility: timing never penalises a child; L1 has no timing input.
- Why band occupancy is not the measure: choosing items predicted at 70–85% satisfies a "band occupancy" metric by construction. Occupancy is a diagnostic only.
Cumulative anti-gaming floor
The per-session bound alone lets repeated failures ratchet difficulty down one step per session. So, for each skill:
- Invariant: the suggested level is never below
max(ladder floor, M − K), whereMis the child's demonstrated mastery level for that skill over the rolling window. - Parameters:
K = 2,W = 10are proposed defaults, not operator decisions. Both are versioned config, fixed in the §6.1 protocol before Phase 3, and changed only by a recorded decision. - Scope of the guarantee: bounded, not indefinite. Failures can never lower
M; only window expiry can. So a child who deliberately fails every attempt can lower the floor, but only afterWskill-sessions with no mastery shown at that level. That is intended: genuine forgetting must eventually be allowed to lower difficulty.
Mastery history (state per skill). One stored maximum cannot work. Example (levels illustrative, W = 10): history A = [(level 10, session 1), (level 7, session 2)] and history B = [(level 10, session 1), (level 9, session 2)] both have current maximum (10, 1). When session 1 expires, the correct M is 7 for A and 9 for B, which a single maximum cannot tell apart. So each skill keeps:
| Field | Meaning | Bound |
|---|---|---|
mh | monotone deque of (age, level) per-session mastery maxima. age is the number of skill-sessions since the mastery was shown (0 = the current skill-session). Ages strictly decrease and levels strictly decrease from front to back. An entry that a newer, equal-or-higher entry dominates can never be the maximum again, so it is dropped | at most min(W, number of ladder levels) entries; 7 for Whetstone's levels 1–7. Every age is in 0 … W − 1 |
ro | per level, the outcomes of the last four attempts in the current skill-session, with a count 0–4 | one value per ladder level; reset at each new skill-session |
A skill-session is a game session in which the skill was played at least once.
No absolute session counter is stored (bounded rebasing). v2.3 stored an absolute per-skill counter s in two base-36 characters, which covers only 0–1,295 and would wrap or overflow at 1,296. v2.4 stores ages relative to the current skill-session instead. Each new skill-session rebases all retained entries together: every age goes up by 1, and entries reaching W are dropped. Ages, window membership and order are all preserved, and every stored value is bounded by W − 1 however long a child plays. Constraint: W ≤ 36, so an age fits one base-36 character. A W above 36 needs a new encoding and a migration. There is no silent wrap: a stored age outside 0 … W − 1 fails validation (below). An implementation may keep an absolute counter in memory only; if one is ever persisted, it must use variable-width encoding with a measured worst-case cap inside the budget.
Mastery criterion: at least 3 correct of the last 4 attempts at a level, within one skill-session. Fewer than 4 attempts at a level never counts as mastery. Because ro resets each skill-session, old outcomes cannot combine with a single new attempt to credit mastery.
Expiry boundary. The window is the current skill-session and the W − 1 before it, i.e. ages 0 … W − 1. Mastery shown at skill-session s0 has age W − 1 at s0 + W − 1 (still in the window) and age W at s0 + W (expired).
Update order (per attempt at skill k, level ℓ):
1. If this is the first attempt at k in this game session: add 1 to every deque age; drop entries whose age is now ≥ W; reset ro.
2. M = the level at the deque front, or none if the deque is empty.
3. Floor = max(ladder floor, M − K) if M exists, else the fixed ladder's own placement. The suggestion (logged only, in shadow) is computed now, so expiry happens before the suggestion.
4. Record the outcome in ro[ℓ].
5. If ro[ℓ] has 4 outcomes and at least 3 are correct, credit mastery (age 0, ℓ):
- if the deque tail is (0, ℓ') with ℓ' ≥ ℓ, do nothing;
- otherwise pop tail entries whose level is ≤ ℓ (equal levels: the newer entry wins, since it expires later), then append (0, ℓ).
6. Persist, subject to the budget. Mastery credited mid-session raises the floor for later attempts at once; it never lowers it.
Validation on load (untrusted input, §2.1): ages are integers in 0 … W − 1 and strictly decrease from front to back; levels strictly decrease and lie in the ladder range; the deque is no longer than its bound; ro counts are 0–4. If any check fails, that skill's mastery history is dropped and the floor falls back to the fixed ladder's placement (§2.4); it never falls below it.
Multi-device scope: per-device continuity (recommended for Phases 2–3). Sync is whole-key newest write wins (§3). A stale device that reconnects and writes can overwrite a newer mastery history and erase its high-water mark, and the overwriting record may be internally valid. So the guarantee is scoped:
- Per device (adopted for Phases 2–3): on a device whose own stored history has not been overwritten, the invariant holds exactly. After an overwrite, the floor follows the surviving record and is never below the fixed ladder's placement (the safe ladder fallback). The high-water mark can be lost; that is disclosed, and it is part of the accepted multi-device risk.
- Player-wide (not adopted): this would need a monotonic merge, e.g. a union of both deques keeping the higher mastery per window position, instead of last write wins. That needs an alignment rule for ages counted on different devices, plus its own design and tests. It is listed as a future option, not a requirement.
Serialised size. Proposed compact encoding per skill: each deque entry as 2 base-36 characters (1 for the age, 1 for the level); each level's ro as 1 base-36 character (count plus outcome bits form 31 states). No session counter is stored. For Whetstone (7 levels) the worst case is about 14 + 7 characters plus separators and quoting, so ≈ 26 bytes per skill per copy. At 60 skills, with the .bak, that adds about 3.1 KB to the existing per-skill state. This is an estimate, not a measurement. G-L1 must re-measure the worst case of the whole learn key + .bak as stored, with the mastery history included, against the same 8,000 bytes. If it does not fit, the number of skills that carry a mastery history is lowered (skills without one fall back to the ladder placement). The budget is not raised.
L3. Judge "not sure": DEFERRED (Phase 4)
- Blocked until the base …or Die judge contract is candidate-tested (malformed replies, valid zero, fallback, provenance; pl21).
- Then:
- friend overrules are a noisy signal, not ground truth;
- evaluate on an operator-labelled held-out set with a risk-versus-coverage curve, false-accept and false-reject rates, and calibration;
- no cap on the "not sure" rate.
- The three-vote judge's votes are correlated, so they don't count as independent.
L4. Opponents: two separate objects (D51, D54)
| L4a. Bot balance (approved, D51) | L4b. Per-child difficulty controller (proposal only) | |
|---|---|---|
| What it is | offline self-play balance tables and fixed bot level tables for Anime Punch and Fish It | a local controller that adapts one child's bot level from that child's own results |
| Consumes | bot-vs-bot self-play only. No child data | that child's own match outcomes (win/loss per match, per game mode) and their chosen start level (D54). No timing, no free text |
| Runs where | oldisgold | in the browser, on the child's device |
| Stores | balance and level tables as game code, shipped through normal releases | one Elo per game mode in the dedicated learn key: device + hub cloud copy (D48), inside the 8 KB budget |
| Exports | tables only; they hold no child data | no additional aggregate or export beyond its declared hub sync: the learn key syncs to the child's hub cloud copy (D48) like all learning state, and nothing goes to oldisgold, aggregates or server-side training |
| Success measure | balanced bot-vs-bot outcomes; fixed level tables can be assessed on their own | the child's win rate settles at 50–60%. This is difficulty adaptation, not evidence of learning |
| Status | runs in parallel (D51); child-visible changes ship only through normal releases | not authorised. D51 covers bot-only data. D50 approved only the L1 Whetstone shadow pilot. D54 fixes the start level if the adaptive bot is switched on, but it does not decide whether child outcomes may be used. Whether L4b may consume child outcomes is an open decision (§10). If approved: behind the operator switch, shadow first, bot outputs clamped |
L5. Factory risk hints: CI only
- Gate and review statistics live in CI, outside child saves.
5. Relation to the modular ANN research
R1/R2 (the selective-training study) is proposed future research under review. Its results do not automatically qualify game transfer or child personalisation.
A shared trunk with per-game heads is an experimental arm against the simple per-game baseline, not the target architecture. Log-loss is lower-is-better, and the two quantities are defined with that direction:
- new-game gain
G = loss_baseline − loss_candidate(positive = the shared-trunk head beats the per-game learner); - old-game degradation
D_g = loss_after − loss_beforefor each existing gameg(positive = worse).
Adoption rule (predeclared, Phase 5):
- Gain: the one-sided lower confidence bound of
Gis above 0. - Non-inferiority on every old game (intersection–union): for every existing game
g, the one-sided upper confidence bound ofD_gis≤ τ. Adoption needs all of them. Because adoption requires every game to pass, each test runs at the predeclared level with no multiplicity adjustment, and no subset of favourable games may be selected. A small point estimate is not enough: an estimateD = 0.01withτ = 0.02fails if its upper bound is0.20. Scope: this intersection–union argument covers only the fixed, predeclared comparison, i.e. one candidate model, the predeclared set of existing games and one analysis. It does not waive controls for later model selection (trying several candidates or configurations), adding or dropping games, or repeated peeking at accumulating data. Each of those needs its own predeclared correction or a fresh, untouched evaluation set. - Dependence: the same children play several games, so the CI method must account for that, e.g. by resampling whole children (a cluster bootstrap). The method is named in the Phase 5 plan.
- No zero-degradation requirement:
τis an absolute log-loss tolerance greater than 0. - Always reported: signed
Gand everyD_g, each with its confidence interval, whether or not adoption passes. A gain on the new game with a loss on an old one is still reported as transfer evidence; the product decision is separate.
τ, the confidence level and the other exact numbers are fixed in the Phase 5 plan before any data is looked at.
6. Evaluation design
- Held-out checks:
- a sealed manifest of check items from item/template families disjoint from training, with lineage by child, item, template and version;
- minimum N and freshness rules, fixed in §6.1;
- exposure tracked, since repeated checks become familiar;
- no tuning against the holdout.
- Comparator: a frozen fixed-ladder baseline, in a within-child design with carryover handled as §6.1 requires.
- Outcomes: exactly one primary, named in the §6.1 protocol. The default primary is gain on held-out checks; retention on delayed checks is then a key secondary. The protocol may instead name retention as the primary, but never both.
- Secondary outcomes:
- observed calibration by skill bucket, with uncertainty;
- quit and stall rates. Human quits stay in the denominators and are reported. Only harness/automation stalls are excluded, and they are classified separately;
- human play only, with automated play excluded.
- Scope honesty: one child over two weeks is a single-child result and is reported that way.
6.1 Evaluation protocol: required before Phase 3 data
Phase 3 collects no data until a written evaluation protocol exists. It must be:
- dated, and committed before the first Phase 3 session;
- version-pinned: the sha256 of
learn.js, the game build version, the gate-test commit, and the protocol file's own sha256, recorded in the operator decision that authorises Phase 3; - immutable once data collection starts. A change means a new protocol version, a new decision, and the data already collected is reported under the old protocol.
It must state, at minimum:
| Item | Required content |
|---|---|
| Purpose | Phase 3 produces feasibility, calibration, safety and exploratory within-child evidence. It is not general-release evidence and cannot meet G-L9 |
| Primary estimand | exactly one, e.g. the within-child difference in held-out check accuracy (or log-loss, with direction stated as in §5) between L1 and fixed-ladder conditions |
| N / power | for Phase 3: the explicit label "exploratory, single-child; no confirmatory claim". A power calculation is required only on the confirmatory route (§9, Phase 3C) |
| Carryover | children keep what they learn, so a crossover does not reset knowledge and a washout does not undo learning. The protocol picks one design and justifies it: (a) arm by skill family: disjoint skill families randomly allocated to L1 or the ladder, with cross-family transfer reported; or (b) a period crossover with a stated period × arm analysis and its carryover limits; or (c) parallel groups across children (confirmatory route only). A washout alone is not accepted as handling carryover |
| CI method | the named method (e.g. a block bootstrap over sessions, or a permutation test over the randomised allocation), the number of resamples, and the confidence level |
| Denominators and missing data | the assignment denominator (every scheduled held-out and delayed check counts, as in intention-to-treat); how missing held-out or delayed outcomes are handled and reported; how human inactivity and stalls are classified; human quits stay in the denominator; automation stalls are excluded and listed |
| Stop rules | the fixed duration and allocation schedule; safety stops that trigger instant rollback to the fixed ladder (operator instruction, a child-distress or quit-rate threshold stated as a number, any gate going red). Every safety stop is reported with its trigger. No early stop for efficacy unless an alpha-spending rule is predeclared |
| Multiplicity | the single primary; for the secondaries, either a named correction (e.g. Holm) or the label "descriptive only" |
| Holdout manifest | the fixed manifest file sha256 and seal hash. Current candidate: ci/learn-holdout-manifest.json, seal dcf9ddf92f64fe6934e850f33410ee204c3d00378e35039facf9d3799409b888. The protocol pins whatever is current at authoring; G-L8 enforces it |
| Anti-gaming config | the values of K and W (§4) |
| Analysis plan | inclusion/exclusion rules (human play only), and what is reported if the result is null or negative |
The confirmatory protocol (Phase 3C) is a separate document with the same rules plus: the intended population (the children a general release would reach), the independent unit (the child, not the session or the item), a power calculation giving N in children, and a randomised design from the carryover options above.
7. Architecture
1. Module learn.js in the factory kit (games/_factory/learn.js, standalone):
- learn.observe(id, enumOutcome) records an outcome, schema-validated;
- learn.suggest(eligibleIds) returns an ID that must be one of eligibleIds; the caller re-checks;
- learn.flag() is builder-only;
- learn.botLevel() is clamped and starts at the player's chosen level (D54). It adapts from child outcomes only if L4b is approved (§4, §10);
- learn.wipe() follows the §2.3 scope.
2. Storage: MM.store("<game>-learn-v1", {v:1,…}, migrate), a dedicated key with in-place migration (D42) and the ≤ 8,000-byte budget (§3). The §4 mastery history is a schema addition, so it needs a migration and the G-L6 checks. MM.store syncs to the hub cloud copy for signed-in children; that is the D48-approved path. The operator switch (mm-learn-op) is device-only and is not stored here.
3. Aggregates: OFF (D48). Not built. If ever proposed, they need:
- a closed aggregate-only export schema: no UID, no per-child rating, no rows, no arbitrary keys;
- bounded contribution per child;
- minimum cohort and rare-cell suppression;
- release cadence and query controls;
- revocable opt-in that cancels queued uploads;
- a new operator decision.
With one family the cohort is too small, so the answer today is no export.
4. Compute: in the browser, simple statistics only. Training experimental neural or shared-trunk arms offline is part of the Phase 5 research that produces the §5 evidence. Shipping fixed, versioned weights to children happens only after that evidence meets §5. L4a self-play runs on oldisgold with bot-only data (D51).
8. Release gates (executable, evidence retained, rollback = fixed default)
| Gate | Checks |
|---|---|
| G-L1 budget | the gate FAILS if the measured combined per-game payload (progress + .bak + learning + .bak), built at the admitted caps (the game's own migrate caps and the learning caps), reaches 100,000 bytes or leaves less than the stated progress headroom. Logging the worst case is not enough. The learning namespace, including the §4 mastery history at its worst case, is ≤ 8,000 bytes including overhead; overflow trims and recovers. If a game's own pre-existing caps make this impossible (Whetstone today, §0 gap 4), there are two options: lower those caps (a game change), or narrow the claim to "the realistic payload fits". A narrowed claim is stated in the gate output and in §0, and is never reported as the admitted-cap result. The current test (v2.1 contract) passes only the realistic case |
| G-L2 fail-safe | missing, corrupt or newer state, and switch OFF, give exactly the fixed behaviour; tested |
| G-L3 sink | plant-and-render runtime tests with unsafe, corrupt or newer learning state, checked in the DOM, attributes, canvas and document title |
| G-L4 trusted boundary | suggest can't return an ID outside the eligible set; injected bad IDs are rejected |
| G-L5 wipe | wiping clears local and cloud state in every game namespace that records, cancels pending writes, reads back as clear, and leaves unrelated unknown fields preserved. Run per game before that game may record |
| G-L6 migration | unknown fields survive; newer versions are quarantined, not deleted |
| G-L7 anti-gaming (session) | adversarial failure trajectories within one session can't push difficulty more than one step below the session start, or below the floor |
| G-L7b anti-gaming (cumulative) | runs on the shadow "would have chosen" log. Cases: (1) mastery shown at level m in session s0, then all-failure sessions: the suggestion is never below max(ladder floor, m − K) at sessions s0 … s0 + W − 1, and the floor may relax only from s0 + W; (2) several maxima: the A/B example in §4 gives M = 7 and 9 after expiry; (3) ties: an equal level in a later session extends the window; (4) fewer than 4 attempts never credits mastery; (5) a corrupt mastery history, including an age outside 0 … W − 1, falls back to the ladder placement; (6) long play / encoding boundary: a skill played across the 1,295 → 1,296 skill-session boundary (and well beyond, e.g. 1,400 sessions) keeps every stored age in 0 … W − 1, with no wrap or overflow, and gives the same floor as a fresh history with the same recent sessions; (7) stale-device overwrite / reconnect: device A shows mastery at level m; device B, holding an older copy, reconnects and writes last. Device A's next suggestion follows the surviving record, is never below the ladder placement, and the test records the high-water mark as lost (per-device scope, §4). It does not assert a player-wide floor. Scope: per-device continuity. Not implemented: no test exists yet (§0 gap 7) |
| G-L8 holdout integrity | the sealed manifest matches the hash pinned in §6.1; no overlap between training and holdout lineage |
| G-L9 promotion | general release of live L1 only if the held-out learning gain against the frozen comparator has a CI lower bound above 0, under the confirmatory protocol (Phase 3C) with its N/power met. An "exploratory, single-child" result cannot meet G-L9. Band occupancy is never a promotion gate |
| Cross-game read (documents) | a game can read another game's learn key: expected to succeed today and recorded as such (D52). It becomes a denial test only when isolation ships |
9. Rollout (simplest first)
| Phase | Work | Child-visible? | Status / exit |
|---|---|---|---|
| 0 | Operator decides the boundary, authority and pilot | — | done: D48–D52, D54 |
| 1 | Check Whetstone's save/event coverage; run L2 offline on synthetic and replayed historical data | No | built; flags the known old bugs (§0); builder report |
| 2 | L1 in shadow mode in Whetstone, operator-enabled for one child; learning state on the device and the hub cloud copy (D48), no export | No | wired, not exited. No pilot enablement or collection until G-L1 passes as defined in §8 for the pilot state (or the operator accepts the narrowed claim), so that progress saves are never put at risk. Exit: calibration measured; G-L1..G-L8 green under a test contract updated to v2.4; the §4 mastery history built and G-L7b green; §0 gaps 3–4 closed for Whetstone |
| 3 | Bounded live pilot: L1 against the fixed ladder, one child, fixed duration, instant rollback. It produces feasibility, calibration, safety and exploratory evidence only; it cannot meet G-L9. Requiring proven benefit before any trial would be circular, so none is required | Yes | entry: a new operator decision (D50) and the dated, version-pinned §6.1 protocol and G-L1..G-L8 plus G-L7b green, under a test contract updated to v2.4. (The D50 row says G-L1..G-L8; G-L7b is this design's added condition.) Exit: protocol-reported result, labelled exploratory |
| 3C | Confirmatory evaluation, prospectively specified in its own protocol (§6.1): intended population, the child as the independent unit, power-based N, randomised design with carryover handled | Yes | entry: its own operator decision and frozen protocol. It needs more than one child, so it may need a decision on cross-child data handling first (§10). With one family it may be infeasible; then G-L9 is not met and live L1 is not generally released. That is an acceptable outcome |
| — | General release of live L1 to all players | Yes | G-L9 met on Phase 3C evidence |
| L4a (parallel) | Bot balance: self-play on oldisgold, bot-only data (D51); changes ship through normal releases | Via releases | own gates |
| L4b | Per-child adaptive bot level (§4): proposal only | — | needs an operator decision on child-outcome use; then shadow first, starting at the chosen level (D54) |
| 4 | L3 judge "not sure", only after the pl21 judge contract ships | Yes | own gates |
| 5 | Neural or shared-trunk arm: offline research, then adoption only per §5 | — | predeclared G lower bound > 0 and every D_g upper bound ≤ τ |
10. Decisions
Decided (operator, 19 Oct 2026):
- D48 device + hub cloud copy; no export.
- D49 operator only switches and wipes; GOAT accounts cannot. What this means against same-origin code is open; see item 1 below and §2.3.
- D50 Phases 1–2 approved, shadow only; going live needs a new decision. The row requires G-L1..G-L8 before any live step; this design also requires G-L7b, and does not edit the row.
- D51 L4 opponent track runs in parallel, bot-only data (L4a here).
- D52 proceed with the documented same-origin boundary; plan isolation in
proposals/storage-isolation.md. - D54 the adaptive bot starts at the player's chosen level.
Open (for a future operator decision):
1. D49 meaning and the operator grant (§2.3). Either (A) "operator-only" is UI authority under the trusted-first-party-code assumption, with forged-switch shadow recording accepted as a residual; or (B, recommended) require an unforgeable hub-side or db-backed operator grant before any collection. (B) also lets the switch be enabled on a child's own claude.ai account (§0 gap 1).
2. G-L1 for Whetstone (§8): lower Whetstone's own save caps so the admitted-cap gate can pass, or accept a narrowed "realistic payload" claim.
3. Phase 3 go-live: authorise the bounded live pilot, with the §6.1 protocol attached.
4. Phase 3C: whether and how to run a confirmatory evaluation (population, cross-child data handling), with its own protocol.
5. L4b: whether a per-child bot controller may consume that child's match outcomes on device (§4).
6. Storage isolation: adopt the proposals/storage-isolation.md design (a separate platform project).
7. Numbers: K and W (§4; W ≤ 36 under the current encoding), and τ and the confidence levels (§5), each fixed before the data they govern.
8. Player-wide floor: whether the §4 floor should later become player-wide through a monotonic merge (not required for Phases 2–3).
Appendix A. Revision history
| Version | sha256 | What changed |
|---|---|---|
| v1 | 7243190113d7f1838893719e37174dd4651fec7cfe005ccdb24abe522d2edc97 | first draft |
| v2 | eac0771a85432175743962583a993823741f8a98f0c2439f4998261be1a40acd | folded 19 review findings, none rejected: Codex master-mind-games seq 1012 (10), codex-ann-coordinator ANN seq 8421 (6), muse-alpine-libra ANN seq 8416 (3). Codex's SDK claims verified at games/_sdk/mm-sdk.js:25,27,55,57,99 and docs/02-architecture.md:92-110 (CLOUD_MAX 100,000 bytes for the whole payload, newest write wins, no opts.merge). Mapping: privacy/data locations → §2.2/§7.3; trusted boundary → §2.1/§7.1/G-L4; sink → §3/G-L3; size and sync → §3/G-L1; authority and wipe → §2.3/G-L5; L1 metric → §4/§6/G-L9; L3 cap → §4; gates → §8; build order → §9; R1/R2 status → §5; separate objectives → §4; human play only → §6 |
| v2.1 | 5645df3780b99ec32fc55c314098ef616ceb57e713fa40a08f91e1d039ba1587 | working copy, never frozen at the time; it is the exact artifact Codex hashed in seq 1047 and 1066, snapshotted afterwards (a retrospective snapshot, not a historical freeze). Added §12 amendments from round 2 (Codex MM seq 1019/1022/1029, coordinator ANN seq 8426/8430): cloud path via MM.store, budget including .bak, same-origin boundary, G/D direction, live trial vs release, quit denominators, and the D48–D52 decided list. Left the v2 body unchanged, so it contradicted itself |
| v2.2 | 08324a7976b2d8ce99efcd83edc92cb1d92a4f8c0377f8c97b3b93496b50d714 | one current policy. Resolved Codex seq 1047/1066: §2 table, §9 Phase 2 and §10 rewritten to the decided state (D48–D52, D54); old questions removed; added the cumulative floor and G-L7b; added the §6.1 pre-data evaluation protocol as a Phase 3 entry condition; multi-device loss restated as an accepted risk; added §0 Status and known gaps from the pilot; history moved here |
| v2.3 | bcf68c80af35e2c6dfa8eabffee5e77c8d7cf87bfeb3e9012fdb3ffda68a69cb | folded the ANN coordinator's v2.2 review. Provenance: the coordinator requested the four amendments at ANN seq 8495 (REVIEW.json 58b51f18…, manifest 3f066949…); the author accepted them at ANN seq 8497; the coordinator's root readback of v2.3 is ANN seq 8504 (REVIEW.json fc0cde9a…), which accepted the substance with two document corrections (folded in v2.4). That readback is design acceptance, not code verification. (v2.3's own text said "the coordinator accepted all four amendments", which conflated these steps.) LFP22-1: bounded mastery history (per-skill session counter, monotone deque of per-session maxima, per-level last-four outcomes), minimum 4 attempts, update order, expiry boundary, load validation, size estimate, and G-L7b cases including the A/B example. LFP22-2: Phase 3 is feasibility/calibration/safety/exploratory only; new Phase 3C confirmatory route; carryover handling in §6.1; G-L9 unchanged in strength. LFP22-3: intersection–union non-inferiority, where every D_g upper bound must be ≤ τ, with signed G and every D_g always reported. LFP22-4: L4 split into L4a bot balance (approved) and L4b per-child controller (proposal). Minor notes folded: C1 bounded guarantee and indexing; C2 single primary, denominators, missing data, safety-stop reporting; C3 wipe precondition and unverified built claims; C4 offline research vs shipping weights. Phase numbering as in v2.2 (Phase 4 judge, Phase 5 neural) |
| v2.4 | (this file) | folded the ANN coordinator's root readback of v2.3 (ANN seq 8504, REVIEW.json fc0cde9a5bce731b…, sha verified) and codex-game-review on v2.2 (master-mind-games seq 1097 and its precision follow-up 1098; still relevant to v2.3). Triage: raised 12 (8504: 4; 1097: 4; 1098: 4). 1098-1 and 1098-2 duplicate 1097-1 and 1097-2 and refine them, so there are 10 distinct findings. Folded 10. Rejected 0. Code claims checked against source before folding: start() trusts only readSwitch() (learn.js:184), while operatorEnable and operatorWipe call ownerCheck() (:283, :312); the G-L1 admitted-cap case is only console.logged, not asserted (learn-gates-test.mjs:323-325); the test header names design v2.1 (:1); the stale wipe comment is at learn.js:307. Folded: 8504-1 session counter replaced by bounded ages (rebased together each skill-session, W ≤ 36, no wrap), plus G-L7b case 6 at 1,295 → 1,296; 8504-2 appendix provenance (requested 8495 / accepted 8497 / readback 8504); L4b wording "no additional aggregate or export beyond its declared hub sync"; IUT scoped to the fixed predeclared comparison; 1097-1/1098-1 D49 residual restated as a storage-integrity limitation of Phase 2 shadow capture, with an operator choice: (A) UI authority under trusted first-party code, or (B) an unforgeable hub/operator grant (recommended); 1097-2/1098-2 G-L7b scoped to per-device continuity with a safe ladder fallback, player-wide merge left as a future option, and G-L7b case 7 for stale-device overwrite/reconnect; 1098-3 G-L1 must fail at the admitted caps, or the claim is narrowed; Whetstone's pre-existing caps noted; 1097-4 Phase 2 marked not exited, with no enablement or collection until G-L1 passes; 1097-3 §2 given real 2.1–2.6 subsections so the §2.x references resolve, and the learn.js:307 comment recorded as a code TODO (code not edited); 1098-4 test-contract drift recorded: the test file is the v2.1 contract, G-L7b is not counted as implemented, and D50's G-L1..G-L8 wording is noted against this design's added G-L7b condition |