Skip to content
Blog
TechnicalAugust 30, 2026

The map that tells

EREME's voyage map fits in an 86-pixel strip. On hover, a crossed node says what happened there; an upcoming node says you know nothing about it. All of it without a single new piece of state: the journal already carried everything, it was missing one line.

An 86-pixel strip

EREME's voyage screen is a column: the status banner on top, the chronicle in the middle, the decision card at the bottom. Between the banner and the rest lives the living map: an 86-pixel-high strip, a horizontal spine with equidistant nodes, the path drawn solid behind the ship, dotted ahead. It replaces a text module that held the route in one line of glyphs; the strip is literally the drawn version of that line.

The nodes carry glyphs typed by structure, never by content: a square for a checkpoint, a triangle for a danger, a diamond for a capsule. And only the two ends of the route and the major places already crossed get named. The rest stays quiet, for a reason that is more architecture than style.

The map never reads the engine

The map widget does not touch the engine: the session contract publishes a route to it, node by node. And the publication rule is what makes the screen sincere: a node not yet reached does not publish empty fields, it does not publish the fields at all. What the surface does not receive, it cannot leak. The route reveals itself as you advance because the data itself reveals itself as you advance.

Three placements, judged on evidence

Where to put this map? Three candidates: a fourth module in the banner, a full-width strip under the banner, or no permanent presence at all. I settled the question with mockups, rendered by the real widget in the real screen. The verdict came with numbers: in the 300 by 60 module, the full map is unreadable, names overlapping, the detour arc out of frame. "The mockup did its job: it priced a cost the description did not announce."

The strip wins, and the text module that displayed the route disappears in the same commit: "keeping both would show the same route in two places, one of them too narrow for its own names."

An animation that cannot lie

The engine has no intermediate position: advancing means going from one whole node to the next. The ship's glide is pure presentation: a single field added to the drawing, a fractional progress, with a sentinel value that means "no animation" and gives back exactly the old drawing. The view eases that progress over 0.9 seconds, decelerating: "a ship that arrives slows down, and a linear motion reads like a loading bar." I judged the animation loops frame by frame, rendered by the real widget, not by a mockup: "What is being judged here is motion. A still capture says nothing about it, and an HTML mockup would lie twice, about the drawing and about the motion." The path is cut at the animated position, solid behind, dotted ahead: the unveiling of the route is seen happening instead of being noticed between two screens.

Hover: what happened there, or what you don't know

Hover over a crossed node: the place's name if it has one, the day, and the decision taken there. A node crossed without an event answers "nothing happened there": "that is a piece of information, not a hole." An upcoming node answers "you don't know yet what is there". And hovering works even on nodes the map does not draw yet: ignorance is displayable, not just information.

The commit that ships this hover says it outright: "No new state, and that is the point. The journal already carried everything: a day, an event, a choice. It was missing which node." One line added to the journal, and the tooltip reads from it. The journal is already the truth and already serialized: nothing can diverge on reload. Even the decision's label is not stored: the journal keeps identifiers, the text is resolved at read time.

The dead data

Every place in the game carries a one-line definition, required by data validation on all ten places. It was displayed nowhere. The commit that plugs it into the hover names the exact danger: "A mandatory piece of data that nobody reads ends up lying, since nothing contradicts it: you can write anything into it for months without a single screen protesting."

The line now shows as the hover's second row, and only when the place is named: otherwise the restraint on names would just be set dressing. The test probe got its own lesson: it aimed at an anonymous node, the tooltip came out correct and empty, "and I would have concluded the line was not displaying". It has also aimed at a named, crossed place ever since.

The harness was judging something else

I validated this map with screen captures, and the capture harness lied to me four times across two commits: a render at day 1 showing a motionless dot, a render stacked on top of the screen, a hover taken before the strip refreshed, a capture taken mid-glide. Each time, the capture itself exposed the lie, and the motif recurs in the commit messages: the harness was making me judge something other than what would ship. Instrument, then verify the instrument: on a screen, the loop is the same as on an engine.

A surface that tells a story is built on two rules: it draws from state that already exists and has a single owner (here, the voyage journal), and it only receives what it is allowed to say (an unreached node publishes nothing, not empty fields). Everything else is drawing.

The lesson

The living map added almost nothing to the game: one line in the journal, one animation field, three hover strings in data. Everything it shows already existed; nobody was reading it. That may be the most profitable form of content: plugging a screen into a memory the system already kept, and discovering along the way the dead data that was only waiting for a reader to become true again.