Two findings, and the first is the kind a person does not catch.
The article cache keyed on `${name}\0${qualifier}` — with the null byte written
into the file rather than escaped into the string. Git calls a file with one of
those binary and stops diffing it, and every editor between here and a reader
does something different with it. The separator was the right idea, because a
name can contain a space and `("Kepler-22 b", none)` and `("Kepler-22", "b")`
are different questions; it just has to be spelled `\0`.
And `navigator.language` is optional in the DOM's own typings and missing in
some embedded engines, where splitting it would have thrown on the first press
of About rather than falling back to English.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016jxMkwA2rbicdGxHosecYi
Every figure this map shows is a measurement or something derived from one,
and it says which. What it could not do was tell you what a place is — a
radius and an eccentricity do not say that Titan is the only moon with a dense
atmosphere. An About control on a body's panel now fetches the Wikipedia lead,
and it is labelled as what it is: prose from another site, credited and linked
back, below the line where this map's own figures end.
Only on the press. Nothing is fetched while a body loads, and nothing is
fetched twice.
Three things the encyclopedia does that had to be handled, all found by asking
it rather than by guessing:
It redirects, generously — "Proxima Cen b" lands on "Proxima Centauri b" and
"Kepler-22 b" on "Kepler-22b" — so the catalogue's own names can be sent as
they are, with no mapping table to maintain.
It disambiguates. "Titan" is a list of everything called Titan, and so are
"Mercury" and "Io". Wikipedia says so in the response, and this app happens to
know the kind, so a disambiguation is retried as "Titan (moon)". Only in
English: every wiki words its own qualifiers, and inventing a translation of
one would be inventing an article title.
And it rate-limits, which it did to me while I was checking the above. A
refusal to answer is not an empty answer, so the two are separate outcomes:
"Wikipedia has no article on this" is about the world, "Wikipedia could not be
reached" is about this minute — and only the first is remembered, so a second
press is allowed to try again.
The extract is capped and scrollable. A lead can run a dozen lines, and this
panel is anchored to the top of a viewport that may be shorter than the prose.
Verified: build clean, 606/606 unit including twelve for the lookup chain, 16/16
end-to-end including three that stub the encyclopedia — this suite tests the
panel, not Wikipedia — design detector clean, and the real thing exercised by
hand against Earth, Titan and Proxima Cen b at three viewport sizes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016jxMkwA2rbicdGxHosecYi
The map held 8750 stars within 50 pc and rendered 371 systems. Both were
lower than they needed to be, for different reasons.
The star catalogue was capped by its own encoding as much as by the
cutoff: one JSON object per star, eight key names repeated each time, 157
bytes a star. At the range HYG actually reaches that is 17 MB to download
and parse before the first frame. So the numbers move into two binary
column stores — positions in stars.bin, which the GPU is handed verbatim,
and id/magnitude/colour/spectral index in stars-meta.bin — and the JSON
keeps only the strings, with 2600 distinct spectral classifications
collapsed to a dictionary. The layout is defined once, in star-catalog.ts,
and the ETL and the app both use it, so the writer and the reader cannot
drift.
The cutoff then goes to 250 pc: 68388 stars, 7.8x as many for 1.7x the
bytes. That is where HYG's measurements stop rather than a round number —
98.6% of its rows are Hipparcos, whose parallaxes are good to about a
milliarcsecond, so beyond 250 pc it would be plotting noise.
Drawing all of them is a separate question from knowing them, and it is
answered separately. The field draws a budget: every star inside 25 pc,
because the nearest are faint red dwarfs and Proxima Centauri is magnitude
11, then the brightest of everything beyond. Search, navigation and the
planet cross-reference still see the whole catalogue. A real GPU would
draw all 68388 without noticing; the budget is for the machines that would
not, and it is one constant.
Systems were limited by something else entirely. The archive data already
shipped named 4735 host stars and only 388 resolved, because the rest lay
outside a 50 pc catalogue — and the cross-reference kept only its own
result, so redoing it meant re-downloading an archive that is not
reachable from here. Host coordinates are now stored with each planet, and
the match is re-resolved at build time against whatever catalogue the run
produced. Even name matching alone, which needs no coordinates and so
works on the records already shipped, rescues 335 planets across 238
systems: 371 renderable systems become 609.
Two selection rules were tuned for a 50 pc bubble and no longer fit.
Tethers followed the Sun's nearest neighbours, which are a speck at this
range, and now follow the brightest; labels were ranked by proximity,
which named whatever sat nearest the middle of the screen, and are now
ranked by brightness — so the view names Canopus, Achernar and Spica
rather than a clump of catalogue designations.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaySiNst4HhDXBHnMy8p5G
The design doc scopes deep-sky objects as a galaxy-view backdrop and lists
fetchDeepSky.ts, deepsky.json and deepsky.model.ts, but none of it existed —
it was the only part of the plan with no implementation behind it.
ETL: fetchDeepSky.ts pulls the OpenNGC catalog, classifies each object as a
galaxy/nebula/cluster, and keeps the ~460 worth drawing (everything Messier,
everything with a common name, and anything brighter than magnitude 9) out of
~12,000 mostly-anonymous rows. build.ts runs it and validates the output.
Distances are the hard part: OpenNGC has no distance column, and both fallbacks
fail for the best-known objects. M31, M33 and M42 are Local Group members whose
redshift is negative or absent, and a galaxy's catalog parallax comes from a
cross-matched foreground star — 6 mas for M31 would put a 780 kpc galaxy at
167 pc. So records store a unit direction on the celestial sphere rather than a
position (the line of sight is always known precisely, and the objects are drawn
on a fixed backdrop shell where true distance is unusable anyway), and distance
is optional metadata carrying its own provenance. Parallax is trusted only for
galactic objects, redshift only above z=0.003 where expansion outweighs peculiar
velocity. 330 of 463 get a distance; the rest honestly report none.
Rendering: DeepSkyRenderer paints the objects as soft additive billboards on a
2500 pc shell — clear of the 50 pc star field, beyond the camera's 2000 pc orbit
limit, and inside its 5000 pc far plane. Size comes from real angular extent, so
Andromeda is six times wider than the full Moon, clamped at both ends. Sprites
rather than points because the WebGPU backend caps point primitives at one pixel;
materials are shared per kind and brightness band, so 460 objects cost nine of
them. The brightest dozen get permanent labels, which needed the label overlay to
accept string ids alongside numeric star ids. The backdrop is decorative, so a
failure to load its dataset is logged and the star field comes up regardless.
Also documents the app in the README, which until now covered only the plugin
marketplace.
Tests: 112 passing, up from 54. Build, both typechecks and the Playwright suite
are green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaySiNst4HhDXBHnMy8p5G
Add star-map Angular app, ETL pipeline, and caveman plugin
Angular 3D star map (galaxy/system/body views, Three.js rendering,
navigation store) plus the NASA ETL tooling that builds the star,
exoplanet and solar-system datasets, Playwright e2e suite, and the
cs:caveman Claude Code plugin (command, agent, skill).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@