feat(data): album data for all 9 sagas and 40 songs

Odyssey links point at Butcher & Lang (#1728), whose Greek names the
keywords were checked against.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Lucas Winther
2026-10-30 04:56:32 +01:00
co-authored by Claude Opus 5.5
parent 3235df8bea
commit 288425501d
8 changed files with 611 additions and 11 deletions
+11 -11
View File
@@ -63,14 +63,14 @@ export function sagaOf(song: AlbumSong): AlbumSaga;
export function songsWithMotif(motifId: string): AlbumSong[];
export function prevNextWithMotif(song: AlbumSong, motifId: string): { prev: AlbumSong | null; next: AlbumSong | null };
```
Values pinned by the spec: titles and order exactly as the content folder (Troy: The Horse and the Infant, Just a Man, Full Speed Ahead, Open Arms, Warrior of the Mind · Cyclops: Polyphemus, Survive, Remember Them, My Goodbye · Ocean: Storm, Luck Runs Out, Keep Your Friends Close, Ruthlessness · Circe: Puppeteer, Wouldn’t You Like, Done For, There Are Other Ways · Underworld: The Underworld, No Longer You, Monster · Thunder: Suffering, Different Beast, Scylla, Mutiny, Thunder Bringer · Wisdom: Legendary, Little Wolf, We'll Be Fine, Love in Paradise, God Games · Vengeance: Not Sorry for Loving You, Dangerous, Charybdis, Get in the Water, Six Hundred Strike · Ithaca: The Challenge, Hold Them Down, Odysseus, I Can't Help but Wonder, Would You Fall in Love with Me Again). Landings: `cave` = Polyphemus, Survive; `arena` = God Games; `hall` = all five Ithaca songs. Ids are kebab-case titles (`wouldnt-you-like`). Motif colours: reuse `src/data/sample.ts` colours for motifs that exist there. Odyssey `url`: `https://www.gutenberg.org/files/1727/1727-h/1727-h.htm#chap{NN}` (two digits).
Values pinned by the spec: titles and order exactly as the content folder (Troy: The Horse and the Infant, Just a Man, Full Speed Ahead, Open Arms, Warrior of the Mind · Cyclops: Polyphemus, Survive, Remember Them, My Goodbye · Ocean: Storm, Luck Runs Out, Keep Your Friends Close, Ruthlessness · Circe: Puppeteer, Wouldn’t You Like, Done For, There Are Other Ways · Underworld: The Underworld, No Longer You, Monster · Thunder: Suffering, Different Beast, Scylla, Mutiny, Thunder Bringer · Wisdom: Legendary, Little Wolf, We'll Be Fine, Love in Paradise, God Games · Vengeance: Not Sorry for Loving You, Dangerous, Charybdis, Get in the Water, Six Hundred Strike · Ithaca: The Challenge, Hold Them Down, Odysseus, I Can't Help but Wonder, Would You Fall in Love with Me Again). Landings: `cave` = Polyphemus, Survive; `arena` = God Games; `hall` = all five Ithaca songs. Ids are kebab-case titles (`wouldnt-you-like`). Motif colours: reuse `src/data/sample.ts` colours for motifs that exist there. Odyssey `url`: `https://www.gutenberg.org/cache/epub/1728/pg1728-images.html#chap{NN}` (Butcher & Lang) (two digits).
Content guidance: use what is well established about EPIC (motifs such as Warrior of the Mind, Open Arms, Just a Man/My Goodbye, Ruthlessness, Monster, Little Wolf, Love in Paradise/Penelope's theme, Thunder Bringer, character themes for Odysseus, Athena, Poseidon, Zeus, Penelope, Telemachus, Polyphemus, Circe, Hermes…). The `.lrc` files in `/home/user/epic-the-musical-content` may be *read* to check where phrases recur, but no lyric text may be written anywhere. Mark every uncertain value `// TODO(verify)`.
- [ ] **Step 1: Write failing tests** in `tests/album.test.ts`: 9 sagas ordered troy…ithaca; `songs.length === 40`; tracks 1..40 contiguous; `songs.map(s=>s.title)` equals the pinned list; each saga's `sagaTrack` 1..n; every `motifIds`/part motif exists in `motifs` and every motif is used ≥2 songs; `storyTime.yearsAfterTroy` non-decreasing by track; landing mapping as pinned; `fanWorks` is `[]`; `prevNextWithMotif` on a motif's middle occurrence returns both neighbours; if `/home/user/epic-the-musical-content/Songs and Lyrics` exists, `scanLibrary` titles (normalised: lowercase, strip non-alphanumerics) equal the album titles in order.
- [ ] **Step 2:** `bun test tests/album.test.ts` → FAIL (modules missing).
- [ ] **Step 3:** Implement the files above.
- [ ] **Step 4:** `bun test` and `bun run typecheck` → all pass.
- [ ] **Step 5:** Commit `feat(data): album data for all 9 sagas and 40 songs`.
- [x] **Step 1: Write failing tests** in `tests/album.test.ts`: 9 sagas ordered troy…ithaca; `songs.length === 40`; tracks 1..40 contiguous; `songs.map(s=>s.title)` equals the pinned list; each saga's `sagaTrack` 1..n; every `motifIds`/part motif exists in `motifs` and every motif is used ≥2 songs; `storyTime.yearsAfterTroy` non-decreasing by track; landing mapping as pinned; `fanWorks` is `[]`; `prevNextWithMotif` on a motif's middle occurrence returns both neighbours; if `/home/user/epic-the-musical-content/Songs and Lyrics` exists, `scanLibrary` titles (normalised: lowercase, strip non-alphanumerics) equal the album titles in order.
- [x] **Step 2:** `bun test tests/album.test.ts` → FAIL (modules missing).
- [x] **Step 3:** Implement the files above.
- [x] **Step 4:** `bun test` and `bun run typecheck` → all pass.
- [x] **Step 5:** Commit `feat(data): album data for all 9 sagas and 40 songs`.
### Task 2: App shell, routes and URL state
@@ -93,11 +93,11 @@ export function createStore(initial: AppState): { get(): AppState; set(p: Partia
```
`index.html` has `<main id="voyage-app">` with regions `#stage` (canvas + frieze layer + caption), `#panel`, `#timeline`, and the credits footer.
- [ ] **Step 1:** Failing tests: `parseState("?song=survive&tab=listen&motif=polyphemus&timeline=closed&ribbons=1&read=1")` round-trips via `stateToSearch`; `parseState("?song=nope&tab=x")` equals `DEFAULT_STATE`; `stateToSearch(DEFAULT_STATE) === ""`; server tests as above.
- [ ] **Step 2:** Run → FAIL.
- [ ] **Step 3:** Implement; `main.ts` boots the store, mounts credits, renders a placeholder stage showing the current song title (replaced in Task 7).
- [ ] **Step 4:** `bun test`, `bun run typecheck`, `bun run build` pass; `bun run dev` → `/` and `/mockups/` load.
- [ ] **Step 5:** Commit `feat(app): app shell at /, gallery at /mockups/, URL state`.
- [x] **Step 1:** Failing tests: `parseState("?song=survive&tab=listen&motif=polyphemus&timeline=closed&ribbons=1&read=1")` round-trips via `stateToSearch`; `parseState("?song=nope&tab=x")` equals `DEFAULT_STATE`; `stateToSearch(DEFAULT_STATE) === ""`; server tests as above.
- [x] **Step 2:** Run → FAIL.
- [x] **Step 3:** Implement; `main.ts` boots the store, mounts credits, renders a placeholder stage showing the current song title (replaced in Task 7).
- [x] **Step 4:** `bun test`, `bun run typecheck`, `bun run build` pass; `bun run dev` → `/` and `/mockups/` load.
- [x] **Step 5:** Commit `feat(app): app shell at /, gallery at /mockups/, URL state`.
### Task 3: Content client