You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This commit was created on GitHub.com and signed with GitHub’s verified signature.
Added
Live map update on grind trail: after Explore/Practice / Continue-on-trail, stone colors (and fog when cheap) refresh on the client from the answer map payload + /api/map before Continue is shown. grindTrail.done + client trailDone exclude stones finished this session so Continue never re-offers them.
Quest complete high-five: when a quest finishes, a celebratory modal shows the quest medal and Campaign Trail progress (N/12), then three choices — 🧠 Reinforcement (light practice on quest topics / due reviews via /api/reinforce), 🛤️ Grind a Trail (Map zoomed to a quest subject), 🎲 Mini-games (free play). Cannot dismiss into silence; fires once when quest.done flips. Preview: /#quest-celebrate-preview.
Continue on this trail (free-play grind): after a My World map mini-session ("Explore now!" / "Practice now"), the end screen offers 🛤️ Continue on this trail (next ready/learning/review stone in the same subject, preferring forward in the same domain) as the primary action, with Back to map secondary. No auto-jump to the map when a next stone exists. Does not force quest mode, skip mastery, or award medals. Engine: nextTrailTopic; miniAfter includes next.
Learner profiles (#87): one household, several kids in one app.
Per-learner records (#82): a profiles registry; every per-learner key (progress, logs, quest packs, boosts, sandbox, clock offset) is prefixed L:<id>: for added learners, while the primary learner keeps the original keys. migrate-profiles.mjs snapshots every key, adds the registry (idempotent) and verifies the record is untouched; --rollback removes it. /api/state gains profile. No registry = one learner from [vars], exactly as before.
Who's playing? (#83): with 2+ profiles the kid app opens on a picker (avatars; a 🔒 PIN pad for profiles with a 4-digit PIN). The pick is remembered on the device (jl cookie, 1 year), so one-learner installs and remembered devices go straight in; "🔄 Switch player" in the footer. PINs are a kid-level guard: salted SHA-256, an HttpOnly token cookie, 1 s delay on a wrong PIN, 401 needPin on the kid APIs. GET /api/profiles, POST /api/profiles/select, POST /api/profiles/forget.
Parent view per kid (#84): a card per kid (avatar, grade, points, medals, mastered, day streak, last played) switches the report (/parent?learner=<id>); "👪 Players" adds a learner (name, grade, interests, avatar, optional PIN) and changes avatars, interests and PINs. POST /api/parent/profiles, POST /api/parent/profiles/update; an optional PARENT_PIN secret guards them. The primary learner's name and grade stay in [vars].
Per-learner nightly (#85): export-progress.mjs, publish-lessons.mjs, backup-progress.mjs and migrate-medals.mjs take --learner <id> (or LEARNER_ID); without it they act on the primary learner exactly as before (and stay offline with --offline). The export adds profile (id, age, grade, interests) and learners; added learners' exports and backups go to <id>/ sub-folders. LESSONS.md / NIGHTLY.md: loop over learners, one quest per kid at their own level. Wrangler calls time out after 3 minutes instead of hanging.
Placement quests (#86): a quest pack with "placement": true finds a new learner's level. Stages carry ladder + band and run from grade level down; ≥ 2/3 right on the first try passes a stage and skips the rest of its ladder; the result (S.placementQuest: level per ladder, beyondMap for subjects passed at the taxonomy's ceiling) goes to the export with taxonomyCeiling. No review gates or pizza starter in a placement quest. The validator checks the placement rules (4-7 stages, ≥ 3 subjects, 2-4 graded per stage, bands descending). The built-in diagnostic window now follows the learner's age (unchanged for ages 10-10.9) and uses a subject's top band above its ceiling. LESSONS.md "Placement quests"; packs/examples/example-placement.json.
The export's quests.nextStep says PLACEMENT WAITING while a learner's placement quest is unfinished, so the nightly author never replaces it; publish-lessons.mjs prints the learner's real key.
Points Market (#74, idea credit: Christopher): points buy fun, choice and peeks, never mastery, medals or permanent fog clear.
Wallet (#68): S.wallet spend ledger; balance = earned (S.points, never decremented) − spent, computed server-side. GET /api/market, idempotent POST /api/market/buy {pid,item,meta} (same purchase id → one charge; one-time items → already), wallet on /api/state, purchase log events, a per-PT-day earned snapshot (last 60 days) and a market block in the export (spent7d/earned7d for price tuning). The whole pre-Market earned total is spendable on day one.
Skins + Market screen (#69): a 🛒 Market tab (balance, cards, buy → confirm → owned, wear / take off; keyboard and reduced-motion safe). 13 skins in 4 slots (ships 300–900, avatars 300–800, boss skins 600–1,000, map themes 800–1,500), bought once, owned forever, equip free (POST /api/market/equip). Equipped skins draw on the Map ships, a new avatar chip in the header, the boss emoji/frame and the Map sea/land/fog palette. The ⭐ chip now shows the spendable balance; the parent report shows both earned and spendable.
Scout flare (#70): 200 ⭐ lights one fogged island for 24 hours (from the Market, or "🎆 Light it up!" on a zoomed Map island): thinned fog, names on the ready/learning stones there, ❓ markers for its unclaimed fog finds. The fog returns at expiry; fogState adds flares only, and mask, clear counts and find eligibility are unchanged.
Prove-it ticket (#71): 150 ⭐ on a review that is due now (Market, or the "🎟️ Prove-It tickets" button on a review-gate card): one harder question, one try (a check item's "why" tier, or the generator one level up). Right first try = the normal review pass (Leitner box +1, new due date, normal points) + 20 bonus earned points; a miss = the review stays due (≤ 1 day, no demotion), no refund. Max 2 a Pacific day; POST /api/market/prove {pid, topicId}. Gates are unchanged: the gate on screen keeps its own item.
Pick your path (#72): 100 ⭐ picks the next new quest's topic (from 2–3 ready frontier topics offered that Pacific day, one per subject), and another 100 ⭐ picks its story theme (8 themes). One open pick of each kind; Play shows "🧭 Next quest: your pick!". The export lists market.choices / market.openChoices and adds a MARKET CHOICE line to quests.nextStep. A quest pack marks a pick used with "honors": {"path", "theme"} (validated by publish-lessons.mjs); "declined" closes the pick and refunds it with a new ledger row. LESSONS.md "Market choices", NIGHTLY.md and docs/NIGHTLY.md tell the nightly author how.
Hidden chests + cheap extras (#73): mystery chests appear in the Market only after nearby fog clears (20 topics world-wide, 15 % of a subject) and hold a cosmetic prize fixed by the purchase id (rare chest-only skins, stickers, titles). Extras: 🤫 Jane whisper hint token (40, also wires the existing Hint Token fog find), 🛡️ streak shield (60; keeps 🔥 on one missed question, never the ⭐ run or any mastery), 🧊 streak freeze (80; covers one missed day of the new 📅 days-played streak). Stock caps 5/3/2. POST /api/whisper; stats.daysPlayed in the export.
Campaign Trail + Fog Map (#54): always-on Play trail + taunt, Quest of Quests overlay, literal fog on the Map (direct mastery clears), fog finds (chests/secrets/power-ups, D11 garnish only), and Final Boss unlock persistence with a Finale authoring hook.
Fixed
Cosmetics, Jane whisper, fog, and review bursts (Landon feedback, #95): worn ship/theme/sticker show where kids look (map hero ship, app background, header sticker chip); whisper is a clear 💡 control that only spends a token when a real trap exists; map fog is darker with safe draw guards; free-play reviews no longer dump randomly or repeat the same overdue topic back-to-back; empty quest strip is honest when none is live.
New subject re-asking the same kid MCQ (#96): "🎨 New subject" no longer re-serves one fixed kid-prompt topic all sitting — skip fixed prompts already in recent when picking focus, and release focus after a fixed quiz item completes.