| name | book-to-map |
| description | Ingest a Wheel of Time epub from books/ and turn it into map content: verified chapter lists, per-chapter character routes, and spoiler-stamped place lore. Use when a new book epub lands in books/, or when existing drafted content for a book needs verifying against the real text.
|
book-to-map
Turn an epub in books/ into map data for this repo: chapter titles
(data/chapters/), character routes (data/routes/), and per-chapter place
lore (content/places/) — all stamped so the spoiler guard reveals nothing
beyond the reader's progress.
Ground rules
- Paraphrase, never quote. The site may go public; lore blurbs must be
original prose in the repo's existing tone (see
content/places/*.md).
Never copy sentences from the book. Chapter titles and place names are fine.
- Book text stays local. Extraction output goes to
books/.extracted/,
which must be listed in .gitignore (as should books/ itself).
- The book is the authority. Where existing data (drafted from memory)
disagrees with the text, the text wins. Keep existing pixel coordinates
unless the book proves a stop wrong — coordinates come from the map art via
?editor, not from the book.
- Spoiler stamping is the core invariant. A fact first revealed in book B,
chapter C is visible only from [B, C] onward:
- Base prose of a place entry may contain only what a reader knows the
first time the place can appear on their map — geography, and common
knowledge an inhabitant of the world would have.
- Anything learned in a specific chapter goes inside
{{< spoiler book=B ch=C >}}…{{< /spoiler >}} stamped with that chapter.
- A place whose existence is itself a surprise gets
revealed: [B, C] in
front matter (omit for well-known geography).
- Events from later chapters must never leak into earlier-stamped prose —
when writing about chapter C, use nothing you learned after C.
Step 1 — Extract the epub
python3 .claude/skills/book-to-map/extract_epub.py books/<file>.epub
Writes books/.extracted/<epub-stem>/ containing:
NNN-<slug>.txt — one plain-text file per spine document, in reading order,
with the detected heading on line 1
index.txt — file list with headings and word counts, for mapping files to
chapters
Check index.txt: front matter (title page, maps, glossary) is obvious from
word counts and headings. Note which file is the prologue and which files are
chapters 1..N. Some epubs split or merge chapters across files — trust
headings over file boundaries.
Step 2 — Chapter list
Verify or create data/chapters/<book-slug>.yaml (slugs: eotw, tgh,
tdr, tsr, tfoh, loc, acos, top, wh, cot, kod, tgs, tom,
amol). Index 0 is the prologue; chapter numbers are list indices. Titles
must match the book exactly.
Step 3 — Read for movements and places
Read every chapter and record, per chapter:
- Where each tracked character is (see
data/characters.yaml; add new
major POV characters there — muted ink-tone hex, matching the existing set).
Note arrivals, departures, travel mode (road / river / ways / horseback
cross-country), and named waypoints passed.
- Places — any named settlement or landmark that is visited or
meaningfully described. For each: what this chapter reveals about it
(events, first sightings, lore told by characters).
Chapters are independent — fan this out over parallel subagent readers, each
taking a contiguous range of extracted files and returning structured notes
(chapter → characters → movements; chapter → places → revelations). Give each
reader the character roster and place-id list so names come back normalized.
Step 4 — Routes
Write data/routes/<book-slug>/<character>.yaml (format documented in
README.md). Rules that matter:
-
A leg { c: N, at: place } means arrives during chapter N. Use x:/y:
raw pixels for unnamed stops; via: to bend a line along a road or river.
-
Route lines must follow the painted geography. When the text says the
characters travel a road or river, check data/paths/ for it and put
follow: <path-id> on the leg — the engine slices the path between the
previous stop and this one. If the path isn't traced yet, trace it ONCE
into data/paths/<id>.yaml (a point every ~80–150 px, more on tight
bends) from the painted art, reading coordinates with the grid helper:
sh .claude/skills/book-to-map/gridcrop.sh supermap.webp <x> <y> <w> <h> [scale]
Trace at scale 2 in segments of ~500 px: overview-zoom traces look right
but drift 15–40 px off the painted line, which shows at street zoom. A
rough trace within ~25 px is enough — then snap it to the actual painted
line (eyeballing alone does NOT converge; always finish with the snapper):
python3 .claude/skills/book-to-map/snappath.py supermap.webp data/paths/<id>.yaml --write
It samples the art perpendicular to the trace and centers each point on
the road's light band between its dark ink edges (type: road) or on the
river's dark channel (type: river). Re-check the result with the Debug
overlay at high zoom. Individual stray points (or any place/leg anchor)
can be fixed by hand: run python3 scripts/edit-server.py beside
hugo server and drag the debug handles — drops save to the source files.
via: is only for one-off geometry (a single cross-country dogleg, riding
a river bank rather than the water). Only genuine cross-country travel
may draw straight. Check which side of a river the party is on — don't run
an overland route through the water. Verify with the site's Debug
checkbox (or ?debug), which overlays paths, place anchors, hit areas,
and leg anchors.
-
The artist painted story vignettes — scenes from the books drawn in
place (e.g. the Tuatha'an camp in the southern Caralain Grass). Before
placing an off-road stop, scan the art nearby: if a vignette depicts that
event, anchor the leg on the vignette instead of estimating from the text.
-
Add a stay leg (same position, later chapter) before every departure,
or the marker drifts along the next line early. A long stop = arrival leg +
stay leg on its last chapter.
Step 5 — Places
For each place in the Step 3 notes, update or create content/places/<id>.md
(front matter format in README.md):
- New places need
x:/y: from the map art: find the painted icon with
gridcrop.sh (see Step 4) or ?editor mode. Anchor on the icon, not the
label — labels usually sit beside their towns, and a place anchored on its
label makes routes look like they bypass it. Verify existing places' anchors
the same way when a route passes through them. If the art has no icon,
estimate from neighbors and add a # TODO: verify coords in ?editor
comment in the front matter.
- Base prose: 1–3 spoiler-free sentences (see Ground rules).
- Spoiler blocks describe the place, not the storyline. A chapter earns a
{{< spoiler book=B ch=C >}} block only if it changes what the place is
to a reader — its history, its secrets, its state (occupied, burned,
freed). Plot beats that merely happen there (arrivals, fights, thefts,
reunions) are the routes' job; leave them out. Merge thin chapters into
the next milestone block, stamped with the LATEST chapter it draws from.
Even the site of a book's climax stays at roughly 3–4 short blocks. Write
in-world, present tense, matching existing entries; order chronologically.
- Places first named mid-story but famous in-world (e.g. capitals) need no
revealed: stamp; places that are secrets or surprises do.
Step 6 — Verify
hugo build (or hugo) must succeed with no warnings about the changed
files. Do not use port 1414 to check a dev server — another Hugo project
squats there; use e.g. 1717 and verify by <title>.
- Spot-check spoiler integrity: pick two or three spoiler blocks and confirm
their stamps match the chapter that actually reveals the fact.
- Report: chapters verified/corrected, routes changed (per character, with
chapter numbers), places touched, and anything the book left ambiguous for
a human pass (coordinates to set in
?editor, uncertain arrivals).