Commit Graph
295 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 0f659af5b3 Pin the M giants' corrections from M4 on, a colour off the table, and a phone held sideways
Three rules the suite passed without, each a test only:

- ef20991 credits its M-giant corrections with M4 going from 0.82 to 1.00 of van Belle's measured
  radii and M5 from 0.71 to 1.00, but only its M6 entry was tested. Reverting M0-M5.5 to the
  dwarf's correction, the M4 or M5 entry to the dwarf's, or the M7.5 entry to M6's, all passed; so
  did reading the table's first entry past its end, which six M8 III stars in the catalogue take
  (28 times too faint, 5.3 times too small). stellar.spec now draws three of van Belle's own
  giants, HD 118669 (M4), HD 104207 (M5) and HIP 68357 (M7), from their median Johnson V, parallax
  and radius in his tables 6 and 4, none behind more than 0.02 mag of dust. They come out at 1.00,
  1.00 and 0.91 of their measured 103.1, 103.2 and 173.3 R; the window is 15 %, since the
  dwarf's correction at M4 only moves the radius by 18 %. And M8 III takes M7.5 III's correction.
- 6a45c91 says a derived radius came "from its type" for a dwarf whose colour the table does not
  read, but no case had one. star-readouts.spec adds HD 49748, G5 V at B-V -0.32; 26 cards in the
  catalogue read this way.
- The phone framing test used a portrait canvas, where the width is the shorter side. A landscape
  one, 844x390, now expects the camera at the framing the 390 px side gives.

Guarded mutants, each failing only the test named, 1 of 837: M0-M5.5 on the dwarf's correction
(`index < 72`); M4 entry -2.2 -> -1.78; M5 entry -2.68 -> -2.025; M7.5 entry -4.86 -> -3.94; past
the end, the first entry (all "draws van Belle's own M4, M5 and M7 giants..."); any colour counted
as read ("says a radius was derived, and from what..."); shorterSidePx the canvas width ("frames
it by the shorter side on a phone held sideways too").

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 21:45:46 +02:00
SenrokaiandClaude Opus 5.5 74913d844d Place a naked-eye star by SIMBAD's name only within a magnitude of its source, and hold HD 45951 to it
e7c420b placed two naked-eye HYG rows along the Gaia source SIMBAD names them as. One of them,
θ¹ Ori A (HD 37020, HYG 26155), came with HYG's V 4.98 and O7, which are Hipparcos's for it and a
companion together: SIMBAD has θ¹ Ori A at V 6.73 and B0V, and its source at G 6.63. At 378 pc the
map drew it at 2.7e4 L and 37 100 K, brighter than θ¹ Ori C beside it, and counted it naked-eye.
The lookup by position already refuses a source more than a magnitude off HYG's V (it refused
this one); the lookup by name now does too. At its own magnitude and 378 pc the map does not keep
the star, as it keeps none that faint past 250 pc: 455 519 -> 455 518 stars, naked-eye 8 899 ->
8 898, of them past 250 pc 1 664 -> 1 663. HD 45951 (V 6.20 against G 5.90) is placed as before.

Nothing checked where HD 45951 was placed, only that its name was there. Along HYG's direction,
31.7' out, every check passed with the star drawn twice: under its name, and as its bare Gaia
designation. validateStars now requires the star named HD 45951 to carry that source's
designation, and requires HYG 26155 to be absent. The MIN_NAKED_EYE_STARS comment gives the new
counts and says why θ¹ Ori A is left out.

ETL mutants, each in a throwaway worktree with a copy of the cache, reached validation and failed
with the expected message:
- HD 45951 placed along HYG's x/y/z: "HD 45951 is not on Gaia DR3 3369454521490604416, the source
  SIMBAD names it as"
- the magnitude check dropped from the lookup by name: "θ¹ Ori A is drawn in the V and type of
  Hipparcos's blend of it with a companion."

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 21:40:06 +02:00
SenrokaiandClaude Opus 5.5 98082a0460 Keep Gaia's colour on a star HYG describes without one
combine took the described entry's colour whole, null included. So a HYG row with no B-V, merged
into its Gaia source, lost the BP-RP Gaia measured for it: HD 45951, HD 45291 and HD 124953, three
naked-eye giants Gaia has at BP-RP 1.25, 1.19 and 0.37, showed no colour on their card. HD 45951's
point had its colour at 280159d, as a bare Gaia designation.

combine now takes the colour, in its own system, from the other entry where the description has
none. The fallback the review proposed, `described.colorIndex ?? other.colorIndex`, reads the same
empty row here, since the Gaia entry is inserted first and so is `kept`, and the HYG row is both
`other` and `described`; it is one of the mutants below. The fold into a source whose HYG star SIMBAD
puts elsewhere keeps the Gliese row's own colour or none, not that other star's.

Published catalogue: 931 stars gain a Gaia BP-RP, all HYG-described entries on a Gaia source with
a V magnitude. 298 of them had neither type nor colour and are now drawn with a temperature and a
radius. 621 dwarfs read their temperature off that colour rather than their type's row, 290 of them
by more than 500 K. The colour is the one every other dwarf on the map is read from, and it agrees
with the brightness where the type does not: HIP 1546, "K:" at V 12.46 and 33.5 pc (M_V 9.8), was
5 270 K and 0.126 R, and is 3 732 K and 0.42 R at BP-RP 2.01. The three giants keep the
temperature and radius their type gives. Star count and every ETL figure unchanged.

Guarded mutants: no fallback, and the review's fallback to `other`, each fail only "keeps Gaia's
colour where the description has none"; the misplaced fold falling back to the other star's colour
fails only "describes the entry by the Gliese row where SIMBAD names the HYG star already there as
another source" (1 of 837 each).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 21:28:29 +02:00
SenrokaiandClaude Opus 5.5 5620776601 Describe a folded Gliese star by its own row where SIMBAD puts the HYG star already there elsewhere
e7c420b folded a Gliese-only row into a Gaia entry a HYG star already described whenever the two V
agreed within 0.5, and kept that HYG star's description. HYG hangs "Gl 905.2A", M5, V 13.11,
B-V 1.55 on HIP 117059, which SIMBAD names as LAWD 93: Gl 905.2B, a DA white dwarf, Gaia DR3
2871730307948650368. Gl 905.2A is G 130-6, another source 3' away with no parallax. So the fold
dropped the white dwarf's own row (DA4, V 12.90, B-V 0.15) and drew its source as a 3 384 K M5
dwarf of 0.292 solar radii.

foldByIdentity now looks up the SIMBAD designation of the HYG star describing the target. Where it
names another source, the Gliese row is the star on this one, and its description, photometry
included, replaces that one (combine takes the described entry as an argument). Five of the 63
folds are of this kind; decoded from the published catalogue, before -> after:

- Gl 905.2A [116690] M5 V 13.11, 3 384 K, 0.292 R -> Gl 905.2B [119589] DA4 V 12.90, 8 175 K, 0.022 R
- GJ 9490A [71678] M0 -> GJ 9490C [118981] K5, the type SIMBAD gives BD+20 3009 (same V and B-V)
- HD 40887 [28371] V 7.85 -> Gl 225.2C [118408] V 8.30, on the source SIMBAD names GJ 225.2 C
- Tau Oph [88131] F5V+ V 4.77 (the pair's light) -> Tau Oph [119195] dF3 V 5.24, GJ 700.1 A's
- HD 65277 [38820] "Gl 293.1B" -> HD 65277 [118511] Gl 293.1A, V 8.06, on GJ 293.1 A's source

Nothing else changes: 455 519 stars, 63 folds, 0 Gliese rows beside their source, no planet
hosted on any of the five ids. The HD 40887 name leaves the map, SIMBAD putting HD 40887 AB on
the source HYG labels Gl 225.2B.

The tolerance's doc said the ten such pairs agree within 0.12. There are 14; 11 do, and the three
that differ by 0.21, 0.45 and 0.47 are three of these five.

Guarded mutants, each failing only "describes the entry by the Gliese row where SIMBAD names the
HYG star already there as another source" (1 of 834): the condition never true; the condition
inverted to describer === designation.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 21:18:13 +02:00
SenrokaiandClaude Opus 5.5 e7c420b296 Fold a Gliese star into its Gaia source in Gaia's photometry, wherever HYG already put it, and place two naked-eye stars by SIMBAD's name for them
The Gliese fold (030447b) kept HYG's magnitude, band and colour over the Gaia entry's, putting
CNS3's V at Gaia's distance. Of the 49 folded, 15 lost a measured BP-RP and 6 their temperature and
radius (Gl 700.1C, Gl 632.2B, GJ 9800B, Gl 734B, Gl 323B, GJ 3605), and GJ 4285, V 11.45 where
SIMBAD has G 13.05, was drawn five times too luminous. A folded star now keeps the photometry of the
entry already there; HYG's name, id and type still come with the fold. Published catalogue, before
-> after: GJ 4285 V 11.45, 3 850 K, 0.491 R☉, 4.76e-2 L☉ -> G 13.05, BP-RP 2.74, 3 291 K, 0.297 R☉,
9.29e-3 L☉; GJ 3207 0.574 -> 0.278 R☉; Gl 700.1C no temperature, 4.54 L☉ -> 5 155 K, 1.214 R☉,
0.937 L☉; Gl 632.2B -> 9 044 K, 0.018 R☉; Gl 734B -> 3 360 K, 0.381 R☉.

The fold also looked its target up by name, so it saw only Gaia entries still bare. Where a
Hipparcos row of HYG's had already taken the source, the Gliese-only row of the same star stayed
beside it and the check counted none: Gl 251 at 5.76 pc beside HD 265866 (the host of GJ 251 b and
c) at 5.58, Gl 422 beside HD 304043. Gaia entries now carry their DR3 designation through the merge
(ETL only; the assets do not store it) and the fold looks it up; into an entry a HYG star already
describes it folds only where the two V agree within 0.5, so a companion SIMBAD gives its primary's
source stays. 14 more rows fold, 63 in all, each within 0.47 of the star it joins: Gl 251, Gl 422,
Gl 162, Gl 794, Gl 225.2C, Gl 905.2B, GJ 3232, GJ 4046, GJ 9490C, GJ 9608, 69 Tau Oph (Gl 700.1A)
and the second rows of HD 65277, HD 120237 and HD 336196. Stars within 10 pc 367 -> 366, within
25 pc 5 479 -> 5 468; distances without an error 346 -> 332; HYG survivors 11 478 -> 11 465.

validateMerge's count of Gliese rows beside their source took SIMBAD's map from the function the
fold takes it from, so a slip in the map turned both off: with it empty, GJ 2097 and GJ 4285 were
back inside 10 pc and the ETL passed. It now also asks for GJ 2097 and GJ 4285 beyond 20 pc and for
no Gl 251 or Gl 422 by name, and glieseGaiaDesignations refuses fewer than 3 200 HYG rows matched to
SIMBAD (3 352 of HYG's 3 801 measured).

4d47896 left 22 naked-eye HYG rows out as having no distance anywhere; two had one. HD 45951 (K0 III,
V 6.2) was on the map only as "Gaia DR3 3369454521490604416", HYG's declination being 31.7' out,
and θ¹ Ori A (HD 37020, HIP 26220) sits 0.1" from Gaia DR3 3017364132050194688, 2.643 ± 0.072 mas,
which the magnitude test refused because HYG gives V 4.98 against G 6.63. One cached SIMBAD query
now names the Gaia DR3 source of each naked-eye HYG row with no distance (199 of 206 HD numbers),
and a row nothing else places takes that source from the bright list, at its position: HD 45951 at
112.0 pc, under its name, and θ¹ Ori A at 378.4 pc. HD 45951 joins the required names. The twenty
still left out are named in build.ts; none has a bright source with a parallax five times its error
within a minute of arc nor under its SIMBAD name. Naked-eye stars 8 898 -> 8 899; 455 532 -> 455 519
stars.

The fold and the identity lookup are tested in star-merge.spec.ts. Guarded mutants (1 of 833
failed unless noted):
- the Gliese row's photometry kept: caught by "...in Gaia's photometry" (and the Hipparcos-row fold test, 2 failed)
- combine dropping the designation: caught by "folds one into a Gaia entry a HYG star of the same brightness..."
- folding whatever the brightness; tolerance 2 magnitudes: caught by "leaves a star with a Hipparcos error alone..."
- folding into bare entries only: caught by the Hipparcos-row fold test (and the leave-alone test, 2 failed)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 19:55:25 +02:00
SenrokaiandClaude Opus 5.5 c8d1bbb879 Hold st_lum's conversion to two hosts' luminosities, and mu2 Sco b to its host
8d59c72 checked only that every host luminosity was positive, which catches st_lum stored
unconverted and nothing else: converted as 10^-x, Proxima Cen b came out 662 L☉ and TRAPPIST-1 e
1 808, and as e^x Proxima 0.0595, 39 times too bright, and in both runs the ETL completed with
every check passing. The value is printed as measured on host cards and warms their planets.
validateExoplanets now asks two hosts for the luminosity their st_lum gives, within 10 %: Proxima
Cen b's, st_lum -2.821, 1.51e-3 L☉ (Ribas et al. 2017 measure 0.00151), and HD 97048 b's, st_lum
+1.602, 40.0, so a sign slip fails on both sides of the Sun. The published file has 1.5100e-3 and
40.000.

5fb0d45 placed mu2 Sco b's host by its parallax, the archive giving no sy_dist, and only the helper
was tested: with the parallax dropped at the call site the ETL still passed, 6 327 of 6 354 hosted
against a floor of 99.5 %. mu2 Sco b must now have a host.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 19:54:48 +02:00
SenrokaiandClaude Opus 5.5 ef2099177a Give M giants their own bolometric correction, which from M4 on is not a dwarf's at their temperature
aafc788 held the M6-M7 giants at van Belle's 3 134 K but kept the dwarf sequence's correction at that
temperature, -2.76, so they were still drawn too small: RZ Ari 81.5 R☉ where its CHARM2 diameter of
10.30 mas gives 119 at its distance, EU Del 72.6 where 9.90 mas gives 126, 30 Her 126 against 177.
The comment called the M6 sizes corrected; they were not.

From van Belle et al. (2021) themselves, m_bol from their table 4 bolometric fluxes (IAU 2015 zero
point) less their dereddened Johnson V (table 6, median of each star's values), the giants' BC_V
by type is within 0.1 of the dwarf sequence's at the same temperature down to M3, and then parts
from it as TiO takes the V light: -2.20 against -1.78 at M4 (31 stars), -2.68 against -2.03 at M5
(15), -3.34 at M5.5 (7), -3.94 against -2.76 at M6 (3), -4.86 at M7-M7.75 (4). From M0 an M giant
now takes those medians, linear between types and held past the ends; G and K giants keep the
dwarf's at their temperature.

Measured, drawn radius over van Belle's own measured one, median by type, before -> after:
M0 0.96 -> 0.96, M2 0.98 -> 1.00, M3 0.94 -> 0.98, M4 0.82 -> 1.00, M5 0.71 -> 1.00,
M6 0.66 -> 1.13, M7 0.38 -> 0.94. In the catalogue, against CHARM2 diameters at the app's own
distances: RZ Ari 81.5 -> 140.3 R☉ (measured 119.3, ratio 1.18), EU Del 72.6 -> 124.9 (126.3,
0.99), 30 Her 126.0 -> 216.9 (177.5, 1.22), Eps Oct 92.6 -> 159.3 (110.4 from a K-band disc of
±13 %, 1.44); Mirach 82.6 -> 83.1, Betelgeuse 538.8 -> 551.0, Antares 409.7 -> 414.0. The M6 value
rests on three stars and both 30 Her and Eps Oct are semiregular variables, so the late-M sizes are
good to a quarter, no better. 312 stars' corrections move by more than 0.05 mag.

Guarded mutants, each caught by "draws an M6 giant at the radius its measured diameter gives..."
alone (1 of 833 failed): M giants back on the dwarf's correction; M6 entry at -2.76; interpolation
taken from the wrong end.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 19:54:26 +02:00
SenrokaiandClaude Opus 5.5 0a88797ff4 Keep the dock's tabs in sight around the date strip, on phones and narrow desktop windows
67a21b4 let the dock's tabs give way to the date and range strips and
scroll, and measured phones only after Go. Reviewers found four things it
did not measure:
- At the present, the state the map opens in, the Range strip, now 121 px
  and unshrinkable, took the tabs' width on a portrait phone: Display showed
  0 of 79 px at 360, 390 and 412 wide, and Bookmarks 0, 27 and 49 of 95.
  Before, the strip sat off screen at x 406-502. It is now hidden below sm.
- After Go on a phone, focus went back to the Display tab while the list
  was still wide; the strip then narrowed it and its scroll stayed, so the
  focused tab was 0 of 79 px in sight (a focus ring off screen), and on
  Saturn's page the Clock tab 28 of 63. The focused tab, else the selected
  one, is now scrolled into view after each render in which the strip comes
  or goes (afterRenderEffect on whether a date is shown).
- On desktop windows 600 to 770 px wide the tab list drew the browser's
  classic scrollbar, light and 15 px tall, in the dark dock, and at 640 the
  open Display panel's tab was scrolled out of sight. The list now asks for
  a thin dark one (scheme-dark, scrollbar-width: thin); the tab is brought
  back as above.
- The range strip's shrink-0 had no test: without it '417 AU' wraps and the
  row grows from 38 to 58 px.

Measured on :4301 in the Sun's system, keyboard only (Tab to Display,
Enter, Tab to the date, 2020-12-21T18:00, Enter), visible px of each tab:
- 360x640 phone, at the present: Bookmarks 95/95, Display 24/79 (was 0),
  range hidden. After Go: the focused Display 78/79 (was 0), the date on
  screen, no page scroll. 390x844: 54/79 then 78/79. 412x915: 76/79 then
  78/79.
- Saturn's page at 360x640, after Go: the focused Clock tab 63/63.
- 600x800 desktop: every tab in full before and after Go, no scrollbar.
- 640x900, 700x900, 768x1024 desktop, after Go: the open Display tab 87/87,
  a dark scrollbar 10 px tall (row 43 px, was 48 with a light one).
- 1400x900: unchanged, row 38 px.
With no strip at all, a phone's row is now 33 px tall at the present and
38 once a date is set; the off-screen range strip used to hold it at 38.

Tests: 'keeps the date strip on screen on a phone' now also sets a range
and expects it shrink-0 and max-sm:hidden, and the list scheme-dark and a
thin scrollbar; 'brings the tab that matters back into view once the date
strip has narrowed the tabs' checks the focused tab on a phone and the
selected tab on a wide screen. Guarded mutants, each failing its named test
alone: the range's shrink-0 dropped, the range shown on a phone, the
default scrollbar, no tab scrolled into view, the selected tab only, and
the effect not keyed on the strip. Unit suite 870 passed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 19:44:59 +02:00
SenrokaiandClaude Opus 5.5 6a45c914ac Say a derived radius came from the star's type where no colour went into its temperature
The card said every derived radius came "from colour and brightness". Since 206e88a a star with no
colour the table reads is placed at its type's row, and a giant's temperature is always its type's
(giantSurface): on the published catalogue that is 844 stars with no colour at all, GJ 3655 (M8)
among them, and 10 702 giants with one, all labelled "from colour". temperatureFromColour, beside
effectiveTemperatureK, says which path the temperature took, and the card now reads "from its type
and brightness" where it was the type, and "from its temperature and brightness" for the archive
hosts whose colour was itself read off st_teff.

Guarded mutants, each caught by "says a radius was derived, and from what" alone (1 of 831 failed):
- the basis always "colour"
- giants not excluded in temperatureFromColour
- colorFromTemperature ignored

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 19:42:37 +02:00
SenrokaiandClaude Opus 5.5 c0ed91dcf9 Pin three rules the suite passed without: the first planet's temperature, the one-step error floor, and G carried to V by type
publishedTemperaturesK keeps each host's first st_teff so the star field tints it as starSurfaceOf
draws its disc; the only test used Proxima, with one planet, and letting the last row win passed
all 828 tests while 189 hosts in the catalogue would be tinted apart from their disc (193 hosts give
their planets differing st_teff, 30 by more than 200 K). The new test gives one host three rows,
none, 4 094 and 3 640 K, and asks both functions for the same 4 094.

The error column's at-least-one-step floor was tested with an error of 1e-6, which at 255 steps
rounded to none but at f31ffe1's 65 535 rounds to 66, so dropping the floor passed. The fixture is
now 1e-12, 0.07 of a step.

206e88a carries a G magnitude to V at the type's G-V where a star has no colour, and no test held
it: the only type-only case was in V. The new case measures GJ 3655 in G, 3.11 brighter at M8, and
asks for the V case's luminosity; taken as V it came out 17 times as luminous. Oph 11 (M9, G 18.91)
is the one published star on that path.

Guarded mutants, each caught by its named test alone (1 of 831 failed):
- "hostStarTemperatureK && !temperatures.has(hostStarId)" -> "hostStarTemperatureK"
- the Math.max(1, ...) floor removed
- "(sequence?.gMinusV ?? 0)" -> "(sequenceAtColour(star)?.gMinusV ?? 0)"

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 19:42:37 +02:00
SenrokaiandClaude Opus 5.5 ee168996f7 Test the disc's limb law by working it out, and the phone framing through the scene
The disc test walked the material's node graph and asserted only that the constant 0.6 was in it,
so any law that kept the literal passed: a limb 1.6 times brighter than the centre, a reversed mu,
mu held at 1, all 828 tests green. It now evaluates the factor the photograph is multiplied by off
the graph itself, at a given cosine between normal and line of sight, knowing only +, -, *, clamp,
dot, oneMinus and negate and failing on anything else, and asks for the Sun's linear law: 1 at the
centre, 0.7 at mu 0.5, 0.4 at the limb and 0.4 past the silhouette. The dot must be of normalView
and positionViewDirection.

The phone framing (e62e2fb) depends on the scene passing the canvas's shorter side, and the test
canvas had no size, so removing that argument left every test passing. A new test gives the canvas
390x844, enters Antares and asks for the camera where systemFramingDistanceAu puts it with
shorterSidePx 390, 1.5 times or more further out than without it.

Guarded mutants, each caught by its named test alone (1 of 831 failed):
- limb 1 + 0.6 mu; mu reversed with oneMinus; mu clamped to 1; negated law (limb 1.6x);
  clamp removed: "...darkened towards the limb".
- shorterSidePx dropped; shorterSidePx times 0: "frames a supergiant on a phone...".

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 19:42:37 +02:00
SenrokaiandClaude Opus 5.5 4951fd8657 Test that a body page opens the next body on its Sun's side and turns an exoplanet for show
Two behaviours of the body page had no test. Showing a body resets the
remembered side of the equator (sunSide = 0), so the next body opens on its
own Sun's side wherever the reader left the camera; the only test that
switched bodies went from Saturn's June Sun (south) to Earth's (north),
which moves the camera with or without the reset. And the page turns an
exoplanet 0.08 radians a second for show, which no test opened.

- 'opens the next body shown on its own Sun's side, wherever the reader
  left the camera: Earth after Saturn in December': Saturn on 2032-12-01,
  the camera taken north, then Earth, whose December Sun is south too.
  Guarded mutant, the reset replaced by void 0: this test fails alone,
  "expected 0.5999999999999999 to be less than 0".
- 'turns an exoplanet slowly for show, clock or no clock': the fake loader
  now carries one exoplanet round the Sun's record; one second with the
  clock standing turns it 0.08 radians. Guarded mutant, the turn replaced
  by void deltaSeconds: this test fails alone.

The template comment said no one has measured an exoplanet's day. The app
ships two whose spin has been: 2M1207 b turns in 10.7 +1.2/-0.6 hours
(Zhou et al. 2016, ApJ 818, 176) and beta Pic b's lines are broadened by
25 +/- 3 km/s (Snellen et al. 2014, Nature 509, 63). It now says the
catalogue does not carry the day, which is what ExoplanetRecord holds.
Unit suite 869 passed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 19:34:15 +02:00
SenrokaiandClaude Opus 5.5 d5a5a0fc3a Move a body page's camera across the equator only once the Sun is 3 degrees past it
a9e910c mirrored the camera whenever the Sun's height above the body's
equator changed sign. The side is chosen for Saturn's rings, whose Sun goes
26.7 degrees either side, but the rule held for every body: Mercury's Sun,
never more than 0.034 degrees off its equator, crosses it 8.3 times a year,
so at a month a second the camera went from one side to the other every
1.45 seconds, mirroring wherever the reader had orbited it, with both sides
lit alike. Venus's Sun reaches 2.6 degrees and the Moon's 1.6.

The side is still chosen whenever a body is shown, but afterwards it changes
only when the Sun is more than 3 degrees across (SUN_SIDE_MIN_SINE, on the
sine of its latitude). Measured in the app on :4301, camera side changes
while the clock ran at a month a second:
- Mercury, 12 s from 2026-10-20: 0 (the Sun at most 0.0339 degrees off).
- Venus, 12 s: 0 (2.64). The Moon, 15 s: 0 (1.58).
- Earth, 12 s from 2025: 2, on 2025-03-28 and 2025-09-30, a week after
  each equinox.
- Saturn, 20 s from 2038-06: 1, on 2039-08-03, half a year after its
  equinox (it followed at 2039-01-19 before).
Reviewers had measured 7 or 8 changes in 10 to 12 s on Mercury's page, 3 on
Venus's and 1 or 2 on the Moon's.

Test: 'leaves the camera on its side while the Sun only grazes the equator:
Mercury through two crossings' steps Mercury's page a day at a time from
2026-10-20 over 60 days, through the Sun's crossings about 1 November and
6 December, and expects the camera south throughout. Guarded mutants, each
failing that test alone: the dead band removed (the sign alone chooses),
and the dead band applied to a body just shown too (Mercury then opens with
the camera on the side away from its Sun). The Saturn tests (2032 and past
the 2039 equinox, set to 2045) pass unchanged. Unit suite 867 passed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 19:33:04 +02:00
SenrokaiandClaude Opus 5.5 69cada7914 Fail the ETL when re-rating a locked moon moves its pole or W off the kernel's at the present
lockedToOrbit re-rates a locked moon's W and node terms and moves their
constants so that on 2025-01-01, the date the IAU's elements were fitted
near, the pole and W are the kernel's own. Nothing checked it: with the
constants left where they were, the solar ETL passed and the suite passed,
though every re-rated moon moved (Rhea's pole 0.018 degrees, Triton 0.0054,
Miranda 0.0037, Europa 0.0027, Callisto 0.0021, Deimos 0.0016, Ganymede
0.0016, measured on the bodies.json it wrote; before the drawn node rates
were corrected, Mimas's W moved 0.21 and Miranda's pole 0.12).

lockedToOrbit now compares orientationAt at PRESENT_JD before and after,
Iapetus's pole round its orbit included, and throws past 1e-6 degrees.
Measured on the shipped catalogue: at most 4.7e-10 (Deimos's W, some 2.6
million degrees round), the pole exactly.

Guarded mutants, each through the solar ETL on the real catalogue:
- node terms' constants not moved: "Deimos's pole or W on 2460676.5 is
  1.60e-3 degrees from the IAU's".
- W's constant not moved: Deimos, 9.12e-2.
- the check disabled with the first: the ETL passes (control).
The unit suite does not see it: the check runs on the kernel, which only
the ETL reads. The ETL writes the same bodies.json as before. Unit suite
866 passed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 19:24:49 +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 4c1e19d635 Give the disc sizes the framing quotes as radii, and the meta columns in the order they are stored
The framing rule works in radii: at 390x844 a giant's disc may take 0.74 - 90/195 = 0.278 of the
195 px half-side, 54 px from the centre, and before e62e2fb it took half of it, 98 px. The comment
and the spec called the 98 px a disc, which reads as a width, and a name 70 px from the centre
would then have been off it. e62e2fb's message says "54 px wide" and "98 px wide" for the same
radii: the discs are about 108 and 195 px across.

The stars-meta.bin doc comment still put the photometry byte before the distance error, which
f31ffe1 moved ahead of it for alignment; metaColumns, the DISTANCE_ERROR_STEPS comment and the
README already had the new order.

The scene's 2 883 hosts past the survey edge are all hosts past it: 2 878 are the archive's own
stars and 5 are HYG's (HD 81817, HD 102272, HD 158996, HD 208527, HD 220074).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 19:17:20 +02:00
SenrokaiandClaude Opus 5.5 280159dcd2 Bring the figures the comments quote back to what the catalogue now holds
Later commits on this branch (0e9ab6f, ff744a0, 8c3a860, and this round's fold, positional lookup
and parallax fallback) changed counts that comments in build.ts, star-readouts.ts, star-catalog.ts
and fetchExoplanets.ts still quoted as "measured" or "today". Each is now the value measured on the
published catalogue after an ETL run from cache (decoded with decodeStarCatalog, or from the
validator log where it prints them):

- distances without a published error 439 -> 346, the Gliese ones 357 -> 264 (82 archive);
- parallax distances ranged 1 109 -> 1 116; HYG stars at Gaia's distance 8 129 -> 8 105, and the
  Gaia-placed stars HYG describes 62 002 -> 61 713 at Gaia's distance, 62 097 in all;
- HYG rows without a Gaia counterpart 11 554 -> 11 478: past 250 pc 8 307 (1 660 naked-eye), inside
  it 3 170 (1 204 brighter than V 8, 1 804 from 8 to 12, 162 fainter, 112 of them Gliese stars
  within 50 pc); the sentence also said those were every star past 250 pc, which the archive's
  hosts no longer let it be;
- planets with a host 6 327 -> 6 328, the 26 left having neither a distance nor a parallax;
- renamed hosts 575 -> 574; the composite table fills 100 of the 127 blank distances, not "the 100".

No ceiling or floor changes; comments only.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 17:46:00 +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 ac23496b37 Give the old Earth W's error at AD 1 as the reviewers measured it, not truncated
a15a46c wrote that the IAU's W for Earth, taken at UT + 69.184 s as it was, left Earth's lit face
4.5 degrees off Horizons at AD 1 (4.2 at UT itself), from measurements of 4.56 and 4.28 on JD
1721600 (0001-06-26), where the same sentence rounded AD 1000's 2.29 and 2.00 up. On 0001-01-28
the same two readings are 4.49 and 4.21, so across AD 1 they run 4.5 to 4.6 and 4.2 to 4.3.

body-orientation.ts, which names no date, now says 4.5 to 4.6 over AD 1 (4.2 to 4.3 at UT); the
spec comment, which names JD 1721600, says 4.6 (4.3). Comments only; the assertion (the ERA, 0.051
degrees off there) is unchanged.

Unit suite 864/864, tsc -p tsconfig.app.json and etl:typecheck clean.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 17:43:53 +02:00
SenrokaiandClaude Opus 5.5 67a21b4a10 Keep the date on screen on a phone after Go, and hand focus back to the tab that folded
09eaf95 folded the Display sheet away on a narrow viewport once a date was set, and said the
strip then read the date. It did only in its text: the dock's tab list could not shrink, the
system view's five tabs take 397 px, and at 360 by 640 and 390 by 844 they pushed the date strip
to x 406-511, past the right edge of a page that does not scroll. With the sheet folded, the set
date was nowhere on screen; before the fold, the open field had at least shown it. And the fold
removed the form that held focus, so focus fell to the page and the next Tab started again at
Search, with nothing announcing the date.

The tab list now gives way (min-w-0) and scrolls (overflow-x-auto), and the date and range strips
keep their width (shrink-0). After a date is set on a phone, focus goes to the tab whose panel
folded, so Enter opens it again.

Live on :4301 in the Sun's system, Display, 2020-12-21T18:00, Enter: at 360x640 the panel folds,
the strip "Date 2020-12-21" sits at x 82-230 and the tabs scroll in 73 px (397 of content); at
390x844 the strip is at 112-260 and the tabs scroll in 103; page scrollWidth equals the viewport
at both, and the tab row stays 36 px high. Focus is on #dock-tab-display at both, and Enter
reopens the panel. At 1400x900 nothing changes: the panel stays open, focus stays in the field,
the tabs take their 437 px. Screenshots dock-go-360x640.png and dock-go-390x844.png, in the
review's scratchpad (wave1/solar/fixr2).

Tests: the tab list carries min-w-0 and overflow-x-auto and the date strip shrink-0 (jsdom lays
nothing out, so the classes are what can be checked there; the positions above are the app's),
and after Go on a phone the focused element is #dock-tab-display. Guarded mutants (full suite),
each failing only its named test: min-w-0 and overflow-x-auto removed, shrink-0 removed from the
date strip ('keeps the date strip on screen on a phone'), and the focus call removed ('hands
focus to the tab that folded').

Unit suite 864/864, tsc -p tsconfig.app.json and etl:typecheck clean.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 17:42:29 +02:00
SenrokaiandClaude Opus 5.5 4d47896b4a Find the naked-eye stars Gaia's HIP cross-match lacks by position, and give two unnamed ones back their names
c64eea0 and ff744a0 left 34 of HYG's stars of V 6.5 or brighter off the map for want of a distance,
saying no survey gave them one. Gaia DR3 does, for twelve, above the ETL's own cut of five times
the parallax error: fetchStars looks Gaia up only by HIP number through hipparcos2_best_neighbour,
which lacks some of theirs (66 Ori's among them), and never looks up a HYG row with no HIP number.
HD 197770 is 1.102 +- 0.029 mas; HD 45291 and HD 124953 were on the map only as bare Gaia entries
a second of arc from where HYG has them, so searching their names found nothing.

fetchStars now fetches, once and cached, Gaia DR3's 35 910 sources brighter than G 7.5 with a
usable parallax, and for a naked-eye row nothing else places takes the nearest within 5" and a
magnitude of its V. At 15" theta-1 Ori (HIP 26220) took the source of theta-1 Ori C, 12.9" away and
already on the map; the twelve found are within 4.1". An ETL run from cache: 455 522 stars become
455 532, the naked-eye stars 8 886 become 8 898 (1 663 past 250 pc), with 66 Ori, HD 49567, HD 54309,
HD 74455, HD 114461, 10 Sge, HD 197770, HD 33948, HD 152249 and HD 162678 added, and HD 45291 and
HD 124953 folded into their Gaia entries under their names. 22 stay out, with no distance anywhere.
In the running app HD 197770 reads "907 +- 25 pc", 66 Ori "469 +- 61 pc", HD 45291 "108 pc" and
HD 124953 "46 pc". Four of the twelve have a RUWE above 3.7 in Gaia; the ETL has never filtered on it.

validateStars now also requires HD 197770 and HD 45291. Control: without the positional lookup,
the ETL fails with "HD 197770 is missing" (the naked-eye floor alone would not see twelve stars go),
and the baseline passes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 17:36:44 +02:00
SenrokaiandClaude Opus 5.5 7080a5a817 Turn Eris, Haumea, Makemake and Nereid on their pages at their measured days on the map's clock
The body page's comment said the body is drawn at the clock's date and turns at its rate, but a
body with no IAU model turned 0.08 radians a second of wall time whatever the clock said: on
Nereid's, Eris's, Haumea's and Makemake's pages the Clock tab's rates and Backwards changed
nothing (Nereid 4.58 degrees a second at every rate), while the system view turns them at their
measured days on the clock. Hyperion spun the same way on its page and is still in the system
view, where it tumbles and has no day.

A body whose day is measured but whose pole is not now turns pole up at that day, counted from its
orbit's epoch as spinFor counts it; Hyperion is left still; only an exoplanet, whose day no one has
measured, still turns for show. The template comment now says so, and the tick doc names all
five bodies. Live on :4301, degrees a wall second at clock rates 1, 3600 and -3600: Nereid 0.009,
31.05, 31.08 (360/11.594 = 31.05); Eris 0, 0.951, 0.955 (360/378.504 = 0.951); Hyperion 0, 0, 0;
Mars, by its IAU elements, 0.004, 14.67, 14.56.

That makes the sphere reset when a body is shown matter for Hyperion and an exoplanet. The test the
earlier fix added for that reset checked only the light, and deleting the reset passed the suite;
a test now opens Hyperion after Earth and expects the sphere at rest (Earth left it 0.89 radians
turned). Eris's page test checks it stands while the clock stands and turns a sixth of a turn in
a sixth of its 378.504-hour day, pole up.

Guarded mutants (full suite), each failing only its named test: the day ignored and Eris turned
for show ('turns a body whose day is measured'); Hyperion turned for show, and the reset deleted
(both 'puts the sphere back at rest ... Hyperion after Earth').

Unit suite 862/862, tsc -p tsconfig.app.json and etl:typecheck clean.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 17:33:01 +02:00
SenrokaiandClaude Opus 5.5 a9e910c2ea Keep a body page's camera on its Sun's side when the clock takes the Sun across the equator, aimed at the body in that frame
The page put its camera on the side of the equator its Sun lights once, when a body was shown.
The Clock tab the same page gained in 74535ac changes the date without showing the body again, so
on Saturn's page, opened today with the Sun and the camera south of the rings, Go to 2045 moved
the Sun 25.7 degrees north (sunLight y +3.063) and left the camera south (-0.6): the unlit face
of the rings, the view the camera side was there to avoid. A clock left running through the May
2025 equinox did the same.

tick now remembers which side the Sun stood on at the last frame, and moves the camera across
only when that side changes: when a body is shown (the side is reset to 0 then) and when the Sun
crosses the equator, as the clock runs or is set. Between crossings the camera is the reader's to
orbit, below the rings if they like. The move came after the controls had aimed the camera for
the frame, which was drawn straight after with the body 22.6 degrees (2 atan(0.6/3)) off the
middle of the view; the camera now looks at the controls' target again before that frame.

Live on :4301 (this worktree's ng serve), Saturn's page opened today: camera -0.6, Sun -0.935.
Clock tab, 2045-06-01T00:00, Go: camera +0.6, Sun +3.063, the rings' lit face; 265 frames drawn
across the move, every one aimed within 0.00 degrees of Saturn. The camera then put south by
hand stayed there (-0.6 a second later). On Earth's page the Sun crosses twice a year, so at a
month a second the camera changes side about every six seconds.

Tests: the page followed into 2045 with the camera north and aimed at Saturn in that frame; and a
camera the reader takes over the rings while the Sun stays south is left there, which the old
single-flip rule only held through the flag it cleared and no test covered. Guarded mutants (full
suite), each failing only its named test: the side chosen only when a body is shown ('follows the
Sun across Saturn's equator'); the side never remembered, so the camera is mirrored back every
frame ('leaves the camera where the reader orbits it'); no lookAt after the move ('follows the
Sun across', on the aim).

Unit suite 860/860, tsc -p tsconfig.app.json and etl:typecheck clean.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 17:27:27 +02:00
SenrokaiandClaude Opus 5.5 2f49002027 Check that a redrawn orbit line reaches the GPU, and hold Saturn to the chords' own sag
The redraw test read the reshaped line's points on the CPU, which reshapeOrbitLine rewrites
whether or not it then sets needsUpdate. three uploads a buffer again only when its version
rises, which only that setter does, so a renderer that dropped the line left J2000's ellipse on
screen and passed the whole suite (857/857). The test now takes Saturn's position version after
drawing J2000 and expects it higher once the clock is at AD 1.

Its Saturn bounds, 0.002 AU at AD 1 and 0.0015 at J2000, sat inside the 128 chords' own sag, which
reaches 0.0032 AU near aphelion, where points spaced evenly in true anomaly lie furthest apart:
the test passed only because Saturn falls near a vertex on those two dates, and a line correct by
construction failed it (Saturn without its a, e and i rates: 0.00229 AU). Both are now 0.0035,
which still catches the line left unreshaped (0.054 AU at AD 1). The renderer's comment put the
sag at 0.003 for Saturn and 0.0005 for Mars; it now says 0.0032 and 0.00055, and where.

Guarded mutants (full suite):
- position.needsUpdate dropped: only the redraw test fails ("expected 0 to be greater than 0");
- the reshapeOrbitLine call dropped: only the redraw test fails;
- Saturn's a, e and i rates removed from bodies.json, line and marker on one ellipse: the
  distance bound now passes (0.00229 < 0.0035); the redraw test still fails, on the version, as
  it should with nothing to redraw, and so does Saturn's AD 3000 Horizons test (0.41 degrees).

Unit suite 858/858, tsc -p tsconfig.app.json and etl:typecheck clean.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 17:22:28 +02:00
SenrokaiandClaude Opus 5.5 030447b83c Fold a Gliese star into the Gaia source SIMBAD names it as, so none is drawn twice or inside 10 pc by mistake
HYG's Gliese-only rows, with no Hipparcos astrometry and no published error on their distance,
reach the merge with positions off by up to minutes of arc, photometric distances and sometimes
wrong proper motions, and isSameStar's geometry missed 49 of them beside their own Gaia entry. 43
lie within 25 pc and 27 are fainter than V 12, the layer the brief asked to merge without
duplicates. GJ 3478 is 16.3" from its Gaia entry, past the 15" tolerance; GJ 2097 moves 39 %
differently by HYG's motion; GJ 4285 is co-moving but 1.6 magnitudes brighter in HYG's V than
Gaia's G. Two of them were false stars inside 10 pc: GJ 2097 at 6.41 pc and GJ 4285 at 6.80, which
Gaia measures at 24.47 and 28.25. HYG also put Gl 94 31.6 degrees from where it is, and HD 23585
and HD 23713, Pleiades members at 135 pc, at 20.6 and 22.2. 0e9ab6f's "no Gliese row within 25 pc
has a co-moving bare Gaia entry 3-300" away" held only under its own motion rule.

fetchStars now asks SIMBAD once, in one cached TAP query, for the Gaia DR3 designation of every
object it knows by a GJ number (4 868), maps HYG's `gl` column onto it, and foldByIdentity folds
each Gliese-only row into the bare Gaia entry of that source: HYG's name, type and photometry,
Gaia's position and distance, as combine does for any other pair. An ETL run from cache folds 49:
455 571 stars become 455 522, the stars within 10 pc 369 become 367, within 25 pc 5 522 become
5 479, HYG rows without a Gaia counterpart 11 517 become 11 468, distances with no published error
395 become 346. exoplanets.json is unchanged. In the running app GJ 2097 reads "24 pc, HYG, Gaia
DR3 distance", GJ 4285 28 pc, Gl 94 17 pc, and 367 stars lie within 10 pc.

validateMerge now refuses any Gliese-only row beside the bare Gaia entry SIMBAD names as the same
star. Controls: folding with an empty identity map fails the ETL with "49 Gliese stars are drawn
beside the Gaia source SIMBAD names them as, starting with GJ 1033" (the baseline passed); in the
unit suite, folding a row with a Hipparcos error fails "leaves a star with a Hipparcos error, and
a Gaia entry already folded into, alone", and keeping the Gliese position fails "folds a Gliese
entry into the Gaia entry SIMBAD names it as, at Gaia's position and distance". HYG's V is kept
for a folded star, which for GJ 3207 is the wrong one (11.51 where SIMBAD has 13.75).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 17:20:32 +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 8d59c720b3 Hold the published catalogue to its naked-eye stars, its host luminosities and five other things no check saw
Seven properties of the catalogue could be lost by a one-line slip in the ETL with every validator
passing and the weekly job publishing the result. validateStars and validateExoplanets now refuse:

- fewer than 8 800 stars of V 6.5 or brighter (8 886 measured), or Rigel, Deneb or Alnilam gone;
  with no magnitude handed to placementDistancePc, 7 378 are left;
- a host luminosity that is not positive, or fewer than 90 % of planets carrying one (6 036 of
  6 354): st_lum is published as log10(L/L_sun), and stored unconverted 3 307 read <= 0 and
  Proxima's -2.82;
- fewer than 40 colours marked as read off a temperature (54 measured);
- an archive-placed star more than 1 mas from its planets' published position carried from
  J2015.5 to J2000 (6.6e-8 mas at most measured; not carried, 6 277.9);
- fewer than 300 HYG stars folded into Gaia keeping their more precise Hipparcos distance (384),
  or Tarazed and Eta Leo off theirs;
- more than 5 archive stars numbered other than their name hashes to (1 measured).

These come from an interrupted earlier attempt left uncommitted in this worktree; its other half,
asymmetric archive distance errors that nothing in the ETL wrote, and a placementDistancePc test
that failed, was discarded (saved in the scratchpad as uncommitted-at-start.patch). The naked-eye
check is new, and runs before the Hipparcos one, which the same slip also trips.

Controls, each an ETL run from cache in a throwaway worktree that reached "Validating output..."
and failed with the named check's own message: no magnitude to placementDistancePc (naked-eye
floor); `10 ** logLuminosity` -> `logLuminosity`; colorFromTemperature never set; the archive
epoch offset times 0; combine never taking the other entry's distance; archive ids in arrival
order. The unmutated baseline passed. star.model.ts's count of flagged hosts, 57, is now 54.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 17:08:31 +02:00
SenrokaiandClaude Opus 5.5 5fb0d45623 Place an archive host by its parallax where the archive gives no distance, so mu2 Sco b has its star
The archive leaves sy_dist blank for mu2 Sco and publishes sy_plx 6.31 +- 0.86 mas. The matcher
only reads the parallax as a second chance beside a finite sy_dist, and the archive-star fallback
needs sy_dist too, so mu2 Sco b had no host while Pipirima (HIP 82545, V 3.56, B2 IV) sat on the
map 0.4" from the archive's direction at 145.3 pc. The build.ts comment and the step report said
the 27 hostless planets had "no distance in either archive table"; mu2 Sco was the one whose row
has a parallax.

archiveDistancePc takes sy_dist, or 1000 / sy_plx where it is blank (158.5 pc here, within the
ratio test of Pipirima's 145.3). An ETL run from cache changes exoplanets.json alone: mu2 Sco b
now has host 82294, Pipirima, and 6 328 of 6 354 planets have a host (26 without, none of whose
rows has a distance or a parallax). hostDistancePc stays the archive's own blank.

Control: ignoring the parallax fails "takes the archive's parallax where it gives no distance,
and nothing from a parallax that is none" alone.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 16:37:30 +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 a15a46c617 Correct what the ETL, the code and the textures README said of their own sources and figures
- The IAU day check said it compared W with the period Horizons states. That holds for the eight
  planets and Phoebe only. Pluto's and Ceres's periods are the IAU's own rate restated (8.5e-12 and
  3.3e-10: Horizons' Pluto period is 360 over its W, and the SBDB notes it derived Ceres's from the
  report's 952.1532 degrees a day), and the 22 locked moons' is their orbit's, from JPL's table, not
  from their Horizons pages ("Synchronous" on eighteen, nothing on Titan's or Proteus's), and now
  their W's own rate. The comment, the log line and the error message say which is which; the log
  shows 0.0e+0 for the twenty locked moons turned at their orbit's rate, 1.1e-8 and 3.1e-7 for the
  Moon and Phobos. BodyRecord.rotationPeriodHours says that where a source states no period, or
  one a later measurement overturns, it is the one the ETL spec carries (Nereid's, Eris's), where
  the previous commit had Eris among the bodies whose source states none.
- Phoebe's spec justified its period by a note in the satellite table, which is about another
  source (Jacobson 2000, Jupiter's outer moons) and says the table carries corrected values. The
  row's n is right as the table defines it, the rate of the mean longitude: n less twice the node's
  rate is 0.6541855 degrees a day, against 360 / 550.30391 = 0.6541840. What made Phoebe drift is
  that the propagator reads a retrograde moon's n as its sidereal rate, as Triton's row gives it.
  The comment now says so; the period, 550.30391 days, is kept.
- body-orientation.ts and its spec said the IAU's W for Earth, "taken at UT", left its face 2.3 and
  4.5 degrees off Horizons at AD 1000 and AD 1. The old code took it at UT + 69.184 s; those are
  that figure's, and at UT itself they are 2.0 and 4.2, as three reviewers measured through the
  code (4.25, 4.25 and 4.28 at AD 1).
- The renderer said the 28 maps entering the Sun's system are about 20 megapixels of JPEG. Read
  from their frame headers: 37.75, nine at 2048 by 1024 with the Sun's, Jupiter's at 3840 by 1920
  and eighteen smaller.
- The textures README gave Titan 0.02 per cent unmapped, counting only the source's zeros. Its
  largest gap is a flat grey of the source's own (147 and 148, its two commonest values), which
  NASA's caption for PIA19658 names as the gap in coverage: measured on titan.jpg, one region of
  1.09 per cent of the pixels, 0.87 of the sphere, at 48-68 N and 37 W to 25 E. The row says 1.1 per
  cent and where, and step 1 says how it is counted; commit 302fa96's "the rest under 0.2%" is
  therefore not true of Titan.

No behaviour changes but the ETL's log and error wording; the solar ETL passes with the new text.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 16:16:53 +02:00
SenrokaiandClaude Opus 5.5 073fb9f718 Ring only the planet hosts inside the survey edge, so the Kepler field is not a band over the view
4c8e4a0 added 3 277 hosts from the archive, 2 883 of them past 250 pc and 916 past a kiloparsec,
most in the Kepler field. Every host got a 12 px ring whatever its distance and a place in the
draw budget's first tier after the pinned stars, rules set when 1 of 1 379 hosts lay past 250 pc.
At boot the opening view held 3 332 rings against 1 290 before, 1 687 of them in its lower right
(x > 1000, y > 550 at 1600x1000) where there were 116, over the 250-350 pc grid labels; hosts 1-3
kpc away read as neighbours in a view whose range is 307 pc, and 629 stars past a kiloparsec were
drawn where there had been 3.

Rings and the host tier now take the hosts within SURVEY_EDGE_PC only. The far hosts still compete
for the budget by brightness, and search still enters them. Measured in the running app at
1600x1000: 1 853 rings in all, 1 698 in the opening view, 199 in its lower right; 31 stars past a
kiloparsec drawn; 1 767 hosts drawn. This reverses "a ring on every host" for the far ones: that
was the user's rule of 2026-09-17, when the only far host was one star, so it is flagged for them.

The scene test's fixture gains the lens's planet, 6.7 kpc out. The new test checks the ring count
and the host tier; the opening-view test now expects the lens out of that tier. Control: dropping
the distance condition fails the new test and that one (2 of 824).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 16:14:28 +02:00
SenrokaiandClaude Opus 5.5 dadae7b8ca Give Haumea's card its three semi-axes beside its mean radius
Haumea is triaxial, 1161 x 852 x 513 km (Ortiz et al. 2017, Nature 550, 219), and is drawn as the
sphere of its volume, 797.6 km. Its card listed "Radius 798 km" under Measured and said nothing of
its shape, which only a code comment and a commit message gave: its long semi-axis is 1.46 times
that radius and its short one 0.64.

BodyRecord takes semiAxesKm, set by the ETL from Haumea's spec, and the card then reads "Mean
radius 798 km" and "Semi-axes 1,161 x 852 x 513 km". Every other body keeps its one Radius row. The
ETL checks that a body's radius is the mean of its semi-axes, the radius of the sphere of the same
volume, to 0.1 per cent (797.6 against 797.62).

Measured on :4301, Haumea's page: "Mean radius 798 km, Semi-axes 1,161 x 852 x 513 km". Test: the
card of Haumea as shipped in bodies.json. Guarded mutants: the semi-axes dropped from the spec (run
through the solar ETL), the row, or the view model, or the label left as Radius, each fail it and
only it; a radius that is not their mean fails the ETL ("Haumea's radius, 1161 km, is not the mean
of its semi-axes").

Horizons gives triaxial radii for Phobos, Deimos, Miranda and Ariel too, which the page parser
already reads into their mean; they are not carried here.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 16:12:30 +02:00
SenrokaiandClaude Opus 5.5 e62e2fb03f Keep a giant's disc clear of its neighbours' names on a phone, not only on a desktop
d1aa22e framed a giant so its disc took half the view's tighter half-extent, inside the ring its
neighbours are named on at 0.74 of it. It measured that at 1600x1000 only. The names hang a fixed
distance in from the ring, 74-78 px, so on a 390x844 phone the 0.24 of the half-side between disc
and ring is 47 px and the names land on the disc: Betelgeuse was drawn 98 px wide with HD 39374's
name 70 px from its centre, Antares with HD 148199's at 68 px.

The framing now takes the canvas's shorter side and keeps the disc inside the ring less 90 px,
at most half the half-extent as before, at least a tenth. Measured in the running app at 390x844
on arrival: Betelgeuse, Antares and Rigel drawn 54 px wide, the nearest names at 70, 68 and 127
px. At 1600x1000 nothing changes: Betelgeuse 250 px, its nearest name at 296. The ring's fraction
moves to system-framing.ts, beside the rule that depends on it, and the scene reads it there.

The review's other half, that the Readout sheet covers 44 % of the disc once opened on a phone, is
not changed: the dock opens with no panel below 640 px and folds it on any tap on the scene, so
the star arrives with nothing over it.

Control: giving the names no reach fails "keeps a giant's disc clear of its neighbours' names on
a phone" alone. The scene's passing of the canvas size has no unit test (the test canvas has no
size); the app measurement above covers it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 16:09:56 +02:00
SenrokaiandClaude Opus 5.5 4e7d4cd1d6 Say a type estimated from a colour read off the archive's temperature came from the temperature
23547de gave archive hosts measured in no colour a B-V read off the dwarf sequence at their st_teff,
and the card's Colour row says so, but the subtitle then estimated a type from that colour and
said "from colour". 30 of the 54 flagged stars read that way: PSR J1719-1438, a millisecond pulsar
whose only input is st_teff 4 500 K, read "Spectral type ~K5, from colour" beside "B-V 1.13, from
its temperature"; DP Leo ~B7, ZTF J1828+2308 ~B5, ZTF J1230-2655 ~A0.

The subtitle now ends ", from its temperature" for those 30. In the running app the pulsar's card
reads "Spectral type ~K5, from its temperature" above "B-V 1.13, from its temperature". The type
itself is still the dwarf the temperature matches, which a pulsar is not; this only stops it
naming a measurement that does not exist.

Control: ignoring the flag fails "says an estimate came from the temperature where the colour
was read off it" alone.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 16:05:08 +02:00
SenrokaiandClaude Opus 5.5 2dac3767e8 List a host's own planets ahead of the systems whose names only run on from its own
Once the archive's 3 277 hosts became stars and 574 Gaia hosts took their names, "K2-18" listed
the star K2-18 and then K2-180 to K2-186, and no planet: "k2-18 b" and "k2-180" were both prefix
matches, and on a tie the ranking put every star before every exoplanet. Replaying each host's
name through the ranking over the published catalogue, 325 hosts had some of their own planets
pushed out of the eight rows shown (655 planet rows), against 75 before the archive stars.

A prefix match that ends where a word does now scores 3.5, between an exact match and a prefix
running on into the same word. The same replay gives 50 hosts and 62 rows. In the running app
"K2-18" lists K2-18, K2-18 b, K2-18 c, then K2-180 onward; "Kepler-186" lists the star and
Kepler-186 b to f before Kepler-1860; Kepler-22 b and WASP-12 b are back. "Proxima", "Io" and
"TRAPPIST-1" list as before.

Control: scoring the word-ending prefix level with any prefix fails "lists a host's own planets
ahead of the systems whose names run on from its own" alone.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 16:05:08 +02:00
SenrokaiandClaude Opus 5.5 fc7677e715 Turn Nereid in the 11.594 hours Kepler measured, where it was drawn still
Nereid's Horizons page states no spin, and the ETL, finding none, left it still; the validator's
comment read that as "Nereid has no spin". Its rotation is measured: Kepler's K2 light curve gives
11.594 +/- 0.017 hours, confirming earlier ground-based periods (Kiss et al. 2016, MNRAS 457, 2908;
arXiv:1601.02395). Its spec now carries that day, as Eris's carries Bernstein et al.'s, and with no
known pole it turns about its orbit normal, as Eris, Haumea and Makemake do. The free-spinner check
accepts it (11.594 hours against a 360-day orbit).

A new validator: a moon without a lock must have a day unless it tumbles, and only Hyperion
("Rotational period = Chaotic") does. Nereid, left without one, fails it: "Moon nereid is drawn not
turning, and is not known to tumble". The renderer spec's example of a body left still was Titan,
said to have no period on Horizons, though it carries its orbit's; it is Hyperion now, and
BodyRecord.rotationPeriodHours says where each kind of period comes from.

Measured: the solar ETL passes; in bodies.json only Hyperion has no rotationPeriodHours; live on
:4301 Nereid's marker turns 60.000 degrees in a sixth of its day. Test: Nereid, as shipped, turns 60
degrees in 1.93 hours. Guarded mutant, the day removed from its spec and run through the solar
ETL: the validator fails, and the suite on the data it wrote fails that test and only it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 16:02:30 +02:00
SenrokaiandClaude Opus 5.5 e8e857ec25 Hold Io and Europa to their own track ceilings, and Hyperion's one-date ceiling to just above its offset
Io's and Europa's periapses turn backwards, held by the Laplace resonance (apsidesRegress), which
brings them to 0.07 and 0.23 degrees of Horizons from 1950 to 2100. Nothing guarded the flag: with it
dropped the ETL still passed, Io at 0.96 and Europa at 2.24 under the general 3-degree track
ceiling, their cards quietly rewriting themselves to "within 1.0" and "within 2.3". They now have
ceilings of 0.2 and 0.5, as Tethys has for the term it takes from its W. The comment on the
one-date check, which said it catches the periapsis run the wrong way, now says it does not
(0.904 against 2.5) and which check does.

Hyperion's one-date ceiling was 21 degrees, described as just above its offset on that date, where
it is 9.413: the 21 was its worst over twelve dates in an earlier check. It is now 10.

Measured on the real catalogue with the solar ETL: Io 0.07, Europa 0.23 at worst, Hyperion 9.413,
all passing. Guarded mutants, run through the solar ETL: apsidesRegress removed from both specs
fails with "Io's mean elements put it up to 0.96 degrees ... (at most 0.2 expected)"; Hyperion's row
misread by 6 degrees of node fails the one-date check at 15.41, which 21 let through.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 15:59:09 +02:00
SenrokaiandClaude Opus 5.5 3b4fd1af6c Turn each locked moon at its orbit's rate and Iapetus's pole round its orbit, so they face their planets at every date the clock reaches
The IAU gives a locked moon's W the mean motion of whichever orbit its authors had, and JPL's table
has another. Near the present the difference is nothing; over the clock's AD 1 to 3000 it turned
Proteus's far side to Neptune at AD 1 (146 degrees), Mimas 52 degrees from Saturn and Miranda 23.
Iapetus was worse for another reason: its IAU pole is a straight line, 3.9 degrees a century in
right ascension, through its orbit normal's 3 439-year circle round the Laplace pole, which by AD 1
has run past the celestial pole (Dec 97.9), 11 degrees off the orbit, with the face 87 degrees
from Saturn. Mimas and Iapetus carry mission maps, so a wrong hemisphere was drawn facing Saturn.
The ETL's lock check sampled only 1950-2100, so none of it failed.

tools/etl/lib/locked-spin.ts, lockedToOrbit, called for every locked moon:
- W's rate becomes the orbit's own mean motion, its constant moved so W is unchanged on
  2025-01-01; the pole and every periodic term stay the IAU's. A W with a quadratic is left
  (Phobos's orbit already takes it; the Moon's is its tidal slowing, 0.75 degrees at AD 1). The
  kernel's rate must be within 1e-5 of the orbit's first (at most 3.4e-6, Iapetus).
- Iapetus (poleFollowsOrbit): the pole follows its orbit normal, as a moon in a Cassini state does,
  in the IAU's own form: sines of the node's angle and four harmonics on right ascension, cosines
  on declination, fitted to the normal's circle and pinned to the IAU pole at the present; W takes
  sines of the same angles, fitted to hold the face where it is today.

build.ts samples the lock over AD 1 to 3000 (8 114 dates, every 135 days) instead of 1950-2100, and
subPlanetLongitudeDeg moved to the lib, shared by both. Measured on the real catalogue: at most
5.36 degrees (Titan) but the Moon 7.62 (its eccentricity, and W's quadratic at AD 1: a new named
ceiling of 8), Mimas 8.94 (ceiling 11 -> 9.5) and Iapetus 15.95 (19 -> 16.5, 9.4 of it its row's
lag); Proteus's own ceiling of 9 is gone, at 2.66. Iapetus's axis stays within 0.74 degrees of its
orbit normal (11.06 before) and its pole is the IAU's at the present to 1e-4 degrees.

Live on :4301, the longitude facing the planet at AD 1 / 1000 / 2025 / 2999: Proteus 2.6 / 2.6 /
2.6 / 2.6 (was -146.5 / -72.9 / 2.6 / 74.5), Iapetus -15.9 / -15.4 / -15.3 / -9.3 (-87.0 / -50.9 /
-15.3 / 22.8), Mimas 4.1 / 6.8 / 5.7 / 8.4 (48.6 / 29.3 / 5.7 / -13.0), Miranda -0.1 / 2.2 / 0.0 /
1.4 (-23.4 / -9.6 / 0.0 / 12.6); the present is unchanged.

The renderer spec now takes the rotational elements from bodies.json too, so no hand copy is left,
and a new test turns Proteus, Miranda, Mimas and Iapetus to their planets at AD 1 and AD 3000.
Guarded mutants, each run through the solar ETL and then the full suite on what it wrote:
lockedToOrbit bypassed (validator: Mimas 52.30, ceiling 9.5; the new test fails), Iapetus on the
IAU's straight pole (98.48), and its pole round the orbit without W's terms (73.13); each fails the
new test and only it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 15:58:04 +02:00
SenrokaiandClaude Opus 5.5 f8582b78d0 Test the star's disc itself: its photograph, its colour and its darkening toward the limb
2242fe0 gave every star the Sun's photograph in grey, tinted at its temperature and limb-darkened
as 1 - 0.6(1 - mu), and nothing tested any of the three: with the coefficient at 0, the tint
dropped from the material, or the photograph replaced by the tint alone, all 817 tests passed. The
one colour test read scene.starTint, a uniform the scene sets whether or not the material uses it.

The new test enters Proxima and walks the disc material's colour node graph for the tint uniform,
the texture loaded from SUN_TEXTURE_PATH and the coefficient 0.6. Controls, each failing it alone
(1 of 820): the coefficient at 0; at -0.6, a limb brighter than the centre; the tint dropped; the
photograph replaced by the tint. A mutant that keeps the texture node in the graph but multiplies
it by nothing is not caught; the test checks the parts, not the arithmetic between them.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 15:57:52 +02:00
SenrokaiandClaude Opus 5.5 368fcfb6f0 Keep each spectral type's giant surface once, rather than reparse it for every star at boot
The star field reads every star's temperature at boot, and each read asked giantSurface, which
splits the type and runs two regular expressions whether or not the star is a giant: 455 571
times for 2 888 distinct type strings. It depends on the type alone, so it is now kept per type.

The tint pass over the published catalogue gives the same colours (checksum 5 830 745.929 before
and after) and in Node takes 167-237 ms against 241-305. In the running app, the star field
rebuilt in the page three times after each of four cold boots: median 65 ms before (60-79), 51
and 53 ms after in two runs (46-67). The longest task after the data lands did not move beyond
the noise (median 851 ms before, 859 and 827 after): most of the boot growth since c38a42c is the
larger catalogue and the per-star colour lookup, which the tint needs. Memoising colorIndexToRgb
on its three inputs, the fix the review also named, would not help: 337 998 of the 455 571
triples are distinct, and reviewers measured it 260-370 ms slower.

No behaviour changes, so no new test; the suite (819) passes as before.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 15:51:41 +02:00
SenrokaiandClaude Opus 5.5 17cf11b6b1 Tint a planet host in the star field at the archive's temperature, which its disc is drawn at
95ffb00 said the field took "the same function and the same temperature the disc is drawn with".
It did for stars without planets, but a host's disc is drawn at the archive's st_teff
(starSurfaceOf), and the field read the host's temperature off its colour. Of the 4 485 hosts with
a temperature on the published catalogue, 1 755 were more than 0.1 in RGB from their own disc:
Kepler-186 at 3 096 K in the field and 3 788 K on its disc, HD 97048 at 6 825 and 10 000.

The scene now hands StarFieldRenderer each host's first published temperature among its planets,
the rule starSurfaceOf uses (publishedTemperaturesK, beside it), and the field tints that star at
it. Over the catalogue the hosts more than 0.1 off their disc go from 1 755 to 0. Measured in the
running app at 1600x1000, field tint against the disc's starTint after entering each: Kepler-186
(1, 0.620, 0.326) against (1, 0.619, 0.326), where the field was (1, 0.498, 0.174); Teegarden's
Star 0.001 apart; Proxima, HD 97048 and Sirius 0.

The field's test compared colorIndexToRgb with the same formula. The new scene test enters Proxima,
whose B-V 1.8 reads 3 070 K and whose disc is drawn at the archive's 2 900, and compares its field
colour with the disc's. Controls, each failing that test alone: the field ignoring the map it is
given, and the scene passing an empty one.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 15:48:09 +02:00
SenrokaiandClaude Opus 5.5 09eaf9532f Keep the Display panel shorter on a phone, and fold it away once a date is set
The date form made the Display panel 400 px tall at 360x640 (from 257) and 363 at 390x844 (from
220), and the Sun's system sat behind it: every orbit at 360x640, 81 per cent of their points at
390x844. A reader who set a date could not see what it did without closing the panel.

The window's description is one line, "AD 1 to AD 3000, where the planets' elements hold." (the
sentence on how far each moon's orbit strays is on each card and in the system note), and below
sm the field shrinks so Go stays on its line. Measured on :4301 with the panel open: 318 px at
360x640, Go at y 501 beside the field at 500; 318 px at 390x844, where 9 per cent of the orbits'
points are behind it and 37 of the 39 lines show.

At 360x640 the system is framed behind even the old 257 px sheet, so a date submitted with Go on
a narrow viewport now folds the sheet, as choosing a search result already does. Measured: after
Go at both sizes the panel is gone and the strip reads "Date 2020-12-21". Wide screens keep it
open.

Tests: the sheet folds after Go on a narrow viewport, and stays open on a wide one; jsdom has no
matchMedia, so the spec gives the dock one it can turn narrow. Guarded mutants: no fold fails the
first, a fold on every screen the second (and "jumps the clock to the date submitted", which then
cannot find Back to now).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 15:44:02 +02:00
SenrokaiandClaude Opus 5.5 74535accc5 Show the date and the clock on a body's page, and open it on the side of the equator its Sun lights
Since the page turns a body as it stands at the map's date, it has been drawn for a date it never
showed, at a rate it gave no way to change: set to 2032 at a day a second in the system view,
Saturn's page ran on at that rate with only Search and Bookmarks on its dock, and at a month a
second Earth turned about 183 degrees a frame at 60 Hz. The dock now takes `clock`, which offers
the clock without the layers, as a tab named Clock, and the page binds it with the date strip the
system view already has. Measured on :4301 at 2032-06-01: the strip reads "Date 2032-06-01", the
tabs are Search, Bookmarks and Clock, and the Clock panel has the four rates and the date field;
Back to now clears the strip.

The camera opened 11.3 degrees north of the equator whatever the season, which since 2025 put
Saturn's page on the unlit face of its rings, and will until 2039. When a body is shown, the next
frame puts the camera on the side of the equator the Sun is on: measured on Saturn's page, the
Sun at -26.71 degrees on 2032-06-01 and the camera at -11.31; Earth's in June stays north.

Tests, each proved by a guarded mutant that fails it:
- the page follows the clock after its first frame (frozen at the first frame: 90 degrees out);
- a body with no IAU model shown after Earth gets the page's own light back (left at Earth's Sun);
- the page's dock shows the date and a Clock tab, and no date at the present (date never set, or
  the dock bound without the clock);
- the dock offers the clock alone as a Clock tab (no tab, or a tab still named Display);
- Saturn's page in 2032 opens with the camera and the Sun both south (camera held north).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 15:42:58 +02:00
SenrokaiandClaude Opus 5.5 aafc788352 Hold van Belle's giant temperatures at 3 134 K from M6, as its table does, and test every piece
giantSurface carried the 64-71.5 fit of van Belle et al. (2021, ApJ 922, 163, table 8) on to
index 73.9, where the table's fourth piece, 72-74, is flat at 3 134 K: M6 III (index 72) came out
3 300 K and M7 III 3 214 K. The paper's own M6 and M7 giants average 3 112 and 3 114 K. The
temperature and the correction at it both feed the drawn radius, so the 29 catalogue giants from
M6 to M7.9 were drawn too small: Rho-2 Ari 57.1 solar radii, now 81.5; 30 Her 88.3, now 126.0;
Eps Oct 64.9, now 92.6. The comment said the scale was held "past M7.75"; it now says from M6.

The giant test only reached the third piece (Aldebaran K5 III, Antares M1). It now checks each
piece against table 8: G8 III 4 797.08 K, K2 III 4 387.58, M5.5 III 3 343.43, and M6 and M7 III at
3 134. Controls, each failing "reads a giant's temperature and correction both off its type"
alone: the third piece carried past M6 again; a G giant indexed as K; the first piece's slope
52.74 -> 45; the second's 199.41 -> 180. Before this, the last three left all 817 tests passing.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 15:42:15 +02:00
SenrokaiandClaude Opus 5.5 206e88ae85 Read a dwarf with a type and no colour at its type's row of the dwarf sequence
A non-giant with a spectral type but no colour took its temperature off the textbook colour
spectralTypeToColorIndex gives its type, read on Pecaut & Mamajek's table, and its bolometric
correction off the old textbook anchors. The table puts those colours elsewhere: M8's B-V 1.88
is M5-M5.5 there, 3 001 K where M8 V is 2 570, and the anchors give M8 -3.92 where the table has
-5.65. GJ 3655 (M8, 14.35 pc) was drawn at 0.035 solar radii, a third of Jupiter, where its
type's row gives 0.106 (Mamajek's M8 V: 0.114). Every O type came out B0's 31 400 K.

Both now read dwarfSequenceAtType, which already existed for the giants, and a G magnitude is
carried to V at the type's G-V too. On the published catalogue, of the 835 non-giant stars with
a band, a type and no colour, those more than 200 K off their type's row go from 21 to 0, those
with a radius more than 1.5 times off it from 7 to 1 and a luminosity from 10 to 1 (the one left,
Oph 11, is a G-band star). GJ 3849 (dM9) goes from 0.030 to 0.089 solar radii, GJ 3855 (M6.5)
from 0.040 to 0.087.

Two tests pinned the textbook path and now read the table: M5Ve with no colour is 3 060 K, not
3 106, and K5 tints as B-V 1.15, its row, not 1.105. An O8 star is 35 100 K, its row, not B0's.
Controls: reading the temperature through the textbook colour again fails "is drawn at its
type's row of the dwarf sequence" (with the three updated tests); reading the correction off the
anchors again fails that test alone.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 15:41:01 +02:00
SenrokaiandClaude Opus 5.5 28fa79e1e0 Check the Horizons vectors against the bodies.json the app ships, and every rate Standish's rows give
The frozen Horizons tests ran on a hand copy of seventeen records, so an ETL that lost Standish's
a, e and i rates, or Io's and Europa's backward periapses, wrote a bodies.json that passed both
its own validators and the whole unit suite: the data-refresh job's "Unit tests against the new
data" read none of it. record() now takes kind, orbit, rates, laplacePole, parentBodyId and
massRatio from src/assets/data/bodies.json, read with node:fs as texture-catalog.spec.ts reads its
JPEG, and the copy is gone. Today's data passes as the copy did (all seventeen were identical).

The parser test checked only the mean motion and the periapsis rate on Earth's row. It now checks
the node, a, e and i rates too, against Standish's Table 2a (-0.24123856, -0.00000003, -0.00003661,
-0.01337178 a century). Dropped, those rates move Saturn 0.66 degrees at AD 1 (node) and 0.36 at
AD 3000 (a, e, i), where no date from 1950 to 2100 shows more than 0.036.

Guarded mutants, each run on the full suite:
- bodies.json without the a, e and i rates, as that ETL writes it: 'puts saturn within 0.1 degrees
  of Horizons on JD 2816787.5' and Earth's fail (and the new orbit-line test, on the same data).
- bodies.json with Io's and Europa's periapsis rates turned positive: Io's and Europa's 1950
  tests fail.
- the parser without its a, e and i rates, and with a node rate of 0: 'gives the rates per day'
  fails, and only it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 15:25:41 +02:00
SenrokaiandClaude Opus 5.5 3e1bd33b6c Draw every body in a system on one shared sphere, so returning to the Sun's system is no long task
Each marker built its own 64 by 32 SphereGeometry, and the Sun's system now has 38 of them: the
renderer's constructor took 15 ms, 12 of them building spheres, and with their first upload a
return to the system made a long task of 52 to 70 ms that the base's 18 bodies never did.

Every marker is now the one unit sphere, scaled to its radius, which it keeps in
userData.radiusAu. The shared sphere is never disposed; Saturn's ring is built in the sphere's
own units, since it is the marker's child; keepMarkersLegible reads the stored radius and scales
against the sphere's.

Measured on :4301, eight returns to the Sun's system each (select null, then 0, at 1600x1000):
before, a long task on 3 of 8 (52-57 ms), swapToSystemSpace 13-17 ms and the first render 30-40;
after, no long task on 8 of 8, the swap 2.7-4.4 ms and the first render 20-38. Earth is drawn at
the same 0.656 AU at arrival, and every member shares one geometry.

Tests: one sphere for every marker, each at bodyMarkerRadiusAu of its radius, and not disposed
with its system; Earth held to its 3-pixel floor at the arrival framing, which no test covered.
Guarded mutants, each failing only its named test: a sphere per marker, the shared sphere
disposed, the ring built in AU inside the scaled marker ('picks Saturn through its rings'), and
the legibility scale divided by the body's radius.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 15:14:02 +02:00
SenrokaiandClaude Opus 5.5 c2683da37b Redraw a planet's orbit line as its axis and eccentricity drift, so Saturn stays on it at AD 1
The orbit lines kept the shape of the J2000 elements and only turned with the node, while the
markers moved on Standish's drifting a and e. Saturn's eccentricity falls 0.00032 a century, so
at AD 1 its line passed 0.056 AU (8.4 million km) from Saturn, Jupiter's 0.016 AU from Jupiter,
and Pluto's 0.021 AU from Pluto at AD 3000. The comment that said no drawn line shows the drift
weighed one century of Pluto's axis, not twenty of Saturn's eccentricity.

update() now writes the line's 129 points again once |da| + a |de| since they were drawn passes
1e-4 AU, well under the 128 chords' own sag. Measured in the app on :4301, marker to its own
polyline: Saturn 0.0016 AU at AD 1 and 0.0028 at AD 2999, Jupiter 0.0014 at AD 1, Pluto 0.0078
at AD 2999 and 0.0082 today, all the chord sag.

The existing Mars test checked only that Mars stays in its line's plane. A new test measures the
distance to the drawn chords: Saturn 0.0017 AU and Mars 0.0005 at AD 1 (0.054 and 0.0022 without
the redraw). Guarded mutants: the call removed, and the threshold raised to 1 AU, each fail it
and only it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 15:01:08 +02:00