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:
1 parent
52feed514d
commit
7503f55a17
5 files changed
+357
No files matched your search
@@ -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.
|
||||
@@ -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;
|
||||
}
|
||||
Reference in new issue
Block a user