diff --git a/PROJECT.md b/PROJECT.md index ecf5830..89d85eb 100644 --- a/PROJECT.md +++ b/PROJECT.md @@ -4922,6 +4922,47 @@ This is stronger measured opposition, not a measured human win rate: league 5 st Oliver's level-26 phone verdict. Oliver requested merging to main and pushing for his iPhone test; this release carries the cache-busting entry version v350. +## Session log: THE PHONE PLAYTEST RECORDER (2026-09-07, v351) + +Oliver: difficulty is better, but title defences still come easily — log real phone play so we +can explain his wins per game before tuning again. The old 50-entry `G.missLog` is tennis-only +and cannot do that. `src/recorder.js` adds opt-in, local-only diagnostics for solo cup, Climb and +training. No video, server endpoint, automatic upload, package or change to difficulty. + +Use **STATS → RECORD PLAYTEST**, play, then **STATS → STOP RECORDING → EXPORT RECORDING**. +`?record=1` also arms/resumes it. A small REC below the quit chip marks active capture; normal +reloads restore the saved file paused. Export uses the phone's file-share sheet when supported, +otherwise a JSON download. Cancellation/export never deletes data. Replacement is a separate, +explicitly labelled button. The native iPhone share sheet still needs Oliver's phone verdict. + +- Events identify run/segment, opponent encounter, roster position, cup round, point number and + the pre-verdict game/point score. Run starts capture build, viewport, court, league, player level + and actual TUNE values. No player name, account id, sync key or other credentials are exported. +- Both serves and returns capture quality, aim, resulting velocity, positions and habit/read + state; player swing attempts and actual probabilistic opponent whiffs get their own events. +- `resolveInterlude` accepts an optional diagnostic reason. Its existing decided/ending guards + gate the recorder too. Reasons distinguish out, double fault, double bounce, dead roller, + passed player, body destruction and the two early-unreachable adjudication paths. Record + BEFORE scoring mutates the state, including a point that kills the player. +- Every point includes up to 90 live update frames of player/opponent/ball movement (roughly + 1.5 s at 60 Hz, deliberately not the inaccurate old 20 Hz closest-approach sampler). Timestamps + use update time; this is evidence for analysis, not a deterministic replay. An ace is derived + from the shot sequence and verdict, not guessed at the logging call site. +- Completed points and run boundaries checkpoint to `splatter-playtest-v1` in localStorage. + Visibility/pagehide also save; backgrounded/resumed play gets explicitly marked segments. + Event data caps at 1.5 million JSON characters, then stops with REC FULL instead of silently + discarding the beginning. Storage errors preserve in-memory export and report the limitation. + +Validation: 138 tests pass, default seed plus 1, 42, 1234 and 20260907. A 180 s Level-26 cup +simulation has identical gameplay state AND RNG state with recording on/off (reset P.dustAcc in +the comparison fixture: resetState's Object.assign leaves that cosmetic accumulator behind). +Tests cover actual shots/scoring, guarded duplicate verdicts, fatal losses, early player wins, +quota failure, size limits, reload recovery, exclusions, file sharing/cancel/download fallback. +Browser checked: opt-in, REC, run exit, Stats export controls, reload paused with export retained, +download-request feedback and no console errors. The in-app browser did not expose a download +completion event; the exporter branches are tested with platform stubs, real Safari export is +the next phone check. New file is under src/, already covered by Docker's COPY. + # OPEN — leftovers, in rough priority order Nothing here is broken; these are known gaps and unverified risks. diff --git a/index.html b/index.html index fd785a3..7d26484 100644 --- a/index.html +++ b/index.html @@ -657,6 +657,15 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat padding-bottom: max(10px, env(safe-area-inset-bottom)); border-top: 2px solid var(--line); background: var(--solid); } .foot .btn { margin-bottom: 0; } + #recLamp { position: fixed; right: 9px; top: max(52px, calc(env(safe-area-inset-top) + 44px)); + z-index: 5; color: #ffd8ca; background: #59251f; padding: 4px 6px; + font: 10px Silkscreen, monospace; pointer-events: none; } + #recLamp[hidden], body:has(#intro:not([hidden])) #recLamp { display: none; } + #recorder { border-top: 1px solid var(--line); padding-top: 10px; margin-top: 14px; } + #recorder p { font: 11px/1.5 Silkscreen, monospace; color: var(--dim); } + #recorder .btn { min-height: 44px; } + #recorder .btn:disabled { opacity: 0.4; } + #recNew[hidden] { display: none; } /* Gore gets a front door; the records become quiet navigation, with full touch targets. */ #mGore { display: flex; align-items: center; justify-content: center; gap: 12px; padding: 9px 12px; border-color: #804338; background: #281b19; color: #f1d6bf; } @@ -1033,6 +1042,14 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat your career now, in four numbers that always mean the same thing, plus the two things the screen is actually good at: the bodies you have collected, and the rack you fired. -->
+Record league, Climb and training points to investigate difficulty. Export here after playing.
+ + + + +