Repository navigation
fix(completion): cross-fade the M1 accent seep under Reduce Motion - #1100
Conversation
The accent glow behind the completion glass was gated on M10 being in its non-Reduce-Motion `.rise` form, so with Reduce Motion on the glow was dropped outright while `CompletionMotionPlan.accentSeep` computed a `.crossfade(0.25)` that nothing rendered (#1028 B-5 finding). design.md §6 M1 + hard constraint 2 say Reduce Motion swaps the seep for a 0.25 s crossfade, not off. - New `CompletionAccentGlow` view resolves which glow plays from the plan alone: M1 `.seep` / `.crossfade` when the ritual plays, M10 glow for a live loss, nothing for review. `.seep` keeps the 0.6 s ease-out; `.crossfade` is an opacity-only 0.25 s ease-in-out. - `CompletionOverlayScaffold` mounts it unconditionally (no `panelRise` gate) and gains an internal `completionReduceMotionOverride` environment seam because `accessibilityReduceMotion` is read-only. - `CompletionAccentGlowTests` pin the rendered path: form + animation resolution, and an offscreen AppKit render of the real scaffold whose bottom band must carry the tint under Reduce Motion. Closes #1065 part of #1014 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Code review MINOR on #1100: `CompletionAccentGlow.form(for:)` / `animation(for:)` and `CompletionOverlayScaffold.effectiveReduceMotion` are pure value transforms nested in View types, so they were implicitly `@MainActor` and every fresh compile of the nonisolated test body warned "implicitly asynchronous". Marking them `nonisolated` removes the warnings; a fresh `.build` of GameShellKit now compiles the test target without them (134 tests / 20 suites green). part of #1065 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
PM merge record, head Code review + acceptance: ACCEPT / MERGE, no BLOCKER or MAJOR. The reviewer reproduced everything first-hand:
Two MINORs were fixed before merge: three pure value transforms are now Behaviour evidence (simulator, both apps). The bottom-band tint under Reduce Motion now matches Reduce Motion off — Sudoku −13.2 vs −13.2, Minesweeper −7.3 vs −7.3, against −6.0 and +1.1 before the fix, where the glow was absent. Frame strips show the panel fading in place ( Deferred, recorded in the PR body: the Reduce Transparency question (the glow sits behind now-opaque glass). The 82 pre-existing implicit- |
What
design.md §6 row M1 says the completion accent seep is
0.6s ease-outby default and acrossfade 0.25sunder Reduce Motion (hard constraint 2: "Reduce Motion 不是關掉,是換 fade"). On main the glow was gated on M10 being in its non-Reduce-Motion.riseform, so with Reduce Motion on it was dropped, whileCompletionMotionPlan.accentSeepcomputed a.crossfade(0.25)that nothing rendered (#1028 B-5 finding). This aligns the code to the design doc; the doc is untouched.CompletionAccentGlowview (Packages/GameShellKit/Sources/GameShellUI/Completion/CompletionAccentGlow.swift) resolves which glow plays from the plan alone: M1.seep/.crossfadewhenever the ritual plays; M10's 0.6 s glow for a live loss (the MS-lossstatus.errorwash, unchanged); nothing forreview..seepkeeps today's.easeOut(0.6);.crossfadeis opacity-only.easeInOut(0.25)— no gradient sweep, no positional motion.CompletionOverlayScaffoldmounts it unconditionally — theif case .rise = motionPlan.panelRisegate is gone. It also gains an internalcompletionReduceMotionOverrideenvironment seam (same pattern ascompletionHeroSkipsReveal) becauseaccessibilityReduceMotionis read-only inEnvironmentValues(typecheck-verified);nilby default = the live setting decides.Tests (
CompletionAccentGlowTests, 10 tests, rendered path not the dead value)form(for:)/animation(for:)— the resolutions the view body renders through.reviewis the no-glow control.EnvironmentValues().completionReduceMotionOverride == nilandeffectiveReduceMotion(override: nil, system:)follows the system value.Mutation 1 — restore the
if case .risegate → red:Mutation 2 — render
.crossfadewith the sweep's animation → red:Mutation 3 — seam default
false→ red:Honest limit: the offscreen capture is one settled frame, so it proves the glow exists under Reduce Motion at the sweep path's settled tint; the crossfade-vs-seep timing is pinned on the animation function the body calls and shown frame-by-frame on the simulator below.
swift testGameShellKit: 134 tests / 20 suites green (bounded run, fresh.build).Snapshots — nothing re-recorded
SudokuUITests
CompletionOverlayScaffoldTests(5) +CompletionViewTests(3) and MinesweeperUITestsMinesweeperCompletionSnapshotTests(7) fail strict.imageon this branch and identically on origin/main 73f76b4 (same 8 + 7). All 15 head-rendered images are byte-identical (cmp) to the base-rendered images; the diffs against the committed baselines are 0.2–2.0 % of pixels, all in the card text / CTA label rows, bottom glow pixel-identical — the #1097 macOS 27 renderer drift. The committed baselines render with Reduce Motion off, so identical output also proves the seep path did not move. TolerantMinesweeperBoardTerminalOverlaySnapshotTests(scaffold over a live loss board) passes. Left to #1097.Simulator evidence (own sim, iPhone 17 Pro, iOS 26.4, Reduce Motion set through the Settings app + read back from
defaults)Mean B−R of the settled completion screen's bottom band (y 640–870 pt), same method as the issue table (
build/1065/tools/band.py; re-running it on the #1028 PNGs reproduces −13.2 / −6.1 / −7.3 / +0.9):Per-frame band tint from the first changed frame (
bandtimeline.txt): Sudoku RM0 reaches −13.3 at +566 ms (0.6 s seep under the 0.45 s rise); RM1 reaches −13.0 at +333 ms and settles by +467 ms (0.25 s crossfade, bounded by M10's 0.45 s fade-in-place). Minesweeper: RM0 settles at +566 ms, RM1 at +400 ms. Panel (shift.py, band y 240–720): RM0 rises dy +60→0 (Sudoku) / +21→0 (MS) over ~450 ms; RM1 dy = 0 on every frame after the first two near-blank frames — fade in place, no sweep.Local evidence (gitignored
build/, this worktree):build/1065/{RM0,RM1}/{sudoku,minesweeper}/—completion.png,band.txt,completion.mov,completion-frames/(33 ms),completion-sheet.png(frame strip),shift.txt,bandtimeline.txt,toggles.txt.Open questions (not fixed here)
lossWashIsUnchangedByResolvingFromThePlan). M10's Reduce-Motion column is silent on its glow; if constraint 2 applies to it too, that is a one-line change inCompletionAccentGlow.form(for:)plus a design.md row.Closes #1065
part of #1014
🤖 Generated with Claude Code