Commit Graph
32 Commits
Author SHA1 Message Date
SenrokaiandClaude Opus 5.5 321540ed91 Merge the solar-system branch, so the star catalogue lands on the sky it now shares
Both branches changed the system view's star, the body card's provenance line, the exoplanet
fetch and the ETL's validators. Resolved by keeping both sides:

- The system view's star is the catalogue's (its own radius and temperature, a limb-darkened
  surface in its colour) and turns like a planet when it is the Sun (the solar branch's IAU pole
  and 25.38-day turn), keyed on SUN_STAR_ID, since the catalogue branch dropped the scene's own
  SOL_STAR_ID. Framing takes the outermost thing drawn (an eccentric orbit's aphelion, from the
  solar branch) and the star's radius for a giant (from the catalogue). The solar branch's comment
  about a halo is dropped: there has been none since #33.
- The card's no-temperature sentence is the catalogue's (the host's luminosity or the orbit's size,
  not "not in the catalogue", which holds for 27 planets) and ends with the solar branch's reason
  why no image is used (a point of light for the 101 imaged planets, none for the rest).
- fetchExoplanets reads the composite table and the distance errors (catalogue) and the imaged
  list (solar); build.ts runs both branches' validators.

The data were regenerated by the full ETL on the merged code, from cache (nothing refetched):
stars.bin, stars-meta.bin, stars-index.json and deepsky.json come out byte for byte the catalogue
branch's, bodies.json the solar branch's, and exoplanets.json the catalogue branch's but for the
imaged flag on 101 planets, WASP-108 b not among them. Unit suite 977 passed, the two branches'
870 and 837 over their shared 730, so no test was lost.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 22:07:34 +02:00
SenrokaiandClaude Opus 5.5 cea4ff799f Bring the counts the comments and README quote back to the catalogue
Measured on the published catalogue at this commit, through starSurfaceOf and starReadouts:

- star-readouts.ts said 11 546 derived radii read "from its type": 10 702 giants with a colour and
  844 stars with none. That left out the 26 dwarfs whose colour is off the table, and the colour
  Gaia now gives 931 HYG-described stars moved the rest: 10 953 = 10 713 giants with a colour,
  214 stars with none (GJ 3655 among them) and those 26. 5 more read "from its temperature".
- stellar.ts said 821 dwarfs are placed at their type's row. Counted over every star that is not a
  giant and whose colour the table does not read: 224 with no colour and 36 with one off the table.
- The README still said the card marks every derived radius "from colour and brightness"; it now
  names the three bases the card gives.
- build.ts's survivor breakdown, after θ¹ Ori A left: 11 464 HYG stars without a Gaia counterpart,
  8 307 of them past 250 pc, 1 660 of those naked-eye.

Comments and documentation only.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 21:48:40 +02:00
SenrokaiandClaude Opus 5.5 24c7be1bae Draw four moons' nodes at JPL's current rates, which Horizons and the IAU's poles agree with
The archived satellite table the moons are read from gives older node
periods than JPL's current one for Miranda (17.727 years against URA182's
17.787), Ganymede (132.654 against 137.812), Callisto (338.82 against
577.264) and Titan (704.60 against 687.370), and 74ea1d6 turned the IAU's
poles after those older rates, taking them for the right ones. They were
not: fitted to Horizons' osculating elements on Uranus's equator over
1601-2399, Miranda's node turns 2023.97 degrees a century (rms 0.04),
against the current table's 2023.95, the IAU's U11 2024.22 and the row's
2030.80. That commit moved Miranda's axis from 0.38 to 2.36 degrees of
Horizons' orbit normal. Mimas's node, 0.986 years in both tables, now takes
the IAU's S3, 36505.5 degrees a century, 1.2 from a Horizons fit where
the row's is 4.5.

The ETL spec carries the node period (nodePeriodYears), and the periapsis
keeps the row's longitude rate: the row gives the longitude's rate (Callisto
68.7 degrees a century, the current table 67.2), so the argument takes up
the change. On the row's argument Callisto's periapsis moved 44 degrees by
2100 and it strayed 0.71 degrees from Horizons over 1950-2100.

Worst angle from Horizons' osculating orbit normal (ICRF), HEAD then now:
- Miranda, 1601-2399: drawn orbit 2.11 -> 0.12, axis 2.36 -> 0.34.
- Mimas, 1750-2249: orbit 0.33 -> 0.11, axis 0.65 -> 0.40.
- Ganymede, 1600-2199: orbit 0.17 -> 0.11, axis 0.13 -> 0.01.
- Callisto, 1600-2199: orbit 0.53 -> 0.20, axis 0.02 -> 0.03.
- Titan, 1750-2249: orbit 0.048 -> 0.052.
Against the 1950-2100 Horizons tracks the ETL checks: Callisto 0.19 -> 0.08,
Miranda 1.73 -> 1.62, Ganymede 0.30 -> 0.29, Mimas 7.43 -> 7.42, Titan 0.06.

lockedToOrbit now barely moves the IAU's node terms: Miranda's U11 to
-2023.95, Ganymede's J5 to 261.23 (262.1), Mimas's S3 not at all, and
Callisto's J6, 3.1 per cent from its new node rate, to 62.36 (64.3), which
takes Callisto's axis from 0.56 to 0.22 degrees of its drawn orbit.

With the drawn rates right, leaving every node term at the IAU's rate no
longer failed the ETL (Mimas used to), nor did a 1 per cent tolerance, the
node's angle without its harmonics, or the older node periods. The axis
ceiling is now 0.25 degrees for Europa (0.13 measured), Ganymede (0.16),
Callisto (0.22), Rhea (0.17), Miranda (0.23) and Triton (0.15), and
Callisto's track ceiling 0.15 (0.08). Guarded mutants, each through the
solar ETL on the real catalogue and then the suite on the bodies.json it
wrote:
- node terms left at the IAU's rates: ETL "europa's spin axis leans up to
  0.33"; suite fails only 'turns the poles of Europa, Ganymede, Callisto,
  Rhea, Miranda and Triton round with their drawn nodes'.
- tolerance 1 per cent: ETL, Callisto 0.33; the same test, alone.
- harmonics dropped: ETL, Triton 0.29 (the suite's three dates miss it).
- the archived node periods: ETL, Callisto's track 0.19; the suite fails
  that test and 'draws Miranda's orbit, and turns its axis, where Horizons
  has its orbit in 1601 and 2390' (orbit 2.02, axis 2.36 at 1601).
- the row's argument kept: ETL, Callisto's track 0.71.

The docs that called the IAU's rates the wrong ones are corrected
(lockedToOrbit, the axis ceiling in build.ts, whose Titan node now turns
in 687 years), and the README and BodyRecord doc no longer say every
locked moon's node terms are re-rated: only those within 5 per cent of a
multiple of the node's rate, never the Moon's or Phobos's, and not the
circles Ariel's, Umbriel's, Titania's and Oberon's poles go round on
(0.36 to 0.50 degrees off their orbits). The README also names the five
bodies the IAU's elements do not turn. Unit suite 866 passed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 19:22:57 +02:00
SenrokaiandClaude Opus 5.5 f31ffe1425 Store a star's distance error in two bytes, so the card prints the error its catalogue published
a81dd49 kept the square root of the relative error in 255ths. A step was a few per cent of the
error itself, which moved the last digit the card prints: over the cached Gaia answers, 2 822 of
the 53 209 stars whose card prints an error printed another one than their published parallax
error gives (Gaia DR3 1415230383034813824 "112 +- 2 pc" for 3), and Rigel, 3.78 +- 0.34 mas in van
Leeuwen 2007, read "265 +- 23 pc" for 23.8.

The column is now a Uint16 in 65 535ths, placed before the photometry byte so its view stays
two-byte aligned whatever the star count; BYTES_PER_STAR_META goes from 16 to 17, the build.ts
round trip tolerance to half a 65 535th, and the README names the column. The same count gives 12,
each on a rounding half. In the running app Rigel reads "265 +- 24 pc".

The cost: stars-meta.bin goes from 7 288 512 to 7 744 044 bytes, and gzip -9 from 3 117 639 to
3 623 379, half a megabyte more to download, since the extra byte is noise-like. One reviewer
judged the one-byte figure within the errors' own accuracy; this takes the other two's view that
the card should print the published number.

Control: 255 steps again fails "keeps an error close enough that the card prints the published
one" alone.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 17:43:54 +02:00
SenrokaiandClaude Opus 5.5 74ea1d655e Turn a locked moon's pole round with its drawn node, so its axis stays on its orbit at every date the clock reaches
The IAU carries a locked moon's pole round its orbit normal on a term of the node's angle, as a
moon in a Cassini state keeps it, but at the node rate the IAU's source had: Miranda's U11 at
-2024.22 degrees a century where the JPL table its orbit is drawn from has -2030.80, Mimas's S3 at
-36505.5 against -36511.16. Over AD 1-3000 that parted Miranda's drawn axis from its drawn orbit
normal by up to 7.89 degrees (AD 9), so Uranus swung 7.6 degrees north and south on its sky every
1.41 days, and Mimas's by 2.63. Only Iapetus's pole had been put on its orbit.

lockedToOrbit now sets every periodic term whose angle turns within 5 per cent of k times the
drawn node rate (k from 1 to 9, the most the report takes, Triton's) to exactly that multiple, and
moves its constant so the angle, and the pole and W with it, are unchanged on 2025-01-01 (the
2025 pole and W of every changed moon are identical to the 1e-14 degree). Measured: the largest
offset taken is Ganymede's J5, 3.4e-2, then Rhea's 1.2e-2; the nearest term that is not a node is
6.0e-2 out (a W-only term of Miranda's), and Umbriel's W has one 1.0e-2 from ten times its node,
which the k limit leaves. Ten moons change: Deimos, Io, Europa, Ganymede, Mimas, Tethys, Rhea,
Miranda, Triton and Proteus. The Moon and Phobos, whose W carries a quadratic, are left as before,
and so is Callisto's J6, 40 per cent from its node rate.

Worst angle between the spin axis and the drawn orbit normal over AD 1-3000, every 135 days,
before and after: Miranda 7.89 -> 0.42, Mimas 2.63 -> 0.47, Rhea 0.77 -> 0.17, Triton 0.51 ->
0.15, Europa 0.33 -> 0.13, Ganymede 0.50 -> 0.16. Faces: Miranda 2.75 -> 2.39, Mimas 8.94 ->
8.89, Deimos 2.14 -> 2.08, Triton 2.88 -> 2.81; none got worse.

build.ts now checks that angle for every locked moon over the same 8114 dates as the face check,
at most 1 degree (Tethys 0.97, whose IAU pole sits 0.69 from its orbit today; Titan 0.94, whose
IAU pole is still while its node turns in 705 years), with four named ceilings: the Moon 7.1 (its
real 6.7-degree tilt, 6.98 at worst), Phobos 2 and Deimos 2 (1.81 and 1.74) and Proteus 1.2
(1.09), whose IAU poles nod with Mars's and Neptune's precessing poles while the Laplace poles
their orbits are drawn round are fixed. The obliquity check at Horizons' epoch shares the new
axisFromOrbitDeg with it. Nothing checked the axis against the orbit before: an Iapetus pole left
on its Laplace pole, 8.30 degrees off at every date, passed the ETL and the suite.

Guarded mutants, each through the solar ETL and then the suite on the data it wrote:
- node terms left at the IAU rates: the ETL fails with "Moon mimas's spin axis leans up to 2.63
  degrees ... (at most 1 expected)", and the suite with only the new test failing;
- Iapetus's pole put on its Laplace pole, W re-phased so its face today is unchanged (worst face
  16.41, under its 16.5 ceiling): the ETL fails with "Moon iapetus's spin axis leans up to 8.30
  degrees", and the suite with only the new test failing.

Also: lockedToOrbit called the Iapetus normal's circle "8.3 degrees across"; 8.3 is its radius (the
row's i = 8.298 to the Laplace plane) and it is 16.6 across. The README credited every rotation to
pck00011 without saying that a locked moon's W and node terms are re-rated to its JPL mean elements
and Iapetus's pole carried round its orbit normal; both of its lines now say so, as the body
model's doc does.

Unit suite 858/858, etl:typecheck and tsc -p tsconfig.app.json clean, solar ETL passes on the
real data.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 17:17:54 +02:00
SenrokaiandClaude Opus 5.5 734b048c9e Write the star assets once, after the archive's hosts are added, not before them as well
Since 494fb56 and 4c8e4a0, fetchStars wrote stars.bin, stars-meta.bin and stars-index.json, and
fetchExoplanets wrote them again with the 3 277 hosts it adds from the archive and the 574 Gaia
designations it renames, after the live Horizons and archive fetches in between. A run stopped
between the two writes, or fetchStars.ts run on its own as the README documented, left 452 294
stars on disk with no archive star and 574 hosts back to their designations, and the existing
exoplanets.json pointing 4 237 planets at stars that were not there. It happened on this branch:
the dev server served those files.

fetchStars now returns the stars and writes nothing; fetchExoplanets, which build.ts runs after
it, is the only writer. fetchStars.ts run on its own runs fetchExoplanets, which fetches the
stars itself. Checked from cache: `tsx tools/etl/fetchStars.ts` exits 0 and writes 455 571 stars,
the published files byte for byte; build.ts with fetchSolarSystem made to throw fails with
"simulated Horizons outage" and leaves every data file unchanged, where before it left the
452 294-star catalogue. The README's table now names fetchExoplanets.ts as what writes them.

The ETL has no unit harness; these two runs are the check.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 16:21:03 +02:00
SenrokaiandClaude Opus 5.5 89c2568018 Describe the star at a system's centre as it is drawn now, not by the innermost-orbit rule and its halo
The README still said the star was sized against its innermost orbit and made visible by a halo,
two things 7213f98 and 2242fe0 removed. It now says what the view draws: the star at its own
radius on the orbits' scale, the archive's for a host and derived from luminosity and temperature
otherwise, marked "from colour and brightness" on the card; a limb-darkened disc in its
blackbody's colour, lighting its planets in that colour against the Sun's; a grey point where
nothing gives a size or temperature (5aac46d); the three-pixel floor and no halo; the camera held
at 0.05 AU or three radii, whichever is further, and a giant framed inside its neighbours' ring
(d1aa22e).

Measured on :4302 at 1600 by 1000 against published radii: Sun 1.0000 R☉ (card "1.00 solar
radii"); Proxima Centauri 0.1410, the archive's st_rad (0.154 in Boyajian et al. 2012); Sirius A
1.7943, derived, card "~1.8 solar radii, from colour and brightness" (1.711); TRAPPIST-1 0.1192
(0.119). The star's light: Sun (1, 1, 1), Proxima (1, 0.523, 0.165), TRAPPIST-1 (1, 0.443,
0.095), ups And (F8V) (0.899, 0.937, 1), Sirius (0.52, 0.666, 1), intensity pi in every one.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 13:34:38 +02:00
SenrokaiandClaude Opus 5.5 23547defe0 Read an archive host's colour off the dwarf sequence at its temperature, and say it was not measured
For an archive-placed host with a temperature and no B magnitude, fetchExoplanets took B−V from
Ballesteros' blackbody fit, which runs 0.1 to 0.2 redder than Pecaut & Mamajek's dwarf sequence
below 3 800 K, and the luminosity then read its correction off that sequence at that colour: 3 500 K
came back as 3 102 K with a correction 1.15 magnitudes too large, anything under about 3 170 K was
clamped to B−V 2.00, and the card printed it as a measured "Colour B−V 2.00". CFBDSIR
J145829+101343, a 580 K brown dwarf, read "Spectral type ~M6, from colour".

temperatureToColorIndex now reads the table itself backwards, interpolating B−V between the two
types the temperature falls between, so the temperature and correction read back off the colour
are the table's at that temperature; it has no answer outside 2 420 to 31 400 K. The colour is
flagged colorFromTemperature, a fifth bit in the photometry byte (the format, README and the ETL's
round-trip check follow), and the card prints it "B−V 1.66, from its temperature", marked derived.

From cache: 57 archive stars change colour; 54 carry the flag and 3, CFBDSIR J145829+101343 among
them, now have none. For the 47 of them the archive gives a luminosity, the one derived from
magnitude and colour moves from a median 0.228 dex off it to 0.124; Kepler-445 (3 157 K) from
0.0282 L☉ to 0.0080 against the archive's 0.0079. Its colour goes from 2.00 to 1.67. The card shows
the archive's luminosity where it has one since earlier on this branch, so this is the figure used
for the rest and for their radii.

Controls, each failing its named test: the nearest hotter row taken without interpolating (2 of
803 failed), no refusal outside the table, the flag not encoded, and the card calling the colour
measured (1 of 803 each).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 21:29:09 +02:00
SenrokaiandClaude Opus 5.5 cbe4908219 Turn Earth by the Earth Rotation Angle, so its lit face stays Horizons' at AD 1 as it is today
Earth was turned by its IAU W, taken at the clock's UT plus today's 69.184 s. That W is a straight
line fitted to the present: 360.9856235 degrees a day, which, once its pole's -0.641 degrees a
century in right ascension is counted, runs 6.3e-6 degrees a day slow of Earth's real turning. The
followsUt comment said the clock's date "already says how far it has turned"; it did not. Against
Horizons (observer quantity 14 from the Sun, TIME_TYPE=UT, Earth one light-time back) the drawn
sub-solar point was 2.3 degrees off at AD 1000 and 4.5 at AD 1.

Earth is now turned by the IERS Earth Rotation Angle (IERS Conventions 2010, eq. 5.15) at the
clock's date, counted from the node the IAU's W starts at, 90 degrees past the pole's right
ascension. The pole is unchanged. Drawn minus Horizons, in degrees:

  date                       before    after
  2025-06-01 12:00 (unit)     +0.06   -0.003
  AD 1000, JD 2086455 (unit)  -2.3    -0.001
  AD 1, JD 1721600 (unit)     -4.5    +0.051
  live app, :4301, same probe as the review's
    JD 2460900.25             +0.089   +0.005
    JD 2086300.5              -2.281   +0.010
    JD 1800000                -4.049   +0.056
    JD 1721450.75             -4.530   +0.072

At noon UTC on 1 June 2025 the Sun now stands over 0.52 W on the drawn sphere, where the equation
of time puts it at 0.53 W (0.43 W before).

TT_MINUS_UTC_DAYS had no other use and is removed; its comment also counted 37 leap seconds where
UTC has taken 27 on top of the 10 s it started from in 1972.

Tests: body-orientation.spec 'lights Earth's face where Horizons does at the far end of the clock
too: AD 1000 and AD 1' (within 0.15 degrees), and the renderer's AD 1000 Earth test now checks the
drawn face against Horizons instead of against the IAU W the old code used. Control: turning Earth
by its IAU W at UT + 69.184 s again fails both named tests (2 failed, 834 passed). The README says
which model turns Earth.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 21:09:54 +02:00
SenrokaiandClaude Opus 5.5 1a8e734651 Leave the dark nebulae out of the backdrop, whose sprites can only add light
c01d3ec read OpenNGC's addendum, which brought in its only two DrkN rows, C099 the Coalsack and
B033 the Horsehead; NGC.csv has none. classifyOpenNgcType mapped DrkN to 'nebula', so both were
drawn as the backdrop's nebula sprite: ff86b0, additive, at the faintest opacity and the 70 pc
minimum size, about 1.6° across. The review measured it adding up to +59 in red over the Coalsack
beside Crux, where the sky has a dark hole, and darkening no pixel, as an additive sprite cannot.

DrkN is now among the codes dropped, with the reason beside the map. fetchDeepSky from cache keeps
488 objects against 490, still 107 Messier objects and every required id; README's count follows.
On :4302 the served backdrop holds 488 objects and the scene 488 sprites, neither of them C099 or
B033.

Control: DrkN mapped back to 'nebula' fails "leaves out a dark nebula, which a sprite that adds
light cannot draw" (1 of 801).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 21:00:31 +02:00
SenrokaiandClaude Opus 5.5 98c4eb901f Credit the sources the solar system's orbits now come from, in the README and a search comment
Since 48319c3 and 1d42be2 no position comes from Horizons: the planets move on Standish's mean
elements (Table 2a/2b), the moons on JPL SSD's satellite table, Ceres, Eris, Haumea and Makemake
on the Small-Body Database's osculating elements, and every body turns by the IAU's rotational
elements from NAIF's pck00011. bodies.json's orbitSource values say so (9 Standish, 25 satellite
table, 4 SBDB, none Horizons). Horizons gives sizes, spins and the positions the ETL checks
against. The README still credited "Solar-system ephemerides: NASA/JPL Horizons", said the Sun's
bodies came "from JPL Horizons", listed fetchSolarSystem's source as "JPL Horizons / SSD", and had
Horizons reporting every element against the ecliptic, where the moons' are against a Laplace
plane or their planet's equator. The branch had edited the credits paragraph and left that line.
search-ranking.ts called the solar-system bodies "eighteen famous objects"; there are 38.

Documentation only; no behaviour changes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 19:57:50 +02:00
SenrokaiandClaude Opus 5.5 db3af1a820 Wrap Deimos in Stooke's Viking map, once its longitudes were settled on the body
Audit #40. deimos.jpg was a 592x592 disc photograph, 32.6% black sky, left in the folder
unlisted by 302fa96 because the one cylindrical map found then (USGS
wms_basemaps/Deimos/deimoscyl4.jpg) gave no way to tell which way its longitudes ran. It is now
Stooke's later map of the same set, from the NASA PDS Small Bodies Node
(MULTI-SA-MULTI-6-STOOKEMAPS-V3.0, deimos_cyl_viking_mro.jpg): Viking Orbiter images with MRO
HiRISE detail, 7200x3600 simple cylindrical, "0 longitude at the center".

The guide does not say which way longitude runs, and USGS georeferences its copy of the older
version with 0 at the left edge. Settled on the body: read with 0 in the middle and east to the
right, and drawn as a globe from outside, the hemisphere at 90 E matches unmirrored the sheet
Stooke titles "trailing side" (270 W), and the one at 90 W his "leading side". A synchronous
prograde moon trails at 90 E, so that reading holds; read the USGS way, the sheets would land on
the wrong hemispheres. A 1 km depression sits at Swift's Gazetteer position (12.5 N, 1.8 E).

Processed like the other maps (build_maps.py): no no-data pixels to grey (13 source pixels of 26
million at 0), area-downsampled to 1024x512, JPEG q85, 55 KB. Black pixels (under 8 of 255): 0%.
The map wraps seamlessly (mean 1.8 grey levels across the seam).

Measured in the running app (port 4311) through the IAU rotation and MAP_TO_BODY: Mars stands
over 0.18 W, 0.41 W and 0.20 W of the drawn map (latitudes within 0.04 deg) on 2000-01-01.5,
2025-01-01 and 2050-01-01, and Deimos heads toward 90.08 W, 90.30 W and 90.11 W: the hemisphere
that matched Stooke's leading sheet leads.

texture-catalog.spec.ts now lists Deimos among the mapped bodies. Mutant: the deimos line removed
from BODY_TEXTURE_PATHS; only 'wraps the moons and dwarf planets that have a mission mosaic in it'
failed (1 of 805). The textures README gives the source, credit, licence (the PDS archive states no
use restriction) and the longitude check; the root README counts twenty-eight photographed bodies.
The five Uranian moons stay derived: USGS has only Voyager control networks for them, no mosaic.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 13:29:22 +02:00
SenrokaiandClaude Opus 5.5 81a4ecfcde Name the columns stars-meta.bin gained in the README
The README listed the four columns stars-meta.bin had before a81dd49 added the photometry byte
(magnitude band, colour system, whether the distance is Gaia's) and the distance's relative
error.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-24 23:48:28 +02:00
SenrokaiandClaude Opus 5.5 302fa963ad Wrap seventeen moons and dwarf planets in the missions' own maps, grey where no probe looked
Audit #40. Io, Titan and Pluto had square disc photographs (40.5, 42.6 and
42.9% black sky) that PR #33 unlisted, and every other moon or dwarf planet
fell through to the derived surface. Seventeen of them are now wrapped in
public-domain global mosaics from USGS Astrogeology and the NASA PDS: Phobos
(Viking), Io, Europa, Ganymede, Callisto (Galileo and Voyager), Mimas,
Enceladus, Tethys, Dione, Rhea, Titan, Iapetus, Phoebe (Cassini), Triton
(Voyager 2), Ceres (Dawn), Pluto and Charon (New Horizons). io.jpg, titan.jpg
and pluto.jpg are replaced by maps under the same names.

Every file is simple cylindrical over 360 by 180 degrees, with longitude 0 in
the middle and east to the right, the frame MAP_TO_BODY puts on the IAU body
frame. Processing: the source's no-data pixels (0 in every band) become one
flat grey, the mean of the mapped surface, never invented terrain; area
downsampling to 2048x1024 for bodies over 1 000 km in radius and 1024x512
for the rest; half a turn where the source is centred on 180; JPEG q85
(Europa q82). Largest file 386 KB (Europa); 3.5 MB for all seventeen.

The centre was read from each GeoTIFF's central meridian and left-edge tie
point, not from its label: Rhea's and Enceladus's labels say CENTER_LONGITUDE
= 180 over images centred on 0. Taken from the label, Rhea came out half a
turn round, which the seam it left down the middle of the map gave away.
Each map was then checked by eye against the IAU Gazetteer: Pele and Loki on
Io, Pwyll on Europa, Osiris and Tros on Ganymede, Valhalla and Asgard on
Callisto, Herschel on Mimas, Ali Baba and Aladdin on Enceladus, Odysseus on
Tethys, Creusa on Dione, Inktomi on Rhea, Xanadu, Shangri-La and Belet on
Titan, Cassini Regio on Iapetus, Jason on Phoebe, Occator and Haulani on
Ceres, Stickney on Phobos, Sputnik Planitia and Cthulhu on Pluto, Mordor
Macula on Charon, Leviathan Patera on Triton.

Unmapped share, now grey: Triton 38.6%, Charon 34.0%, Pluto 31.9%, Phoebe
20.4%, the Galilean polar gaps 3.6-4.3%, Ceres's south pole 3.6%, the rest
under 0.2%. Pixels darker than 8 of 255: at most 0.55% (Charon's Mordor
Macula, Pluto's Cthulhu), against the 20-43% black sky of the photographs
PR #33 dropped.

Left out, and said so in src/assets/textures/README.md: Deimos, whose only
cylindrical map (Stooke, Viking) has no label for its longitude direction and
on which neither Voltaire nor Swift could be found to settle it; the five
Uranian moons, whose only maps (Schenk 2020, USRA) carry no licence; Hyperion,
Nereid, Proteus, Eris, Haumea and Makemake, which have no photographic
simple-cylindrical map.

Source URLs, credits, licences, processing and each measurement are in the new
src/assets/textures/README.md; the root README now points there and counts
twenty-seven bodies in real photography. texture-catalog.spec.ts checks that
the seventeen are registered and that Deimos, the Uranian moons, Hyperion and
Eris are not. Unit suite 790 -> 792 tests.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-24 23:41:28 +02:00
SenrokaiandClaude Opus 5.5 c01d3ec2bc Read OpenNGC's addendum, so the Pleiades and the Large Magellanic Cloud are on the map
OpenNGC keeps the objects no NGC or IC number covers in a second file, addendum.csv, with the
same 32 semicolon-separated columns as NGC.csv. The ETL only ever fetched NGC.csv, so the
brightest deep-sky object in the sky, the Large Magellanic Cloud (V 0.29), was missing while
the Small one was drawn, and so were the Pleiades (M45), the Hyades, the Horsehead and the
Coalsack. fetchDeepSky now fetches the addendum beside NGC.csv, caches it as
openngc-addendum.csv, and runs its rows through the same loop.

Of its 64 rows, 27 pass the existing filters: all 22 named or Messier rows except M40, which
OpenNGC types as a double star, and M102, typed as a duplicate of M101; plus seven anonymous
open clusters brighter than V 9 (H05, H20, H21, Mel071, Mel101, Mel105, MWSC3171). deepsky.json
goes from 463 to 490 objects (galaxies 73 -> 85, clusters 294 -> 306, nebulae 96 -> 99), with
no existing record changed. Messier coverage goes from 106 to 107 of 110. 340 of the 490 have
a distance: the Pleiades 135.8 pc and the Coma Star Cluster 85.9 pc from their parallaxes;
the Hyades and the Local Group dwarfs honestly have none.

validateDeepSky now requires the Andromeda Galaxy and the Small Magellanic Cloud from NGC.csv,
the Large Magellanic Cloud and the Pleiades from the addendum, and at least 107 Messier
objects. Both checks were run against the real catalogue with the addendum removed: the ETL
fails on "Deep-sky object ESO056-115 is missing", and with the required list emptied, on
"Only 106 Messier objects were produced".

In the app, on the dev server: 490 sprites. The Pleiades sprite lies 0.00182 deg from Alcyone
(0.00183 deg from the published coordinates); the LMC 18.4209 deg from Canopus and 26.8215 deg
from Achernar (18.4209 and 26.8215 published); the Hyades 2.2591 deg from Aldebaran (2.2591).
The twelve labelled deep-sky objects now open with the Large Magellanic Cloud and the
Pleiades and include Brocchi's Cluster, in place of h Persei, chi Persei and NGC 2516.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-24 20:59:39 +02:00
Claude 7d07921d69 Say what the project is, in the two places people arrive
The README opened without mentioning that the thing is deployed, and the HTML
document carried a title and nothing else — no description, no link preview.
Both matter more now that there is a public URL to land on.

README: the live link up top; the object card and the measured/derived split
from #3, which were built but never written down; `shared/format/`, `public/`
and `.github/workflows/` in the layout.

index.html: a description, a theme colour matching --color-void so the browser
chrome does not flash white around a black sky, and Open Graph/Twitter tags so
a shared link renders as something other than a bare URL. Those URLs are
absolute and name the deployment outright — a scraper has no document to
resolve a relative path against, so they cannot follow <base> the way the rest
of the app's paths do. A custom domain later means editing these three lines.

The preview image is the galactic view, copied into `public/` so it is served
by the site itself rather than linked out to raw.githubusercontent.

Verified: the built document keeps every tag, the base href still rewrites to
/star-map/, the image answers 200 at the path the og:image names, and both the
root and a deep link still render. 527 tests pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaySiNst4HhDXBHnMy8p5G
2026-08-07 14:22:29 +00:00
Claude 433bcca8a2 Stop publishing the repo as a plugin marketplace, and refresh the run skill
Three small things.

Removed `.claude-plugin/marketplace.json`. It is the file `/plugin
marketplace add` reads, so without it this repo no longer offers itself as a
marketplace — which is the odd part of an Angular app carrying one. The
caveman plugin's own files stay, so it can be listed from a marketplace of its
own later; only the listing is gone.

That left four documents asserting an install path that no longer exists,
including a README section handing out `/plugin marketplace add` and
`/plugin install` commands that would now fail. All four now say what is
actually true. `.junie/plans/nasa-star-map.md` still describes the repo as
containing only a marketplace, and is left alone: it records what was here
before the app was written, and is not a claim about the present.

The run skill's numbers had drifted a release behind — 496 tests in 28 files
against a real 527 in 31, and a build timed at 9s that now takes 12. Measured
rather than guessed. The pinned Playwright version it names was correct.

It also gains the Pages build, which is not a plain `npm run build`: the base
href and the 404.html copy are both required for a project site, and the
artifact root is `dist/star-map/browser` rather than its parent. Plus the
matching gotcha, since a Pages build served at `/` looks broken in a way that
tells you nothing about whether it would work.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaySiNst4HhDXBHnMy8p5G
2026-08-07 14:02:12 +00:00
Claude 8191254b3c Put the four views in the README, and correct what one of them showed
The README described the app without showing it. Adds a screenshot to each
of the four sections it describes, captured from a real run at the current
state of the code rather than assembled or touched up.

Capturing them caught a claim that had gone stale. The galactic view's
readout still said everything inside 50 pc was real, which was true when
that string was written and has been wrong since the catalogue reached
250 pc. It now quotes the catalogue's own size and reach, so it cannot
drift again — and, usefully, that makes a stale screenshot self-evident:
the numbers are in the picture.

JPEG rather than PNG, at 1.1 MB for all four instead of about five. These
are dark scenes with fine gradients, where JPEG can band, so they were
checked at quality 88 rather than assumed to be fine.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaySiNst4HhDXBHnMy8p5G
2026-08-05 09:35:19 +00:00
Claude efa9e4084a Draw the whole catalogue, and build the aggregation the rest would need
Two things, one verified and one that cannot be.

The render budget is now the whole catalogue: 68388 stars, one instanced
draw call, which is what a GPU should be asked to do. The budget itself
stays, because the catalogue is meant to grow past what any machine should
draw at once — Gaia alone could contribute a million — and at that point
the selection is what keeps the field legible rather than a grey wash. A
`?stars=` override handles the machines that cannot, including the
software rasterizer the end-to-end suite runs against, whose frame rate is
two orders of magnitude below a real GPU's and which was measuring the
rasterizer rather than the app.

The aggregation is the second thing, and none of it has run. Every ESA,
NOIRLab, SDSS and Euclid endpoint is unreachable from here — only GitHub
raw is, which is why HYG and OpenNGC are the current sources. So this is
infrastructure and a Gaia query written against the published DR3 schema,
not data.

What the framework encodes is that these surveys are not interchangeable.
The distinction is not size but whether a catalogue knows how far away its
objects are, because a 3D map cannot place a star it only has a direction
for. Gaia is the only one of the five that can add stars here, because it
is the only one that measures parallaxes. DECaPS2 has fifty times Gaia's
object count and photometry alone — not one of its 3.32 billion objects
can be placed in depth. Euclid's bulge is 8 kpc away, where a parallax is
microarcseconds; its contribution would be imagery. SDSS-V and SAGA are
keyed to stars something else already places, so they enrich rather than
extend. Those roles are recorded as data the ETL prints, not as prose that
can drift.

Overlapping catalogues are reconciled on direction rather than on 3D
proximity, which is the one non-obvious part. Two surveys agree on a
star's direction to within an arcsecond and disagree on its distance by
tens of per cent, so a star at 200 pc is 50 pc from itself between
catalogues while being unmistakably the same object. Matching in 3D would
need a tolerance so loose it swallowed real neighbours. The better
parallax wins where both reach; where only one does, the star stays.

Names become dense-with-holes with a source dictionary, because a survey
catalogue has no proper names — writing "Gaia DR3 4472832130942575872"
once per star would cost 25 MB per million to repeat what two adjacent
fields already say. An empty entry costs three bytes and is regenerated on
load. The Sun needed its own case in the merge: it sits at the origin, has
no direction to compare, and appears in every catalogue.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaySiNst4HhDXBHnMy8p5G
2026-08-05 08:51:54 +00:00
Claude 29fd92d118 Widen the star catalogue, and separate what is drawn from what is known
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
2026-08-05 08:35:41 +00:00
Claude 029162ff52 Lower the star halo floor so the inner orbits stay legible
The floor that stopped the Sun disappearing reached past Venus and up to
Earth, covering the two orbits it most needed to leave alone.

Halved, from 3.5% of the framed radius to 2%. The halo's visual radius is
half its extent, so that puts its edge at 1% of the framed radius, and the
orbits it has to clear sit at their own fraction of the same radius: in
the solar system, framed to hold Pluto, Venus is at 1.3% and Earth at
1.8%. Both are now outside it, and the star still reads at about nine
pixels across on a typical window.

Mercury, at 0.7%, is still inside — and would be at any halo large enough
to see, since its orbit is only three pixels wide at that range. That is
now a pinned test rather than an oversight.

The floor was only ever the lower bound; the tests now state the upper one
too, in the terms the trade is actually made in — pixels on screen for
visibility, AU against real orbits for clearance.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaySiNst4HhDXBHnMy8p5G
2026-08-05 08:01:19 +00:00
Claude be19d9cbcc Keep the star visible at the distance that frames its system
Framing the whole system pushed the camera far enough back that the star
at the centre became a speck — about a pixel across for the Sun.

The cause is a constraint that cannot be tuned away. A star is sized
against its system's innermost orbit, because it must never swallow its
closest planet, while the camera is placed to frame the outermost ring.
In the solar system those differ by a factor of a hundred: at the distance
that fits Pluto in view, a disc that stays clear of Mercury is a pixel
across. No radius satisfies both, because the information genuinely does
not fit on one screen at that zoom.

So the disc stays honest to the orbits and the halo carries the
visibility. Light is not a surface: a glow that reaches past the innermost
orbit says the star is bright, not that it is large. Its extent is still a
multiple of the star — so a compact system keeps exactly the corona it had
— but floored against the framed radius, which is what the wide systems
needed.

The disc grows a little too: it may now reach 45% of the innermost orbit
rather than 35%, which still leaves clear space between the star's limb
and the closest orbit.

Also makes createGlowSprite take the extent it will draw rather than a
radius and a multiplier. The two were only ever multiplied together, and
how large a star's halo should be is not a property of the star — it
depends on how its system is framed, which is a decision that belongs with
the framing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaySiNst4HhDXBHnMy8p5G
2026-08-05 07:40:10 +00:00
Claude 6019987fc4 Frame the system view from the camera it actually has
The grid overflowed the frame in 368 of the 371 systems the datasets
contain — median fill 1.11, and the outermost ring cut off by the viewport
edge in almost every one.

Two compounding causes. The framing distance was a fixed multiple of the
outermost orbit, tuned by eye against a 55-degree field of view; the
engine's camera is 50. And it framed the outermost *orbit*, while the
widest thing actually drawn is the grid's outer ring, which by
construction always sits beyond it.

Neither is fixable by adjusting the multiple, because a multiple is the
wrong shape of answer: what has to fit is a radius on screen, and how much
radius a given distance buys depends entirely on the lens. So the distance
now comes from the camera's own vertical field of view and aspect —
picking whichever screen axis is the tighter one, so a portrait window
backs off further rather than clipping — applied to the grid's outer ring
with an explicit margin around it.

The ceiling goes up with it. Eighty AU could not frame the solar system
out to Pluto once the real field of view was accounted for; that needs 120
on a landscape display and 140 on a portrait one. Only companions hundreds
of AU out reach the new ceiling, and those still arrive framed on their
inner region.

Measured across every system in the data, at three window shapes: the
overflow count drops from 368 to 2, the fill settles at exactly 0.89 —
the margin, uniformly — and the outer ring still encloses the outermost
orbit everywhere, so neither invariant was traded for the other.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaySiNst4HhDXBHnMy8p5G
2026-08-05 07:30:02 +00:00
Claude ac296f5133 Derive a surface for every body that was never photographed
Fifteen bodies here have a real photograph. Every exoplanet does not, and
never will on current instruments — none has ever been imaged — and nor do
several of the solar system's own moons. Those all shared one crude
stand-in: a few noisy bands tinted by category, cached per colour, so
every exoplanet in the app was literally the same picture.

They now get a surface reasoned from what has actually been measured.

The chain is standard at every link. A host star's luminosity comes from
its catalogued apparent magnitude and its parallax distance — that pair is
exactly an absolute magnitude — plus a bolometric correction for its
spectral class. The correction is not optional: an M dwarf radiates most
of its light in the infrared, so its visual magnitude understates it more
than tenfold, and M dwarfs are what most nearby planet hosts are.
Luminosity and the semi-major axis then give an equilibrium temperature,
mass and radius give a bulk density, and size, temperature and density
together give a class of world.

Checked against the solar system the temperatures land on Earth 255 K,
Jupiter 112 K, Neptune 46 K, all within a kelvin or two of published
values, and 51 Pegasi b comes out at 1227 K against a published 1200.

Each class carries a palette reasoned from its chemistry — methane absorbs
red light, which is why the ice giants are blue — and a structure: zonal
bands for a body with a fluid envelope, because a rapidly rotating
atmosphere organises into them, and fractal terrain for one with a solid
surface. Polar caps grow and shrink with the derived temperature, which is
the clearest visible consequence of the whole chain.

The generator samples three-dimensional noise along the sphere rather than
a flat field, so there is no seam to stitch at the antimeridian and no
pinching at the poles, and it writes into a byte array rather than a
canvas — a pure function, testable, with no 2D context to be unavailable.

Two things the derivation cannot do, both stated on screen next to the
measurements it rests on. Equilibrium temperature ignores greenhouse
warming and internal heat, so Venus comes out at 300 K against a real
surface of 737 K and Io, kept molten by tides, classifies as ice. And
these are illustrations: reasoned, but not observations.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaySiNst4HhDXBHnMy8p5G
2026-08-05 06:52:22 +00:00
Claude a84e2d3a69 Put a reference grid under the system view
A system was a handful of ellipses floating in the dark. You could see
that one orbit was bigger than another, but not how big, and not that a
planet sat above or below the plane the others share.

Adds the same plane-and-tether reading aid the outer scales got: a polar
grid in the system's own reference plane, with a drop line from each body
onto it.

Ring radii snap to a 1-2-5 ladder rather than dividing the system evenly,
because the point is to put a number on a distance — 5, 10, 15 AU can be
read at a glance and 4.34, 8.68, 13.02 cannot. That holds across the four
orders of magnitude real systems span: the solar system gets 5 AU rings,
TRAPPIST-1 gets 0.01 AU ones. The outermost ring encloses the outermost
orbit rather than falling just inside it.

The rings are dashed. Solid ones would sit in the same plane as the orbit
ellipses, which are themselves rings, and at a glance a reference circle
and a circular orbit are the same picture. Dashes are cut by dropping
whole segments rather than by a dashed material: the ring is already built
from independent segment pairs, so a material's dash pattern would restart
at every one.

Drawing the grid exposed a framing bug it made unmissable. The camera
settled along one fixed direction derived from the ecliptic, which is
face-on only for the one system whose elements are ecliptic. Every
exoplanet system — measured against the plane of the sky, perpendicular to
the line of sight to its own host star — was being presented nearly
edge-on, a smear of overlapping ellipses. The settle direction is now
taken relative to whichever plane the system was measured in, so all of
them read as discs. The solar system is unmoved, which a test pins.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaySiNst4HhDXBHnMy8p5G
2026-08-04 20:32:31 +00:00
Claude 2e525fb5c3 Open the map out to the whole Milky Way
The map stopped at the catalogued 50 pc around the Sun — 0.33% of the
Galaxy's width — and looked like a point cloud with a search box.

Adds the galactic scale above it and the heads-up display the reference
map is built from.

The Galaxy is not a third coordinate space. It is the same parsec space
four orders of magnitude further out, so the model and the star field
crossfade against camera distance instead of switching, and the Sun stays
where it really is: 8.18 kpc out, on the Orion Spur, between the
Sagittarius and Perseus arms. The depth range scales with that distance —
one fixed near/far pair cannot both fly into a star and hold the Galaxy.

The structure in shared/astro/galaxy.ts is measured: the directions of the
centre and the north galactic pole, which fix the disc's 63 degree tilt
against the celestial equator; the Sun's galactocentric distance; and a
radius, azimuth and pitch angle per arm. The particles scattered around it
are not, and cannot be — dust hides the disc, so no catalogue holds the
Galaxy's stars. The view says so, and the model fades out before the
camera reaches the 50 pc where the real stars are.

The rest is the look: polar grids lying in the galactic plane with drop
lines from the Sun's neighbours, a scale ladder, a readout panel, range,
reticle and frame brackets. Two things had to give way for it. The
deep-sky shell is the sky as seen from here, so it dissolves rather than
letting the camera fly through a wall of nebulae, and so does the skybox,
which is a photograph taken from inside the thing now being viewed from
outside. Labels are picked by screen separation rather than distance
alone: the Sun's fifteen nearest neighbours are all inside four parsecs
and printed as one unreadable clump.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaySiNst4HhDXBHnMy8p5G
2026-08-04 19:59:01 +00:00
Claude 2f45fa7fef Measure exoplanet inclination from the plane of the sky
The Exoplanet Archive measures orbital inclination from the plane of the sky —
the plane perpendicular to our line of sight to the host star. Ninety degrees
means edge-on as seen from Earth, which is why transiting planets pile up there:
1643 of the 2061 published inclinations are within five degrees of 90. The
renderer fed that straight into a propagator that reads inclination as an angle
from the reference plane, so every transiting system was tilted against a plane
its inclination was never measured against.

Each body's elements are now rotated out of their own reference plane into the
scene by a per-body quaternion. Solar-system elements keep the ecliptic rotation
from the previous commit. Exoplanets get a rotation carrying the elements' +Z
onto the line of sight to their host, which is exactly the star's own position —
so an inclination of i means the orbit's normal sits i from our line of sight,
which is the definition.

The rotation about that axis is the node's position angle on the sky. The
archive does not publish it and the ETL does not request it, so the shortest arc
is used: deterministic, and no less arbitrary than anything else given no data.
Planets with no published inclination default to face-on, which is the honest
reading of an unconstrained orbit rather than a guess at a tilt.

Unifying this replaced the direct eclipticToEquatorial call in the renderer, so
solar-system bodies and moons come out exactly where they did before — verified
against Sol side by side.

Tests: 253 passing, up from 247. The strongest one is the definition itself: a
90-degree planet must pass through our line of sight to the star, which is what
a transit is. One test of mine had to be corrected rather than the code — it
asserted that two systems at the same inclination must occupy different planes,
which is not guaranteed once the node angle is arbitrary, while each still sits
at the correct angle to its own host.

Note the e2e camera-flight test flaked once under parallel load during this
work, then passed in isolation and on two further full runs. Its click-until-
entered poll has a fixed 15s budget that a loaded machine can exceed; that is
pre-existing and unrelated to this change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaySiNst4HhDXBHnMy8p5G
2026-08-04 18:05:07 +00:00
Claude 2293585940 Put orbits and stars in the same reference frame
The app's two sources disagree about which frame they are in, and nothing
reconciled them. HYG star positions are equatorial J2000 — that is what
raDecDistanceToXyz produces and what the galaxy view renders directly. Orbital
elements come from JPL Horizons, whose default reference plane for element
output is the ecliptic, and the ETL never overrides it. The two are tilted 23.4
degrees apart, so the orbits sat that far off the sky they are drawn against.

Confirmed rather than assumed, from both ends: the Horizons request in
lib/horizons.ts sets no REF_PLANE, and the resulting solar-system inclinations
are 0 to 17 degrees with Earth exactly 0.00 — which is only true of the ecliptic,
since Earth's orbit defines it.

eclipticToEquatorial now rotates orbit positions into the scene frame, so a
direction means the same thing in the galaxy view and the system view. The
rotation is about the vernal-equinox axis, which both frames share.

That exposed a presentation problem the old code had been hiding. The renderer
mapped the propagator's z straight onto the scene's vertical, which silently
redefined the frame but did make systems render flat. In a properly equatorial
scene, orbital planes lie 23.4 degrees off the scene's own axes, so a system
would be presented edge-on. Rather than rotate the world back into a comfortable
pose — which would only put the orbits at odds with the sky again — the camera
now settles relative to the orbital plane: a three-quarter view about 37 degrees
off the ecliptic normal. The arrival still begins along the approach direction
and swings round as it settles, so the transition stays continuous, and the
framing is now the same every time rather than inherited from wherever the
camera happened to be.

Tests: 247 passing, up from 237. The frame tests are the discriminating kind —
Earth's orbit must lie perpendicular to the ecliptic pole rather than to the
scene's vertical, and must reach 23.4 degrees of declination a quarter orbit on,
where it used to read zero. Verified in a browser against Sol and Gl 357.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaySiNst4HhDXBHnMy8p5G
2026-08-04 16:10:17 +00:00
Claude c1feb4b74e Rewrite the star field as instanced billboards
Plan step 3 promises glow and size driven by magnitude and spectral type, but
the star field was a THREE.Points cloud and the WebGPU backend — the renderer
this app targets — caps point primitives at a single pixel. Every one of the
8750 stars drew as an identical 1 px dot with a hard edge, discarding the
magnitude sizing entirely; the class comment already admitted sizeNode only did
anything on the WebGL2 fallback.

Each star is now an instanced camera-facing quad on a SpriteNodeMaterial, which
behaves the same on both backends. That material takes each instance's centre
from positionNode rather than from an instance matrix, so position, colour and
size ride on instanced buffer attributes and the mesh itself never moves. A
radial falloff in opacityNode gives each star a bright core inside a soft halo.

Sizes are angular rather than world-space. That keeps a star the same apparent
size at any camera distance, which is both what the old screen-space points did
and what is physically right: real stars are unresolvable point sources, so
apparent size follows brightness, not distance. World-space quads would instead
have made the whole field vanish at the camera's 2000 pc limit.

Picking had to be rebuilt. Billboarding happens in the vertex shader, so the
CPU-side geometry is one quad at the origin and Raycaster cannot see the star
field at all. Selection is now done in screen space against the size each star
is actually drawn at, which is strictly better than the fixed 1.2 pc world
radius it replaces — that radius was over-permissive up close and sub-pixel at
the far end of a 4000x camera range. Stars behind the camera need an explicit
depth guard, because project() mirrors them back onto the screen.

Two things only caught by running it. The colour attribute was declared with
node type 'color', which is not a GLSL type, so the generated shader failed to
compile — it has to be vec3. And the click tolerance was first written as a
floor on the drawn radius, which flattened every star to one hit size, since a
floor generous enough for the faintest star exceeds the brightest star's radius;
adding the slop instead keeps a brighter star the easier target.

Tests: 151 passing, up from 145. Verified in a real browser — shaders compile
clean and the Playwright click-to-select flight passes against the new picking.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaySiNst4HhDXBHnMy8p5G
2026-08-04 11:11:23 +00:00
Claude 3a859360ba Add the deep-sky backdrop, the last unbuilt piece of the plan
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
2026-08-03 15:19:39 +00:00
Senrokai d7e8ea1d4d @
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>
@
2026-08-03 16:50:10 +02:00
Senrokai 1e1b58b0e9 Initial commit 2026-07-15 16:54:27 +02:00