Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions projects/sunwake-a6f3/JOURNAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,26 @@ Design: preserve the warm sail-and-dunes world, Bricolage display and DM Sans bo
- Visual review covered the desktop atlas, mobile atlas at 320/390 px, a three-seal mobile finish, the hangar/Kestrel, fullscreen landscape results, and the new static thumbnail. Screenshots and exploratory scripts stay ignored under `qa/artifacts/`.

- 2026-09-24 (codex / gpt-6): Built and balanced the Sun Atlas expansion, preserved old saves and course physics, added collectible wakes/Kestrel and controller support, and passed engine/unit/browser/publication checks. Version 0.3 is ready for the automatic pull-request publishing workflow.

## Fourth journey — polish and smoothness

- [x] Measure rendering cost, stop unnecessary background/paused drawing, and smooth presentation between physics ticks without changing course rules.
- [x] Fix input, controller handoff, focus, scrolling, and fullscreen edge cases; make return-to-flight actions immediate and dependable.
- [x] Refine flight preferences, motion/accessibility, live feedback, and small-screen controls while retaining the illustrated world.
- [x] Verify existing saves, identical physics, realistic browser flows, performance, and the exact publishing gate; publish the update.

Design stays with Sunwake's existing sail, sun, and contour-line landscape: plum #342c40, chalk #faf7f2, amber #f3ba7b, rose #b86783, turquoise #75d8ce. Bricolage titles, DM Sans controls, and tabular flight instruments remain. This pass prioritizes responsive flight and quiet, readable controls over new content.


## Version 0.3.1 decisions and validation

- One scheduler now polls controls, advances the simulation, updates audio, and draws. Paused screens, results, and menu backgrounds redraw only for a change or resize. Hidden tabs schedule no loop work; the welcome scene targets about 30 FPS and stays still under reduced motion. The old app-level 50 ms frame clamp caused slow motion below 20 FPS; it now accepts the engine's existing 100 ms catch-up limit. A browser check delivering frames every 64 ms confirms that ten seconds of wall time remains about ten seconds of flight.
- Skiff position, rotation, camera, scenery time, and ghost presentation interpolate between the original 120 Hz physics steps. Gameplay state, collisions, rewards, and recorded ghosts remain authoritative and unchanged. Sixteen 100-second simulations across four modes/four seeds matched every state field against version 0.3. Separate checks cover smooth, monotonic presentation at 60/90/144/240 Hz and clean pause/resume/reset boundaries.
- The renderer reuses bounded terrain samples in a 4,096-slot cache per canvas. Checked errors stay below 0.02 logical pixels across negative positions, four regions, long courses, seed changes, and cache wrap; collision terrain stays exact. Automatic scenery detail budgets roughly two million backing pixels, with explicit Sharp and Light alternatives. These change drawing resolution, never frame rate or handling.
- Flight settings are reachable from the stage, including fullscreen and pause. Saved controls cover scenery detail, device/reduced/full motion, sounds, coaching, and ghosts. Old saves default to Automatic/Follow device without losing records or unlocks. Muted play no longer constructs an audio graph, and enabled audio suspends after fading on pause or completing the arrival chime.
- Launches and resumes reveal the stage and return keyboard focus. A paused flight focuses Keep flying for normal keyboard activation. Focus loss, hidden tabs, scrolling the game out of view, controller loss, and leaving fullscreen pause safely with a useful explanation. Swapping away from a disconnected controller cannot use the second controller's held buttons to immediately resume. Right-click no longer dives, keyboard navigation cannot accidentally boost from a focused button, M toggles sound, and Space after results retries the same course.
- Dialogs lock background scrolling, keep their close button visible, and distinguish a backdrop click from a drag beginning inside. Fullscreen labels track the actual state and notices appear within the fullscreen stage. Short landscape views use compact controls/results even outside fullscreen. Mobile instruments and touch labels are larger; burst charge is explicit; coaching and events have space above the speed readout; live digits use tabular spacing.
- Measurement: in local headless Chromium at a 1440 × 1050 viewport, three-second observation windows previously contained 95 paused, 36 menu-background, and 98 results world paints. The updated windows contain zero for all three (and still repaint on resize). Ready-scene paints dropped from 181 to 70 in the observed windows. These are local render observations, not a claim about FPS or battery life on every device.
- Verification: exact publication gate, engine checks, and 38 unit tests pass. Twelve existing gameplay scenarios, seven atlas/controller scenarios, and ten polish scenarios pass in production Chromium. Added checks cover frozen canvas counts, hidden tabs, slow render frames, audio suspension, saved visual preferences, controller handoff, right-click isolation, mobile scrolling/focus, backdrop drags, sticky dismissal, and fullscreen exit. Final visuals reviewed at desktop, 320/390 px portrait, and 844 px landscape. Artifacts remain ignored in qa/artifacts.

- 2026-09-24 (codex / gpt-6): Completed Sunwake's performance, smoothness, bug-fix, and quality-of-life pass. Preserved course physics and saves, verified the exact publishing gate, and prepared version 0.3.1 for automatic PR publishing.
18 changes: 14 additions & 4 deletions projects/sunwake-a6f3/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ Catch rising thermals, chain three rings for a light magnet, carry a sun shield
| Soar | Release | Release |
| Solar burst | Shift or ↑ | Burst button when charged |
| Pause / resume | P or Escape | Pause / resume button |
| Start / retry | Space; R while paused or after a run retries the same route | Let’s fly / replay controls |
| Start / retry | Space starts; after results Space or R retries the same course. R also restarts a paused flight. | Let’s fly / replay controls |
| Mute / unmute | M | Sound button or Flight settings |

- **Voyage:** a fresh course each run, sunlight management, personal records and challenge rewards.
- **Daily flight:** the same course for the same UTC date, with a personal daily record. No online leaderboard or account.
Expand All @@ -25,9 +26,9 @@ Catch rising thermals, chain three rings for a light magnet, carry a sun shield
- **Course links:** share the exact landscape with a versioned URL. Recipients race their own local ghosts; the link shares the course, not your saved replay.
- **Flight log:** revisit the last twelve journeys, replay any course, and compare distance, duration, score, and light collected. Historical daily courses replay as Voyage.

The flight guide explains each pickup and lets you switch coaching or ghost visibility off. Your debrief includes a height trace, perfect landings, sky chains, longest glide, top speed, and a tip for the next journey. Sixteen challenges reward progress through the game.
Flight settings are always available from the sliders button above the horizon, including in fullscreen and while paused. Choose automatic, sharp, or light scenery; follow your device’s motion preference or select reduced/full motion; toggle sounds, coaching, and your ghost. Choices are saved and never change the physics. The flight guide also explains each pickup. Your debrief includes a height trace, perfect landings, sky chains, longest glide, top speed, and a tip for the next journey. Sixteen challenges reward progress through the game.

Progress is stored on this device with `localStorage`. Existing Sunwake records, unlocks, and earned light survive the update. If browser storage is blocked, the game and ghost replay still work for the current session. Audio starts only after a gesture and can be muted. The game pauses on focus loss and follows the system’s reduced-motion preference.
Progress is stored on this device with `localStorage`. Existing Sunwake records, unlocks, and earned light survive the update. If browser storage is blocked, the game and ghost replay still work for the current session. Audio starts only after a gesture and can be muted. The game pauses on focus loss, when scrolled out of view, or when leaving fullscreen, with a short explanation. Launching or resuming reveals and focuses the game. Returning to the tab never resumes a flight without your input.

Standard controllers also work: hold the bottom face button or right trigger to dive; use the right/left face button or left trigger to burst; Menu pauses/resumes. The bottom face button starts from the welcome screen and retries from results. Releasing one input never cancels another held input. Disconnecting the active controller pauses the flight. Browser support uses the standard Gamepad mapping; a button press may be needed before the browser detects it.

Expand All @@ -46,6 +47,12 @@ Each route awards an arrival seal and two optional skill/speed seals. All seals

Three seals unlock the Seafoam wake; six unlock Wild violet; twelve unlock Kestrel and Aurora; all eighteen unlock Stardust. These rewards change appearance only. Existing version-2 course links and ghosts retain their exact physics. Expedition links use a separate `#/expedition/1/<route>` format and open the atlas, where the route's normal unlock requirements apply.

## Smoothness and battery use

Flight still uses the original deterministic 120 Hz physics. Presentation interpolates between ticks, so high-refresh screens no longer repeat skiff/camera positions. Slow render frames can catch up through 100 ms without slowing the flight clock. Recorded ghosts and rewards use the actual simulation, never the interpolated picture.

One scheduler handles controllers, simulation, sound, and drawing. Paused screens, results, and menu backgrounds only redraw when something changes; hidden tabs schedule no loop work. The welcome scene animates at about 30 FPS, or stays still with reduced motion. Automatic detail limits large canvases to roughly two million pixels; sharp and light options are explicit alternatives. Bounded, render-only terrain samples reuse nearby heights with less than 0.02 logical-pixel error in the checked courses. Audio is never constructed for muted play and suspends after a pause or the finish chime.

## Develop

```sh
Expand Down Expand Up @@ -73,9 +80,10 @@ For browser checks, install a Playwright Chromium browser (`pnpm exec playwright
```sh
SUNWAKE_URL=http://127.0.0.1:4179 node qa/browser-check.mjs
SUNWAKE_URL=http://127.0.0.1:4179 node qa/atlas-check.mjs
SUNWAKE_URL=http://127.0.0.1:4179 node qa/polish-check.mjs
```

Set `CHROMIUM_PATH` if using an existing browser executable. The suites exercise the real canvas loop and React UI, including desktop/touch/controller controls, pause/resume, once-per-run rewards, saved purchases, repeatable daily flights, shared courses, ghost accuracy, history replay, preferences, blocked clipboard/storage, expedition arrivals and unlocks, three-seal flights, dialogs, and portrait/landscape layouts. Local screenshots are ignored under `qa/artifacts/`.
Set `CHROMIUM_PATH` if using an existing browser executable. The suites exercise the real canvas loop and React UI, including desktop/touch/controller controls, pause/resume, once-per-run rewards, saved purchases, repeatable daily flights, shared courses, ghost accuracy, history replay, preferences, blocked clipboard/storage, expedition arrivals and unlocks, three-seal flights, dialogs, and portrait/landscape layouts. The polish suite also checks idle/hidden render counts, slow-frame timing, graphics/motion persistence, sticky dialog dismissal, scroll/focus restoration, controller handoff, right-click isolation, fullscreen exit, and audio suspension. Local screenshots are ignored under `qa/artifacts/`.

The exact repository publishing gate, from the repository root:

Expand All @@ -86,6 +94,8 @@ node scripts/verify-project.mjs sunwake-a6f3
## Code map

- `src/game/engine.ts`: fixed-timestep physics, terrain, seeded pickups and hazards, collision handling, and scoring.
- `src/game/presentation.ts`: bounded terrain samples and display pixel budgets.
- `src/FlightPreferences.tsx`: saved sound, guidance, graphics, and motion controls.
- `src/game/renderer.ts`: layered landscapes, skiffs, effects, region transitions, and the personal-best flag.
- `src/game/progress.ts`: validated saves, challenges, daily seeds, and cosmetic unlocks.
- `src/game/replay.ts`: versioned course links, bounded recordings, saved ghosts, and interpolation.
Expand Down
2 changes: 1 addition & 1 deletion projects/sunwake-a6f3/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "sunwake",
"private": true,
"version": "0.3.0",
"version": "0.3.1",
"type": "module",
"packageManager": "pnpm@11.5.2",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion projects/sunwake-a6f3/qa/atlas-check.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ try{
},true)

await scenario('atlas-landscape-finish',{width:844,height:390},async page=>{
await page.getByRole('button',{name:'Toggle fullscreen',exact:true}).click()
await page.getByRole('button',{name:'Enter fullscreen',exact:true}).click()
// A modal remains usable while the stage occupies fullscreen.
await page.getByRole('button',{name:'Six expeditions await'}).click()
await depart(page);await page.clock.runFor(19000)
Expand Down
2 changes: 1 addition & 1 deletion projects/sunwake-a6f3/qa/browser-check.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ try {

await scenario('landscape-fullscreen-touch-and-results', { width: 844, height: 390 }, async page => {
await page.goto(`${baseURL}/#/course/2/3j`, { waitUntil: 'networkidle' })
await page.getByRole('button', { name: 'Toggle fullscreen', exact: true }).click()
await page.getByRole('button', { name: 'Enter fullscreen', exact: true }).click()
assert.ok(await page.evaluate(() => Boolean(document.fullscreenElement)))
await fly(page)
const dive = page.getByRole('button', { name: 'Hold to dive, release to soar', exact: true })
Expand Down
Loading
Loading