wip(mockups): pause sea-chart round — saved spike plans and 08 geo module

Agents paused at the usage limit. Resume notes live in
docs/design/002-direction-round-notes.md and docs/design/002-sea-chart-spikes/.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Lucas WintherandClaude Opus 5.5 committed 2026-10-23 19:30:13 +02:00
1 parent 52feed514d
commit 7503f55a17
5 files changed
+357

No files matched your search

+25
View File
@@ -91,6 +91,31 @@ jumps there.
This file exists because of that request. It gets updated after every user message in this round.
### 7. Paused at the usage limit
> "we have almost reached usage limit. Make the agents save their work for now, so we can continue
> without losing progress."
**State when paused (branch `feat/sea-chart-round`, not pushed):**
| Spike | State |
|---|---|
| 08 | Only `src/mockups/08-chart-logline/geo.ts` is written: chart geography, route and log-line maths. Nothing imports it yet, and it has not been typechecked. `main.ts` and `style.css` are still stubs. |
| 09 | No code yet; the stub is unchanged. The design plan and resume checklist are written. |
| 10 | No code yet; the stub is unchanged. The design plan and resume checklist are written. |
| 11 | Not started. It is still a stub. Idea: an overview chart of the whole sea; clicking an island opens a zoomed inset chart with that saga's songs as ports. |
- **Brief and per-spike plans:** `docs/design/002-sea-chart-spikes/`. It holds `round2-brief.md` and one `<slug>-report.md` per spike, each with a design plan, a what's-left checklist and the next step. Agents should keep writing reports to `.superpowers/brainstorm/`, and the controller copies them into this folder.
- **Preview:** http://10.20.20.2:8099/ still runs the `main` build (01–07). It does not show 08–11 yet.
**To resume:**
1. Read this file and the brief.
2. Dispatch one agent per spike, at most 3 at a time. Point each at its report file and the brief, and tell it to continue from the checklist.
3. Build 11.
4. Rebuild the preview container from this branch:
`docker build -t epic-timeline . && docker rm -f epic-timeline-preview && docker run -d --restart unless-stopped -e PORT=3000 -p 10.20.20.2:8099:3000 --name epic-timeline-preview epic-timeline`
5. Ask the user which chart approach reads best.
## Open questions (in priority order)
1. Which sea-chart approach (08–11) reads best, or which mix?
@@ -0,0 +1,72 @@
# 08 Sea chart + log line: status report (paused at usage limit)
## What's done
- Read the round-2 brief, AGENTS.md, DESIGN.md, the round notes, the shared contract, and mockup 01's ink approach. Loaded the `frontend-design` skill.
- Design plan (not built yet):
- **Palette:** Admiralty-chart look. Water `#dfe9e6`, buff land `#e8d8a8`, navy ink `#1e2c3b`, chart magenta `#a3195b` for the course, ship and selection, and paper `#fbfbf7` for the log.
- **Motif colours:** darkened with `color-mix` against the ink, with a thin ink casing so the pale Odysseus colour stays readable.
- **Type:** Cormorant Garamond for titles and place names, with sea areas in italic. Inter for small numbers.
- **Layout at 1440×900:**
- 56px header;
- the chart fills the space below it (world 1600×600);
- a 268px log line with a sticky 200px motif legend on the left;
- the credits footer.
- **Detail panel:** 380px, slides over the chart's right edge.
- **`src/mockups/08-chart-logline/geo.ts` is written and complete (not yet compiled or typechecked).** It contains:
- the chart world size;
- the coastlines (Troy, Cicones coast, the Cyclops' island, Goat Island, a rock), drawn as closed Catmull-Rom paths;
- port positions, label offsets and waypoints per song;
- `buildRoute()`, which builds one smooth course split into port-to-port legs, with the years crossed on each leg;
- log line x positions: `LOG.hop` 156 per song plus `LOG.year` 300 per in-story year, giving a total width `LOG_W` of about 2550px;
- `xToIndex()`, which maps a log x position to a fractional song index for scrubbing;
- `yearsLabel()`, and the `rng()` helper for depth soundings.
- `index.html` is untouched (the stub is fine). `main.ts` and `style.css` are still the stubs.
## What's left
- [ ] `main.ts` chart SVG:
- land with a hachured coast (stroke the same path at widths 30, 18 and 8 under the land fill);
- graticule, compass rose, soundings;
- saga sea-area names in italic, plus the place names Troy, Island of the Cyclopes and Land of the Cicones;
- the route: dashed in full, plus a solid magenta overlay up to the ship, using `stroke-dasharray` from cumulative leg lengths measured by `getTotalLength` on legs appended to the SVG;
- ports as circles with the track number, title labels from `ports[id].label`;
- "1 year at sea" and "2 years at sea" labels on the crossing legs.
- [ ] Ship marker at a fractional index: `getPointAtLength` on the leg, rotated to the tangent.
- [ ] View sync:
- animated `viewBox` `{cx, cy, z}`;
- while scrubbing the log, z≈1.5 centred on the ship;
- after leaving the log, return to the full view after 600ms;
- when a song is selected, focus the port at x≈36% so the panel doesn't cover it.
- [ ] Log line:
- saga brackets and a year ruler;
- the time axis, with wavy "N years at sea" gap labels;
- song buttons (track, title, story label);
- 7 motif lanes (16px apart) with a casing and a line from the first to the last occurrence;
- per-song part boxes, filled where the motif plays, so motifs read at part level;
- a scrub cursor with a year readout;
- a sticky legend of motif buttons.
- [ ] Clicking a chart port selects the song and smooth-scrolls the log line to its x. Clicking a log entry selects the song and focuses the chart on it.
- [ ] Detail panel:
- title, saga and track, story time, Odyssey link;
- parts with singers and motif chips, with the selected motif highlighted;
- `createAudioSlot`, `createVideoCards`, a close button.
- [ ] Motif selection:
- colour the route legs where both songs carry the motif, and dot them where it skips;
- ring the ports that carry it;
- fade the other log lanes.
- [ ] URL state (`?song`, `?motif`, `?overlay=1`) via `replaceState`.
- [ ] `?overlay=1`:
- dark sea gradient backdrop, with the app as a centred ~1180×740 panel;
- title bar reading "You are here: Survive", with the ship fixed at Survive;
- clicking a port opens a popover with a "Sail here" button that animates the ship there;
- the scrub shows a probe ring instead of moving the ship;
- the credits footer stays visible: fixed, light text on dark.
- [ ] `style.css` (unique). It needs the `@fontsource` imports: `cormorant-garamond/latin-{400,500,600,400-italic,500-italic}.css` and `inter/{400,500}.css`.
- [ ] Build to `<scratchpad>/r2-08-chart-logline`, serve on port 4108, take the 4 screenshots, and look at them. Use `?overlay=1&song=my-goodbye` for the overlay shot so the popover shows.
- [ ] Run `bun test` and `bun run typecheck`, finalise this report, and reply in the brief's format.
## Known broken bits
- None at runtime. The page still renders the stub text, because `geo.ts` is not imported anywhere yet.
- The port label offsets in `geo.ts` are first guesses and need tuning against a screenshot.
## Next step to resume
Write `src/mockups/08-chart-logline/main.ts` against `geo.ts`, following the "What's left" list in order, then `style.css`, then build and screenshot.
@@ -0,0 +1,102 @@
# 09 — Schematic sea chart (`09-chart-transit`): interrupted
Status: **stopped before any code was written** (the coordinator called a halt for the usage limit).
`src/mockups/09-chart-transit/` is still the untouched stub. No files were left half-edited.
## What's done
- I read the brief (`round2-brief.md`), `AGENTS.md`, `docs/design/002-direction-round-notes.md`, the shared contract (`src/data/*.ts`, `src/shared/*.ts`), the layout code for mockups 03 (`map.ts`) and 07 (`route.ts`), and the screenshots for 01, 03 and 07.
- I loaded the `frontend-design` skill and settled the design plan below. It is not built yet.
## Design plan (settled, ready to build)
**Look:** this is an Admiralty-chart palette, not cream parchment.
- Deep water `#f4f7f5` is the background.
- Shoal bands `#d3e5ea`.
- Buff land `#ecdca4`.
- Navy chart ink `#1f3346`.
- Chart magenta `#a8326e` marks the ship's course and "you are here". This is the one bold element.
- Fonts are Cormorant Garamond and Inter, both via `@fontsource`:
- Cormorant: upright for land and saga names, italic for sea areas and in-story time.
- Inter: port labels and UI.
**Schematic layout** (to go in `layout.ts`):
- The x-axis is story time:
- hop 92 between songs;
- +110 per year;
- +50 when the saga changes;
- X0 = 110.
- The rows are octilinear:
- Troy row at y≈290;
- a 45° diagonal down to the Cyclops row at y≈410;
- a long diagonal down to Storm at y≈590.
- The diagonals sit only in the gaps between ports, so every port is on a horizontal.
- Labels are horizontal, centred above each port, and wrap to two lines (max ~88px).
- The `storyTime.label` goes in italic below the lanes, with a year tag wherever the year changes.
- Gaps of a year or more carry an italic note on the course, such as "a year passes".
**Motif lines:**
- Each motif gets a fixed lane parallel to the course: character themes on the left (north) side, musical motifs on the right (south) side, spaced 10 apart.
- A lane is drawn only over runs of consecutive songs that contain that motif. A single-song run gets a ±30 stub.
- Between runs, a faint dotted lane shows the motif returning.
- Lanes are built as offset polylines with miter joins, plus `roundedPath` (re-implement it locally; don't import from 03).
- Pale data colours are darkened toward a warm dark until they reach about 2.6:1 contrast on the paper. Each line has a paper-coloured casing.
- At each port, a capsule perpendicular to the course spans the lanes present. A port with 2 or more motifs is an interchange and gets a heavier border.
**Chart layer:**
- A compass rose in the empty top-right, around (1210, 190), with faint rhumb lines radiating across the chart.
- Schematic islands: soft blobs drawn as closed Catmull-Rom curves.
- A Troy blob under the Troy row.
- A Cyclops blob under the Cyclops row.
- A storm cell (dashed rings) at Storm.
- Dashed depth contours, drawn as scaled copies of each blob.
**Morph slider (schematic ↔ true geography):**
- Sample the course densely by schematic parameter `u`.
- The geo course is a polyline through the songs' lon/lat in an equirectangular projection (lon 11–28.5, lat 35–42, x scaled by cos 38.5°).
- Point = lerp(schematic(u), geo(u), t). Recompute the lanes and ports for every `t`.
- Land crossfades between the schematic blobs and rough Mediterranean polygons: Italy, Sicily, Greece with the Peloponnese, Asia Minor, Crete and Ithaca. I drafted the coordinates in my head; re-derive them.
- Geo positions:
- songs 1–4 near Troy/Ismarus (26.2,40.0 → 24.6,40.3);
- Warrior of the Mind off Cape Malea (22.0,36.0), with via-points so the route avoids Attica;
- the Cyclops songs on Sicily (~15,37.7);
- Storm NW of Sicily (13.0,38.9).
- As `t` rises, the time labels fade out and port labels shrink to track numbers, which shows the trade-off.
- Support `?morph=0..1`.
**Page:**
- Title cartouche at top-left, morph control at top-centre, motif key at bottom-left.
- The detail drawer is about 400px on the right and pans the chart when the port would be hidden.
- Contents: title, story time, Odyssey link, parts with singers, motif chips, `createAudioSlot`, `createVideoCards`.
- Selecting a motif fades the other lanes and rings the ports where it plays.
- The footer is styled to be always visible.
- URL params: `?song=`, `?motif=`, `?overlay=1`.
**Overlay (`?overlay=1`):**
- A dimmed CSS-gradient sea backdrop.
- The chart as a compact card, about 1180×650: story-time labels hidden, key as compact chips, slider hidden.
- A magenta ship marker reading "You are here: Survive".
- Clicking a port opens a popover with "Sail here", which animates the ship along `u`.
## What's left
- [ ] Write `layout.ts`: schematic trunk, port positions, geo coordinates and polygons, morph sampling, offset lanes, `roundedPath`, blobs.
- [ ] Write `main.ts`: SVG render (static chart layer plus a dynamic layer rebuilt on morph), key, motif select, detail drawer, URL params, overlay mode with Sail here, `mountCredits()`.
- [ ] Write `style.css`. It must stay unique, not byte-identical to any other mockup's.
- [ ] Update the `index.html` title if needed. Keep `<footer data-fan-credits>` and the `credits.css` link.
- [ ] Build to `/tmp/claude-1000/-home-user-epic-the-musical-timeline/fb650f6d-47a0-47e4-8f6b-197d02417f3b/scratchpad/r2-09-chart-transit` and serve on port 4109.
- [ ] Take screenshots: `screenshots/09-chart-transit-{default,motif,song,overlay}.png`, plus optionally `-geo.png`. Review them.
- [ ] Run `bun test` and `bun run typecheck`.
- [ ] Rewrite this file as the final report: the idea, what works, what doesn't, and the screenshot paths.
## Known broken bits
- None in the code, since nothing has been written. The stub still renders "Schematic sea chart (stub)".
- Expected risks:
- Port labels may be cramped at hop 92.
- Lanes may self-intersect in geo mode where ports cluster. That is acceptable and shows the trade-off.
- The pale Odysseus colour may sit close to Warrior gold once darkened.
## Next step to resume
Start with `src/mockups/09-chart-transit/layout.ts`, built from the plan above. Then write `main.ts` and `style.css`, then build, take the screenshots and run the checks.
@@ -0,0 +1,55 @@
# Round 2 brief: sea-chart spikes (08–11)
## The question these spikes answer
**How can a sea chart be as easy to navigate and read as mockup 01 (editorial scroll) and mockup 03 (transit map)?**
The user's own words:
> "Yeah i did think sea chart, im just not sure how we have it easy to navigate and look at as with the editorial scroll and transit map."
Read `docs/design/002-direction-round-notes.md` for the full context. In short, the planned app has two layers:
- A 2D sea chart that you can browse on its own as a simple display.
- A 3D sea voyage (see mockup 07) with landings, where the chart is reused as an in-3D map overlay. The Greek frieze (mockup 04) acts as the transition between them.
Before designing, look at mockups 01, 03, 04 and 07: their code is in `src/mockups/`, and their screenshots are in `screenshots/01-*`, `03-*`, `04-*` and `07-*`. Your chart should borrow their best ideas:
- From 01: legible editorial typography, time-proportional spacing, and the motif-thread idea.
- From 03: crisp motif lines, interchanges, and the legend.
- From 07: the geography of the route and its islands.
## What every spike must show
- **The chart itself.** Use stylised geography: islands for the sagas (Troy, the Cyclops' island, and the open sea and storm) and the ship's route. Each song is a port or waypoint on the route.
- **Readable at a glance.** Song titles, saga names and the passage of in-story time must be easy to see without hunting. This is the whole point of the round.
- **Motifs as coloured routes or lines.** Selecting a motif highlights every song and part where it plays.
- **Opening a song.** Use a simple detail panel with the title, story time, Odyssey link, parts with singers, motif chips, and the shared `createAudioSlot` and `createVideoCards`. Keep it lighter than in 01–07, because the navigation is what's being tested here.
- **The in-3D overlay state.** At `?overlay=1`, show the same chart as a compact overlay sitting on a dimmed backdrop. A static image or a CSS gradient standing in for the 3D sea is fine. Mark the ship's current position ("You are here: Survive"), and make clicking a port show a "Sail here" affordance. This shows how one chart serves both uses.
- **Credits.** The fan notice and the credit to Jorge Rivera-Herrans must be fully visible in every state, via `mountCredits()`, which the stub already calls.
## Rules
- **This is a throwaway spike.** It doesn't need to be bulletproof and no unit tests are needed. Desktop 1440×900 only.
- **Edit ONLY `src/mockups/<your-slug>/`.** Never touch shared files, and add no new dependencies.
- **Fonts.** Use the bundled `@fontsource/cormorant-garamond` and `@fontsource/inter`, or system stacks. No CDN.
- **No three.js.** These are 2D: SVG, Canvas 2D, or plain DOM.
- **Unique CSS.** Your `style.css` must not be byte-identical to any other mockup's.
- **Build.** Build into your own outdir, never `dist/`:
`bun build src/server.ts --target=bun --production --outdir /tmp/claude-1000/-home-user-epic-the-musical-timeline/fb650f6d-47a0-47e4-8f6b-197d02417f3b/scratchpad/r2-<slug>`
Then serve it with `cd <outdir> && PORT=<port> bun server.js`. Ports: 08→4108, 09→4109, 10→4110, 11→4111. Kill only your own server; don't use `pkill` patterns.
- **Screenshots.**
- Command: `google-chrome --headless=new --disable-gpu --hide-scrollbars --window-size=1440,900 --screenshot=<file> <url>`
- Support `?song=<id>`, `?motif=<id>` and `?overlay=1` so the states can be screenshotted.
- Save `screenshots/<slug>-default.png`, `-motif.png`, `-song.png` and `-overlay.png`.
- Look at them before reporting.
- **Checks before reporting.** `bun test` and `bun run typecheck` must pass.
- **Committing.** Don't commit; the controller commits.
- **Subagents.** Never spawn subagents.
## Report
- Write a short report to `.superpowers/brainstorm/<slug>-report.md` covering:
- what navigation idea you tried;
- what works;
- what doesn't;
- your screenshot paths.
- Then reply with only: status, one line on the idea, screenshot paths, and concerns.
+103
View File
@@ -0,0 +1,103 @@
import { songs } from "../../data/sample";
import { gapYears } from "../../data/query";
/** Chart world in SVG units. Geography is stylised; nothing is to scale. */
export const W = 1600;
export const H = 600;
export type Pt = readonly [number, number];
/** Closed Catmull-Rom outline through the points, for coastlines. */
export function closedSmooth(pts: Pt[]): string {
const n = pts.length;
let d = `M${pts[0]![0]},${pts[0]![1]}`;
for (let i = 0; i < n; i++) {
const p0 = pts[(i - 1 + n) % n]!, p1 = pts[i]!, p2 = pts[(i + 1) % n]!, p3 = pts[(i + 2) % n]!;
d += cubic(p0, p1, p2, p3);
}
return d + "Z";
}
function cubic(p0: Pt, p1: Pt, p2: Pt, p3: Pt): string {
const t = 1 / 6;
const c1: Pt = [p1[0] + (p2[0] - p0[0]) * t, p1[1] + (p2[1] - p0[1]) * t];
const c2: Pt = [p2[0] - (p3[0] - p1[0]) * t, p2[1] - (p3[1] - p1[1]) * t];
return ` C${c1[0].toFixed(1)},${c1[1].toFixed(1)} ${c2[0].toFixed(1)},${c2[1].toFixed(1)} ${p2[0]},${p2[1]}`;
}
export const land = {
troy: closedSmooth([[-40, -40], [170, -30], [186, 60], [160, 130], [205, 192], [232, 258], [214, 322], [246, 384], [222, 452], [178, 520], [196, 600], [120, 660], [-40, 660]]),
cicones: closedSmooth([[300, -40], [660, -40], [622, 38], [562, 78], [484, 92], [420, 70], [362, 84], [312, 40]]),
cyclops: closedSmooth([[800, 300], [842, 256], [905, 246], [972, 254], [1020, 298], [1030, 360], [990, 414], [920, 430], [852, 416], [806, 372]]),
goat: closedSmooth([[1078, 168], [1104, 160], [1122, 176], [1110, 196], [1084, 194]]),
rock: closedSmooth([[1508, 470], [1532, 462], [1546, 480], [1526, 494]]),
};
export interface PortGeo { at: Pt; label: [number, number, "start" | "middle" | "end"]; via?: Pt[] }
/** Port positions per song id, label offset, and waypoints sailed *before* arriving. */
export const ports: Record<string, PortGeo> = {
"the-horse-and-the-infant": { at: [258, 296], label: [18, 5, "start"] },
"just-a-man": { at: [300, 412], label: [18, 5, "start"] },
"full-speed-ahead": { at: [420, 134], label: [0, 34, "middle"], via: [[340, 270]] },
"open-arms": { at: [548, 142], label: [18, 5, "start"] },
"warrior-of-the-mind": { at: [652, 302], label: [0, 34, "middle"], via: [[612, 214]] },
polyphemus: { at: [768, 262], label: [-16, -14, "end"] },
survive: { at: [905, 220], label: [0, -20, "middle"], via: [[830, 222]] },
"remember-them": { at: [1052, 290], label: [18, 5, "start"], via: [[985, 226]] },
"my-goodbye": { at: [930, 458], label: [0, 34, "middle"], via: [[1068, 384], [1012, 452]] },
storm: { at: [1400, 320], label: [0, 44, "middle"], via: [[1100, 476], [1262, 408]] },
};
export interface RouteSeg { from: number; to: number; d: string; years: number }
/** One smooth course through every port and waypoint, split into port-to-port legs. */
export function buildRoute(): RouteSeg[] {
const pts: { p: Pt; port: number }[] = [];
songs.forEach((s, i) => {
const g = ports[s.id]!;
for (const v of g.via ?? []) pts.push({ p: v, port: -1 });
pts.push({ p: g.at, port: i });
});
const segs: RouteSeg[] = [];
let cur = "";
let from = 0;
for (let k = 0; k < pts.length - 1; k++) {
const p0 = pts[Math.max(0, k - 1)]!.p, p1 = pts[k]!.p, p2 = pts[k + 1]!.p, p3 = pts[Math.min(pts.length - 1, k + 2)]!.p;
if (!cur) cur = `M${p1[0]},${p1[1]}`;
cur += cubic(p0, p1, p2, p3);
const next = pts[k + 1]!;
if (next.port >= 0) {
segs.push({ from, to: next.port, d: cur, years: gapYears(songs[from]!, songs[next.port]!) });
from = next.port;
cur = "";
}
}
return segs;
}
/** Log line x positions: a short hop between songs, plus a fixed width per in-story year. */
export const LOG = { start: 70, hop: 156, year: 300, end: 260 };
export const logX: number[] = [];
songs.forEach((s, i) => {
if (i === 0) logX.push(LOG.start);
else logX.push(logX[i - 1]! + LOG.hop + Math.max(0, gapYears(songs[i - 1]!, s)) * LOG.year);
});
export const LOG_W = logX[logX.length - 1]! + LOG.end;
/** Log x → fractional song index (piecewise linear between song columns). */
export function xToIndex(x: number): number {
if (x <= logX[0]!) return 0;
for (let i = 0; i < logX.length - 1; i++) {
const a = logX[i]!, b = logX[i + 1]!;
if (x <= b) return i + (x - a) / (b - a);
}
return logX.length - 1;
}
export const yearsLabel = (y: number) => `${y} ${y === 1 ? "year" : "years"} at sea`;
/** Small deterministic RNG for the depth soundings. */
export function rng(seed: number) {
return () => ((seed = (seed * 16807) % 2147483647) - 1) / 2147483646;
}