31e0c04fe1e3686b7f614be00f70bd34720a6ab3
49
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
31e0c04fe1 |
Read a colour past either end of the dwarf table at that end, where the star has no type instead
dwarfSequenceAtColor answers null outside Pecaut & Mamajek's table, B−V −0.301 to 2.16 and BP−RP −0.12 to 5.1, and effectiveTemperatureK then fell back on the type, which Gaia's stars do not have and carbon stars' parser does not read. 219 stars with a measured colour got no temperature and so no radius, and were drawn at the Sun's radius in the Sun's colour: 110 white dwarfs within 50 pc, 38 Gaia stars redder than BP−RP 5.1 (Gaia DR3 6439125097427143808, an ultracool dwarf 4.0 pc away), HD 46687, La Superba and the other carbon stars, and an O8 star. Past the table, the colour is now read at the row it is past (clampToTable), but only for a star with no readable type: beside a type an off-table colour is more often the bad measurement — HD 49748 is G5 V at B−V −0.32 — so the type still wins there, as it did. The luminosity reads the same point, so a star past the red end also gets the M8.5 row's G−V and correction. A type's own colour past the table, which only O types have, is read at B0. Carbon and S stars (C, N, R, S) now count as giants, so their correction stays their type's, not an M8.5 dwarf's −5.78. Measured on the shipped catalogue: stars without a temperature 3 053 -> 2 834, without a radius 3 077 -> 2 858; all 292 stars with an off-table colour now have a temperature, against 73. Gaia DR3 6439125097427143808 is 2 420 K and 0.110 R☉ (M8.5 V: 0.104); the 110 white dwarfs a median 0.018 R☉ at 10 700 K (0.0013 to 0.034); HD 46687 212 R☉ at 2 420 K and La Superba 133; audit #16's Gaia DR3 5612323414549657984, k1 Pup, B6 V at BP−RP −0.15, 203 L☉ and 4.1 R☉ against 143 and none (FLAME gives 336 and 3.49). The 2 851 stars left without a radius have neither a colour nor a readable type, or no band. Controls, each failing its named test: no clamp for an untyped star (2 of 784 failed), the clamp winning over a type, an O type's colour unclamped, the luminosity off the unclamped sequence, and carbon stars not counted as giants (1 of 784 each); clampToTable ignored (3 of 784). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> |
||
|
|
d097f4b477 |
Correct a giant's light by its type, not by the cooler dwarf its colour reads as
|
||
|
|
2242fe0a7f |
Give every star a limb-darkened surface in its own colour, and light its planets with it
Every star but the Sun was a flat disc of one colour, and the Sun wore the texture pack's orange photograph, lit by the same white light as every other star's planets. Every star now shares one TSL material (starSurfaceMaterial in the scene): the Sun's map in grey, times the colour of a blackbody at the star's temperature, times a linear limb-darkening law, 1 - 0.6 (1 - mu), the Sun's coefficient in the visible. The temperature is the one the radius uses: the archive's st_teff for a host, else the dwarf sequence at its colour or type, else the Sun's. The colour comes from blackbodyColor (stellar.ts): Kim et al.'s cubic fit to the Planckian locus, then CIE XYZ to linear sRGB, brightest channel 1. At D65 it gives 2 900 K (255, 180, 103), 5 800 K (255, 241, 235) and 9 600 K (208, 219, 255), against (255, 182, 98), (255, 241, 231) and (211, 221, 255) in Charity's integrated blackbody table. The tint is a uniform, so the shader is built once and not per system. The star's PointLight takes the same colour against the Sun's, since the planets' photographs were taken in sunlight: the Sun's light stays white at pi, TRAPPIST-1's (2 566 K) is (1, 0.44, 0.10) and Proxima's (2 900 K) (1, 0.52, 0.17), Sirius's (0.52, 0.67, 1). The intensity stays pi. No halo comes back. sun.jpg was 2048 by 1024 and 822 427 bytes for a disc that reaches 216 px across at the Sun's closest approach on a 1080-line screen. It is now 1024 by 512 in grey, 31 306 bytes, which covers the disc to a 1440-line screen. Its brightness varied by 56 % rms, which made every star a mottled rock; it is rescaled to 14 % rms about the display's white, of the order of the Sun's granulation contrast, the brighter half clipped as in a photograph exposed for the disc (6 % rms remains). Measured on the dev server (1600 by 1000, the camera at its closest approach): - the disc's brightness against the law, from r/R 0.52 to 0.97: Sun 0.970/0.841/0.763/0.657/0.560 against 0.927/0.829/0.755/0.645/0.554, ups And and Sirius the same to within 0.05; - the disc's centre in sRGB: Sun (245, 233, 226), ups And (F8V, 6 157 K) (249, 239, 239), Sirius (199, 209, 243); - the first system entry of a fresh page, three runs each in alternating blocks against the previous commit: Sol's longest task 90 ms before, 86 ms after, two tasks over 50 ms in every run either way; Proxima Centauri's none over 50 ms after, one run of six with a 53 ms task before. The long tasks on Sol's first entry predate this change. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> |
||
|
|
7213f987c4 |
Draw every star at its own radius, measured where the archive has one and derived otherwise
The system view drew the Sun at its own radius and every other star at 0.45 of its innermost orbit, capped at 0.2 AU: a size chosen so the star would not swallow its planets, not the star's. Proxima Centauri was drawn at 2.8 solar radii, eighteen times its own, and every star without planets at 43. starSurfaceOf (body-view-model.ts) now gives each star a radius and a temperature. A planet host takes the archive's st_rad and st_teff from its planets' rows: 4 439 hosts are drawn at a measured radius, 22 at a derived one. Every other star's is derived: its temperature off Pecaut & Mamajek's dwarf sequence at its colour (the same table the spectral estimate reads, or at the colour its type implies where it has none), its luminosity from its absolute magnitude and the bolometric correction luminositySolar already applies, and R = sqrt(L) / (T / 5772 K)^2. Against the archive's own st_rad for the 1 447 catalogue hosts that have one, the derived radius is within 0.018 dex at the median, 0.071 dex at the 90th percentile, and within a factor of 1.5 for 97.1 %. Sirius comes out 1.79 solar radii (1.711 published, Liebert et al. 2005), Wolf 359 0.117, Betelgeuse 584, the Sun exactly 1. A star with no band has only the ETL's stand-in magnitude, and gets no derived radius: PSR J1719-1438 came out 2.3 solar radii from it, wider than its planet's orbit. With the stars that have neither a colour nor a type, that leaves 3 077 of 455 608 stars (274 of 4 735 hosts) with no radius; they are drawn at the Sun's, and their card gives none. The card says which it is: "Radius 0.141 solar radii" for a published one, "~0.10 solar radii, from colour and brightness" for a derived one, two figures because colour does not give three. A giant drawn at its size can be wider than its system, so systemFramingDistanceAu also makes room for the star, and the controls' closest approach is now three of the star's radii where that is more than the old 0.05 AU. 23 211 stars are drawn wider than 3.6 solar radii, which put 0.05 AU inside three of their radii, and a zoom would have carried the camera through the surface of the largest. The Sun keeps 0.05 AU. starMarkerRadiusAu and the renderer's innermost axis, which only it read, are gone. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> |
||
|
|
3392f06c85 |
Read a star's bolometric correction off its colour, and carry Gaia's G to V first
A star's luminosity was its absolute magnitude plus a bolometric correction read off its spectral type, with its magnitude taken as V whatever band it was in. Gaia classifies none of its stars, so all of its 379 000 got the Sun's correction, and their G was read as V: TRAPPIST-1 came out at a seventh of its luminosity. The dwarf sequence spectral.ts already reads types off (Pecaut & Mamajek 2013, table 5, online version 2022.04.16) now carries its effective temperature, bolometric correction to V and Gaia G-V columns, and dwarfSequenceAtColor interpolates them at a colour, in the colour's own system. luminositySolar uses it wherever the star's colour is inside the table: the G magnitude is carried to V, then corrected. Only without such a colour does it fall back to the spectral type, as before. Against the archive's own st_lum for the 1 449 hosts that are catalogue stars, the median error goes from 0.038 to 0.022 dex and the 90th percentile from 0.292 to 0.115 dex; within a factor of 1.5, 84.5 % -> 93.8 %. For the 572 hosts Gaia describes: 90th percentile 0.332 -> 0.073 dex, 81.8 % -> 97.0 % within a factor of 1.5. Barnard's Star with no type now reads 0.0029 L_sun against 0.0035 published, and TRAPPIST-1 7.3e-4 against 5.5e-4 (Agol et al. 2021). Across the catalogue, 311 255 of 455 608 stars move by more than 10 % (median ratio 0.90: a G-type star's G is 0.16 brighter than its V), and so do the hosts of 3 158 planets, whose equilibrium temperatures follow as the fourth root. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> |
||
|
|
b46c840369 |
Warm every body by the microwave background too, so none reads colder than space
The derived equilibrium temperature balanced starlight alone. For the widest orbits that is less than the 2.7 K every body in space is held at by the cosmic microwave background. The planet 7 493 AU from 2MASS J21252752-8138278 read "Equilibrium temp. 1 K", and the one 19 000 AU out from UCAC4 328-061594 would have read 0 K. equilibriumTemperatureK now adds the background as a second source in the same balance, T^4 = T_star^4 + (2.7255 K)^4 (Fixsen 2009). Inside a few hundred AU of any star it changes nothing that shows: Earth, Mars, Jupiter and Neptune reproduce their published values as before. Across the 6 354 exoplanets, the rounded temperature changes for 11, all on orbits of 350 AU or more. Ten go from 0, 1 or 2 K to 3 K, from VHS J125601.92-125723.9 b at 350 AU to UCAC4 328-061594 b at 19 000 AU, and 2MASS J22501512+2325342 b at 518 AU goes from 4 to 5 K. On the dev server, the detail pages of the 7 493 AU planet and of GJ 900 b read "Equilibrium temp. 3 K". Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> |
||
|
|
5333be615e |
Estimate a spectral type from each star's colour where no catalogue gives one, and say so
383 695 of the 455 608 stars read "Spectral type Unknown", every Gaia star among them, although 380 884 of them carry a colour. The card now gives those the type of the dwarf whose colour is nearest, in the colour's own system, marked as an estimate: TRAPPIST-1, BP-RP 4.90, reads "Spectral type ~M8, from colour", which is how it was classified (M8 V). A star with neither a type nor a colour gets no subtitle rather than the literal "Unknown". The colours are Pecaut & Mamajek's mean dwarf sequence (2013, ApJS 208, 9, table 5), as Mamajek maintains it online (version 2022.04.16, which carries Gaia BP-RP), from B0 to M8.5. B-V cannot tell O types apart (the whole sequence spans 0.03 of it), and BP-RP turns back past M8.5 and is tabulated only from B9. A colour outside the table gets no estimate: 186 BP-RP and 41 B-V colours, among them the blue Gaia DR3 5612323414549657984 at BP-RP -0.15. 380 657 stars get an estimate: F 97 840, G 144 327, K 105 984, M 29 142, A 3 272, B 92. Checked against catalogued types: - 19 433 HYG dwarfs with a B-V: 74.3 % within two subclasses, 95.5 % within five. - 146 Gaia exoplanet hosts the archive types as dwarfs, from BP-RP: 88.4 % within two subclasses, 98.6 % within five. The estimate assumes a dwarf, so a giant reads later than it is (Pollux, K0 III at B-V 0.99, reads K3). It also ignores reddening. The comment says both. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> |
||
|
|
a81dd491ad |
Say which band each star was measured in, whose catalogue it is, and how sure its distance is
The readout named Hipparcos, Yale and Gliese while 378 775 of the 455 608 stars are described by Gaia DR3, printed one "Magnitude" for G and V alike, and gave every distance to the parsec. The band cannot be read off the star's source, which records whose position it has: 62 002 stars Gaia places keep HYG's V and B-V, and the 575 hosts renamed after their planets are Gaia's, in G. stars-meta.bin gains two byte columns, 14 to 16 bytes a star (6 378 512 to 7 289 728 bytes; gzip -9 2 804 049 to 3 110 991). One holds the magnitude's band (V 76 555 stars, G 378 744, none 309, whose magnitude is a stand-in), which colour the colour index is (B-V 75 203, BP-RP 376 703), and whether the distance is Gaia's parallax. The other holds the distance's relative error as its square root in 255ths: a step is 0.08 % of distance at 1 %, 0.35 % at 20 %, and 100 % is the top. encode, decode, BYTES_PER_STAR_META and the build.ts round trip cover both; no workflow reads the format. Where the errors come from: - Gaia rows keep the parallax_error their query already fetched: median 0.3 %, 90th percentile 1.2 %, at most 20 %, the query's own cut. - A HYG star at Gaia's distance takes the cross-match's parallax_over_error, and a star merged into a Gaia entry keeps that entry's error with its position. - The 3 067 Hipparcos stars that keep their Hipparcos distance, Rigel, Deneb and Alnilam among them, take e_plx from van Leeuwen's 2007 reduction: a new cached query of public.hipparcos_newreduction on the ESA archive, whose 117 955 rows HYG's distances invert. - The archive's stars take sy_disterr1/2 from pscomppars, in a query and cache file of their own so the composite rows already cached were not refetched. - 439 distances have no published error: 357 Gliese rows and 82 archive hosts. Of the errors, 392 786 are 1 % or less and are not printed; 61 156 print as "117 ± 12 pc" to the distance's own digits; 1 196 between 20 and 100 %, and 30 past it, print as the range the parallax gives, since a symmetric error in parallax is a lopsided one in distance. The star card (measured on the dev server) now reads, for example: - Rigel: 265 ± 23 pc, V 0.18, B-V -0.03, source HYG. - Deneb: 433 ± 60 pc. - Alnilam: "476 pc to 833 pc" (Hipparcos 1.65 ± 0.45 mas). - Gaia DR3 5612323414549657984: 111 ± 2 pc, G 4.63, BP-RP -0.15, source Gaia DR3. - Proxima Centauri: 1.30 pc, V 11.01, source "HYG, Gaia DR3 distance". - TRAPPIST-1: G 15.62, BP-RP 4.90, source Gaia DR3. - Kepler-186: V 15.14, source NASA Exoplanet Archive. The neighbourhood's subtitle reads "Gaia DR3 378,775 · HYG 73,556 · NASA Exoplanet Archive 3,277", counted by the catalogue describing each star. build.ts validateStars now fails a catalogue with more than 1 000 stars without a band (309 today) or without a distance error (439). Dropping G from the Gaia rows gave 379 040 without a band, and dropping their parallax_error gave 441 216 without an error; both runs failed. Decoding the catalogue in Node took a median 29 ms before and 24 ms after (nine runs each, within noise). In the app, five cold boots gave a 654-786 ms long task after the data landed and the HUD at 1.83-2.07 s. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> |
||
|
|
4c8e4a02f3 |
Give every planet with a distance a star, from the archive where the catalogue has none
After matching, 4 237 planets still had no star: their hosts are too faint for either Gaia query (fainter than G 12 past 50 pc) or too far (past 250 pc), Kepler-186 at 177.6 pc among them. fetchExoplanets now adds one star per such host from the archive's own figures, 3 277 of them, whenever the archive gives a position and a distance: - position carried back from J2016, where the archive publishes it (741 of the 746 matched hosts moving over 100 mas/yr sit nearer their star carried back, a median 0.11" against 3.47" as published), and placed at sy_dist; - magnitude in V (2 999 hosts), else Gaia G (13), the band the catalogue's Gaia stars are already in; 265 have neither, 128 KMT, 95 OGLE and 32 MOA microlensing hosts at a median 6.2 kpc among them, and take the ETL's faint stand-in of 15; - colour as B-V from the archive's B and V, else from st_teff through a new temperatureToColorIndex (Ballesteros 2012, inverted; the Sun's 5 772 K gives 0.65), else left to st_spectype; - ids from 1 070 000 000, past Gaia's two ranges and under the 2^30 validateStars enforces; source "exoplanet-archive". Hosts past 250 pc are included: 399 of the 3 277 are within 250 pc, 1 962 between 250 pc and 1 kpc, 916 beyond. The drawn budget still chooses what is drawn (70 000 of 455 608). Planets with a star: 2 090 -> 6 327 of 6 354. The other 27 have no distance in either table (Luhman 16 A, mu2 Sco, PSR B1620-26 among them). Systems in the Solar Neighbourhood readout: 1 450 -> 4 736. validateExoplanets now refuses a catalogue where fewer than 99.5 % of planets have a host (measured 99.58 %); dropping the added stars fails it at 2 090, and dropping the composite fill at 6 227. No added star sits within an arcsecond of a catalogue star (validateMerge's twins stay at 23); 5 of the 399 within 250 pc have one within a minute of arc, VHS J125601.92-125723.9 (archive 12.7 pc) 5.8" from a Gaia entry at 21.2 pc the likeliest duplicate. In the app, searching TRAPPIST-1 or Kepler-186 and picking the star enters a system with its seven and five planets drawn. gzip -9: stars.bin 5 030 741 -> 5 067 864 B, stars-meta.bin 2 784 614 -> 2 804 064, stars-index.json 4 003 584 -> 4 015 882, exoplanets.json 342 467 -> 446 532 (host parameters, both commits). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> |
||
|
|
494fb5616b |
Find the hosts the catalogue already holds, and name them after their planets' star
The archive's default-parameter rows leave sy_dist blank for 100 planets, TRAPPIST-1's seven among them, so the matcher could not place a host the catalogue had drawn since the Gaia nearby query (Gaia DR3 2635476908753563008, 12.47 pc). fetchExoplanets now also reads the Planetary Systems Composite table (pscomppars) and fills a default row's blank host cells from it. Only host columns: a composite row takes each column from its own reference, so orbits still come from the default row alone, one fit per planet. Where both tables give a distance or a position they agree on all 6 225 and 6 352 rows. Three hosts the catalogue holds by name failed the distance ratio test because the archive's sy_dist, from TICv8, contradicts its own parallax: Lalande 21185 (GJ 411) 5.68 pc against 392 mas, Luyten's Star (GJ 273) 5.92 against 263 mas, Struve 2398 B (Gl 725 B) 6.84 against 285 mas. resolveHostStarId now takes the archive's parallax (sy_plx) as a second distance the ratio test accepts. It is a second chance, not a replacement: 47 of 5 959 systems disagree past the tolerance, and for faint far hosts the inverse parallax is the worse figure (K2-238: 538 pc by sy_dist, 6 779 by parallax). Planets on a catalogue star: 2 071 -> 2 090 of 6 354. The 19 gained are TRAPPIST-1 (7), GJ 273 (2), GJ 411 (2), Gl 725 B, HD 62509 (Pollux), K2-65, TOI-2267 A and B, and two brown-dwarf hosts, 2MASS J02192210-3925225 and DENIS-P J082303.1-491201. No planet lost or changed its host. A matched host whose catalogue name is a bare Gaia designation now takes the archive's host name, so search finds TRAPPIST-1, Teegarden's Star, TOI-700, LP 791-18 and K2-18: 575 stars renamed. validateExoplanets refuses a host that is still only a designation, and the star assets are rewritten after the exoplanets for that reason; stars.bin and stars-meta.bin are unchanged. TOI-2267 A and B both land on one Gaia entry, which takes the name TOI-2267 A. Each planet also carries its host's radius, effective temperature and luminosity (10^st_lum), and a mass for 6 344 planets instead of 5 474, from the default row where it gives one and the composite table otherwise, for the star-physics step. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> |
||
|
|
74b93a0428 |
Fill in the Sun's faint neighbours from Gaia, and fold the Gliese entries they repeat
Gaia's G < 12 cut took a quarter of what lies within 10 pc: the red, brown and white dwarfs most of the neighbourhood is made of, which the map only had where Gliese happened to list them. Teegarden's Star was missing, and with it its three planets' host. A second query fetches the complement out to 50 pc: G >= 12 or no G, parallax > 20 mas, with pmra/pmdec so the J2016 -> J2000 propagation applies. The quality filter was chosen by counting. Parallax over error > 5 keeps 39 751 of the 39 764 sources that pass the floor, but the Gaia Catalogue of Nearby Stars (GCNS; Gaia Collaboration, Smart et al. 2021) rejects 11 752 of them as spurious: median G 20.2, astrometric excess noise 5.4 mas against 0.15 for the ones it keeps, 6 933 toward the Galactic centre. So the query joins the GCNS main table (EDR3 astrometry and source ids, which DR3 carries unchanged) and keeps 28 012 rows; the error cut stays and costs no GCNS source, brown dwarfs included. The query has its own row-count floor (28 012) and the row-limit cap, and is ordered by (phot_g_mean_mag, source_id); a second ETL run reproduced stars.bin, stars-meta.bin and stars-index.json byte for byte. Its ids start at 1 050 000 000, clear of the main query's and under 2^30, which V8 keeps unboxed: numbered from 2 000 000 000 they made the app's boot task 230 ms longer (medians of five interleaved runs, 1.41 s against 1.18). validateStars now refuses an id outside 0 to 2^30. The Gliese entries these stars duplicate were not folded: HYG carries them with positions off by up to a minute of arc and photometric distances, so they missed the 15" tolerance or failed the distance test. Their proper motions, which Gliese measured well, give them away: isSameStar now takes two entries moving within 20 % of each other as one star up to 60" apart, whatever their distances, brightness still permitting. Of the 602 Gliese-only rows left without a counterpart, 253 have such a Gaia entry; with every entry shifted a quarter degree, none does. fetchStars passes HYG's motions only for rows without Hipparcos astrometry: given them too, 15 Hipparcos stars took a co-moving companion's Gaia entry and the cross-catalogue pairs under an arcsecond went from 23 to 35. Of HYG's 1 200 stars fainter than V 12 within 25 pc, 1 024 now sit on a Gaia position (325 before); of the 176 left alone, 43 still have a Gaia entry 3-60" away (218 without the motion rule), some of them real companions. Measured on the rebuilt catalogue, against the GCNS (sources with parallax > 100, 40 and 20 mas): within 10 pc 336 -> 372 (GCNS 312; the map adds 60 HYG-only stars) within 25 pc 3 652 -> 5 560 (GCNS 5 111) within 50 pc 13 702 -> 40 916 (GCNS 40 231) 452 331 stars (+27 214). Proxima, Barnard's Star, Wolf 359, Rigel, Deneb and Alnilam are all present by name; Teegarden's Star is Gaia DR3 35227046884571776 at 3.83 pc and hosts its three planets. Luhman 16 is not in Gaia DR3 with a parallax (5353626573555863424 has a two-parameter solution) and stays absent. 94 more exoplanets find a host (2 071), none changes host. TRAPPIST-1 is now drawn (Gaia DR3 2635476908753563008, 12.47 pc) but its planets are not yet matched to it. Merge gate, ceilings unchanged: HYG rows without a Gaia counterpart 12 352 -> 11 554 (ceiling 15 000; 10 886 before the naked-eye stars), cross-catalogue pairs under an arcsecond 23 -> 23 (ceiling 100). The gate's comment now accounts for the survivors by magnitude. gzip -9 sizes against the catalogue before both changes: stars.bin 4 711 922 -> 5 030 741 B, stars-meta.bin 2 576 213 -> 2 784 614 B, stars-index.json 3 724 855 -> 4 003 584 B (+806 KB, 7.3 %). Boot on the dev server, five interleaved cold runs: the task that indexes the catalogue after the data lands, median 1 072 -> 1 182 ms; HUD shown, median 2 622 -> 2 716 ms. This machine measured 0.82-1.30 s for the same baseline task today, above audit #25's 627-843 ms. The drawn-star budget is unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> |
||
|
|
c64eea0803 |
Keep every star the naked eye sees, Rigel, Deneb and Alnilam among them
The 250 pc cutoff took 1 543 of HYG's 8 920 stars of V 6.5 or brighter:
1 339 that both surveys put past it and 204 HYG has no distance for. Five
of the fifty brightest stars in the sky were gone (Rigel, Deneb, Alnilam,
gamma-2 Vel, Wezen), and Naos, Sadr, Aludra and Arneb with them, while
11th-magnitude Gaia stars at the same distance were drawn. The cutoff
bounds a download, not what the sky shows.
placementDistancePc now keeps a star of V 6.5 or brighter at any distance,
at the better of its two distances as before: Gaia's where the archive's
Hipparcos cross-match has a usable parallax, Hipparcos's otherwise. Gaia
saturates on the brightest, so Rigel (264.6 pc), Deneb (432.9), Alnilam
(606.1), Wezen (492.6), Naos, Sadr, Aludra and Arneb sit at their
Hipparcos distance. 1 502 HYG rows come back, 165 of the 206 without a
Hipparcos distance among them because Gaia measured them; 36 fold into a
Gaia entry. 41 naked-eye stars stay out because neither survey gives them
a distance: beta Phe, Polis, Mu Cep, Rho Cas, Eta Car, Alp Cam, Phi Cas,
Chi Aur, Psi-1 Aur, theta-1 Ori, Omi-1 Cen, 66 Ori, 16 Sgr, 10 Sge and 27
HD stars.
Measured on the rebuilt catalogue: 425 117 stars (+1 466), 8 301 of them
past 250 pc (+1 466), the same 336 within 10 pc and 3 652 within 25 pc.
Merge gate: 12 352 HYG rows without a Gaia counterpart (was 10 886; the
ceiling of 15 000 is unchanged, and its comment now counts the naked-eye
stars among the survivors) and the same 23 cross-catalogue pairs under an
arcsecond. Five more exoplanets find their host: HD 81817 b and c,
HD 158996 b, HD 208527 b, HD 220074 b. gzip -9 sizes: stars.bin
4 711 922 -> 4 728 315 B, stars-meta.bin 2 576 213 -> 2 587 977 B,
stars-index.json 3 724 855 -> 3 731 763 B.
The brief behind this also asked to cut once on the best distance, which
would drop the 6 835 stars Hipparcos puts inside 250 pc and Gaia outside.
Those already sit at Gaia's distance (
|
||
|
|
337602f606 |
Make the budget test spend the budget, and bound the probes that earn nothing
The fixture built for "exactly MAX_VISITED stars reachable" was 338 short: its random cloud leaves clumps the departure never reaches (the LCG gives 12 212 distinct positions for 39 999 stars), so the search settled 39 662 and the pre-fix code answered `gaveUp: false` too. The test could not fail on the code it was written to pin — and the mutant that seemed to prove otherwise was failing to compile, not failing the test. It is now a line of 40 000 a parsec apart with the island off the line: settled 40 000 exactly, 115 ms, and the pre-fix code does report a give-up. Both mutants now compile and are caught. The give-up cap also has to hold while the bisection has earned nothing: the exception added for that case had no bound at all, so a search could spend the resolution's own eight full-budget probes — about 17 s of "Plotting…" — where two used to cost 4 s. Bounded at five. On the repo's crowded-knot fixture: 1.9 s for the unearned ceiling figure with the old cap, 7.2 s for a range the bisection earned, and five probes is where that lands. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016jxMkwA2rbicdGxHosecYi |
||
|
|
1a53f26474 |
Answer the review: say which search gave up, and let the bisection earn its offer
The panel printed "No route at this range." beside the range it was offering — which is the sentence this branch exists to stop it printing. It was gated on there being no offer, and a search that gives up usually has one: Sol to HD 120147 at 4.5 pc spends the budget, offers 4.83 pc, and says there is no route where a 71-jump route exists. The wording now follows the search at the range that was asked for, and nothing else. That needs the two give-ups kept apart, so `least` travels beside `gaveUp` to the panel: one says the asked range was not searched out, the other that the search for a range that would work was. HIP 69445 at 3 pc — asked-range search exhaustive in 44 ms, ceiling probe out of budget — used to read "Too many stars to search at this range." and now reads "No route at this range.", with nothing claimed after it. Two more from the same review. The budget flag was read off the settled count, so a search that proved a dead end with the last star it was allowed reported a give-up; it now records why the loop stopped. And the bisection's cap could fire before a single probe had narrowed anything, leaving the ceiling route's own longest hop as the answer: star 1000115173 at 3 pc was told to go to 8.00 pc, the control's maximum, for a crossing that works at 6. It now offers 6.93. Measured in the app, all three: "Too many stars to search at this range. 4.90 pc would reach.", "No route at this range." alone, and 7.00 pc in place of 8.00. The duplicated dead-end test now asks the question it was named for — exactly the budget's worth of stars reaching each other and none of them the destination — and each fix kills its own mutant. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016jxMkwA2rbicdGxHosecYi |
||
|
|
7ab92e61a1 |
Give the budget tests room and cells to run in
Both make a search spend its whole 40 000-star budget, twice over in the bisection, and the CI runner timed out at the default five seconds. The crowds are now indexed in cells sized for the ranges asked of them, as the real catalogue is, and the two tests carry their own 30 s timeout. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016jxMkwA2rbicdGxHosecYi |
||
|
|
7971ec4007 |
Simplify: without a give-up the bisection can only have closed on the resolution
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016jxMkwA2rbicdGxHosecYi |
||
|
|
862fb65ea4 |
Tell a search that gave up from a route that is not there
The route search stops after MAX_VISITED stars and returned null, which everything downstream read as "the catalogue holds no chain". On the real catalogue that was wrong for real questions: Sol to HD 120147 (136 pc) at 5 pc is 50 jumps, and the panel said there was no route. The budget also sat under what the shipped catalogue needs, so it is now 40 000 rather than 20 000: both that route and a star at 170 pc are found, and Sol to HD 2626 at 6 pc, which used to be refused after 4.7 s, plots 56 jumps in about 2 s. A search now reports whether it gave up. The range search no longer counts a give-up as proof that nothing routes below it — that is what reported ranges up to 29% too wide — and it stops after two of them, since those are the probes that cost the most and settle the least: for HD 2626 at 3 pc it offers 5.92 pc in about 4 s, against 6.13 pc in 4.7 s. At the panel's widest range the refused route and the range search are the same question, so it is asked once. Where nothing can be said, the panel says "Too many stars to search at this range." rather than claiming there is no route. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016jxMkwA2rbicdGxHosecYi |
||
|
|
539a0f0a3e |
Answer the review: budget in drawn pixels, re-ask when the budget moves, sort only the band it runs out in
- The budget counted CSS pixels; lines are drawn in device pixels, so a screen scaled to 150% or 200% drew 1.5-2x the calibrated line. It now counts the canvas's drawn pixels. - A graph was re-asked only when the drawn stars changed, so with a star budget covering the whole catalogue, or a resize, its budget and centre stayed wherever the layer was turned on. A view that chose its stars again now asks, and a graph is rebuilt when the stars, the range or the budget changed (the budget by more than half the margin, or its centre by more than 5 pc). - From inside a system the budget was worked out in astronomical units about the system's origin. Graphs are now asked for in parsec space only; the flight back out asks. - Comparing budgets let a request re-asked with a slightly different one supersede its twin, and the twin's rejection cleared the state of the request that replaced it. A rejection now clears it only for the latest request. - The worker sorted every link to keep a few thousand, 2.2x an unbudgeted build. It now bands links by distance, keeps every band before the one the budget runs out in, and sorts only that one: 142-168 ms on the real catalogue against 233-388 ms, 103 ms unbudgeted, returning early when all fit. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016jxMkwA2rbicdGxHosecYi |
||
|
|
bd5e9d4b0d |
Budget the jump-link layer in pixels of line, nearest the view's centre first
With the drawn set following the view, the layer at 8 pc cost the integrated Radeon 503 ms a frame at 30 pc from the Sun. Measured, the cost follows the length of line on screen (about 10 ms per million pixels near the Sun), not the number of links: 100 000 links were 12 ms at the opening view, 25 000 were 61 ms at 30 pc. So the budget is a length: a million pixels, turned into parsecs at the depth the view is centred on, spent on the links nearest that centre by their nearer end. Orbiting with links at 8 pc on the iGPU: 12-18 ms p50 at every pose measured, no long tasks. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016jxMkwA2rbicdGxHosecYi |
||
|
|
c6206a8311 |
Link only the stars that are drawn
The jump-link graph linked the whole catalogue: 3.7 million links at 8 pc, 7.4-7.8 s in the worker and a 443-515 ms frame on the main thread when they landed, and most of them between stars that were neither drawn nor clickable. A graph request now carries the star field's drawn stars, and the worker links only those, over an index of its own with cells as wide as the range. The scene asks again once a new drawn set has held still for 250 ms. The renderer is handed the graph's bounding sphere instead of computing it: three.js walked every vertex on the main thread in the first frame that drew a new graph, 48-55 ms at 8 pc. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016jxMkwA2rbicdGxHosecYi |
||
|
|
f156e03822 |
Answer the review: send the worker one request at a time, keep only the latest, and never wait for a dead one
The adversarial review confirmed three defects in this PR, all reproduced in the browser. 1. Superseded graphs queued up in front of routes. The worker answers messages one at a time and cannot drop one it has started. With the jump-link layer on, every pause on the range slider posted a full graph build, seconds of work at 6-8 pc. Answers no longer wanted were thrown away only once built. A route asked for afterwards waited behind every one of them: a one-jump route took 44 s. RoutingClient now holds requests and sends them one at a time. While one is out, only the latest of each kind waits: a newer graph replaces an older one before it is ever built, and the older promise is rejected with SupersededRequest. Routes go ahead of graphs. The same question asked again while outstanding shares the answer rather than being worked twice, as when the layer is turned off and on during a build. The same scenario in the browser (layer on, range stepped 5 -> 8 pc with 400 ms pauses, then Sol to Proxima): the route came back in 110 ms. The worker was sent "links 3, links 5, route, links 8"; 6 and 7 were never built. 2. A worker that failed left the panel stuck. With no error handling, a worker that failed to load (a 404 on its chunk after a redeploy) or threw left "Plotting…" and a disabled button for good, and a graph at a range could not be asked for again. The worker now answers an exception with a 'failed' message, which rejects that request. A worker that fails to load or dies is abandoned, and what it left outstanding, and everything asked afterwards, is answered in place. The scene releases the panel when a route fails, and forgets a graph range that was never drawn so it can be asked for again. 3. Nothing type-checked the worker. The application builder never reads webWorkerTsConfig, and bundles the worker with esbuild, which strips types without checking them. tsconfig.app.json leaves the file out. A type error in the worker shipped. `npm run worker:typecheck` (tsc -p tsconfig.worker.json) now runs in CI beside the other project checks. webWorkerTsConfig is removed from angular.json, since it only suggested that something checked the worker. Tests with a fake worker cover one request at a time, a waiting graph replaced and a route sent ahead of it, a question shared, a failure rejected and the next request sent, and a failed worker's requests answered in place. A scene test covers the panel released after a failed route. Negative controls, each caught: several requests sent at once, a waiting graph kept, graphs ahead of routes, a question asked twice, a failure answered as a success, a failed worker waited on, the panel left pending, and a type error in the worker (caught by worker:typecheck). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016jxMkwA2rbicdGxHosecYi |
||
|
|
965739e99e |
Merge branch 'feat/drawn-set-follows-view' into perf/routing-worker
The label and star-field review fixes arrive under the routing client: the scene keeps constructing RoutingClient beside the neighbourhood, and builds the brightness index where it built the order. Both sides' new scene tests are kept. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016jxMkwA2rbicdGxHosecYi |
||
|
|
86e143131e |
Answer the review: pin by the index the neighbourhood holds, and choose again only when it can matter
The adversarial review confirmed three costs this PR added, all reproduced in the browser. - The first pinned refocus stalled the first flight of a session. The renderer built its own id-to-index Map of 423 651 entries the first time a star was pinned, which is at the first selection, inside the approach flight. The worst frame was 47-103 ms, and the Map stayed as a second copy of a lookup the scene already had. The scene now pins by catalogue index, through the StarNeighbourhood it builds at load (new `indexOf`), and the renderer takes indices. First selection, measured in the browser: worst frame 18 ms. - At galactic scale every label pass rewrote the drawn set. The view centre sweeps hundreds of parsecs a pass there, far past any star, so each pass chose the same 70 000 stars again and uploaded 2 MB to the GPU: 11 times on the flight out to the Galaxy. The scene no longer refocuses at galactic scale, where the whole catalogue is a few pixels, and the renderer leaves its buffers alone when the drawn set is unchanged. Flight to the Galaxy: 2 refocuses, no frame over 50 ms. - At load the same set was chosen twice: once by the renderer's constructor around the Sun, and again by the first label pass, centred on the Sun. The scene now records the constructor's choice as the current focus. Tests: the buffers keep their version for an unchanged set, no refocus at load, none at galactic scale, and pins arrive as indices. Proxima's id in the scene spec now differs from its index, so a lookup by id cannot pass for one by index. Negative controls, each caught: an unchanged set rewritten anyway, a refocus at galactic scale, the boot choice not recorded, and pins passed as ids. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016jxMkwA2rbicdGxHosecYi |
||
|
|
c3fcb2e481 |
Merge branch 'perf/label-scan' into feat/drawn-set-follows-view
The label fix turns the brightness order into an index with positions and ids laid out beside it. The star field only needs the order, so it is handed `.order`. Both sides added scene tests in the same place; both are kept. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016jxMkwA2rbicdGxHosecYi |
||
|
|
b071d87d8a |
Answer the review: walk the brightness order in memory order, and stop at the fifteenth label
The adversarial review confirmed a regression in this PR. Near the Sun, the label pass became three to four times slower than the scan and sort it replaced. Within about 11 pc of the Sun, and in any plan view zoomed tighter than that, the label radius clamps to 4 pc. That sphere holds a few dozen faint dwarfs deep in the brightness order, so the walk rarely finds fifteen stars to name and reads nearly the whole catalogue. Reading the star objects in brightness order jumps all over memory, so a full walk took 19-25 ms against the old 5-6 ms. The review also found that spreadLabels checked the label count at the top of its loop. After placing the fifteenth label it asked for a sixteenth candidate, which near the Sun can lie at the far end of the order. brightnessIndex now lays each star's position and id out beside the brightness order, in that order. The walk tests stars from those arrays in sequence and reads a star object only when it yields one. spreadLabels breaks straight after placing the fifteenth label. Measured on the real catalogue with the label logic reduced to what decides placement, camera at the given distance from the Sun (old sort / this PR as first pushed / now): 2 pc 4.9 / 24.7 / 2.5 ms 5 pc 5.7 / 23.0 / 3.1 ms 10 pc 6.3 / 18.2 / 0.95 ms 307 pc 22 / 0.01 / 0.00 ms (the opening view) The labels are identical in every case. Now faster than the old sort at every distance. A new scene test counts the candidates spreadLabels takes: exactly fifteen for fifteen labels. Negative controls, each caught: positions one axis off, ids in catalogue order, the selected star dropped, the radius edge excluded, and the count checked before taking a candidate. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016jxMkwA2rbicdGxHosecYi |
||
|
|
8c69a7a8b2 |
Plot routes and build the jump-link graph in a Web Worker
Route plotting ran on the main thread, and so did the jump-link graph: - the range search for a far target, HD 2626 at 236 pc, takes 4-5 s; - the graph at 8 pc is 3.7 million links, 6-10 s to build, then as many link objects again to turn into vertices. The map stopped for as long as either ran. A Web Worker now does both. RoutingClient sends it the catalogue's ids and positions once, and it keeps its own spatial index. A route question comes back with the route, or with the range that would open one. A graph comes back as one Float32Array of segment vertices, transferred rather than copied. On the scene side, only the latest route request is shown: an earlier answer arriving later is dropped. Only the graph for the range last asked for is drawn. The Routes panel says "Plotting…" and holds its button while a request is out. collectJumpLinks gave way to jumpLinkSegments, which writes the vertex pairs straight into floats rather than building link objects first; the scene was its only caller. The routing module (routing.ts) is the message protocol and the one function answering it, so the worker is a dozen lines, and the same answers are worked out in place where there is no Worker, as in the unit tests' DOM. The worker is built with its own tsconfig, as the Angular builder expects. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016jxMkwA2rbicdGxHosecYi |
||
|
|
2d997e41db |
Draw the stars around wherever the view is, not only around the Sun
The star field draws a budget of the catalogue: everything within 25 pc of the Sun, then the brightest of the rest. That choice was made once, at load, around the Sun, and never again. On the Gaia catalogue it left most of the map empty wherever the view went: - a region 150 pc out drew 49 of the 442 stars within 25 pc of it; - a plotted route ran through stars no one could see or click. Sol to Almach at 8 pc passes 19 stars and drew 6, Sol to Mirfak 11 of 26; - a search for a faint star flew the camera to an empty point. The drawn set now follows the view. The scene chooses it again at the label cadence, once the orbit target has moved more than 5 pc or the pinned stars have changed. The budget goes, in order, to the selected star and the stars of a plotted route, then everything within 25 pc of where the view is centred, then the same around the Sun, then the brightest of the rest. The instance buffers hold the budget and are rewritten in place. Checked in Chromium on WebGPU, framing Mirfak from 12 pc: with the set chosen around the Sun, 122 of the 649 stars within 25 pc were drawn; following the view, all 649. At the opening view the drawn set is the same as before. A refocus takes 9 ms in the browser (5 ms of it choosing). The first version took 16-36 ms in the browser, a visible hitch during a flight. Most of that time went on walking the 423 651-star brightness order once per neighbourhood, out of catalogue order, and on recomputing 70 000 colours. Now both neighbourhoods are gathered in one pass in catalogue order and sorted on their own, and colours and sizes are computed once for the whole catalogue. The brightness order itself sorts a typed copy of the magnitudes, taking 83 ms at load instead of 104-139 ms. STAR_RENDER_BUDGET is now 70 000, and its comment gives the measurements behind it rather than "currently set to the whole catalogue", which stopped being true when Gaia landed. At 1920 x 1080 on a Ryzen 7700X: - on the RTX 4080, the whole catalogue costs the same 6.1 ms a frame as the budget; - on the processor's two-core Radeon, standing in for an entry-level laptop, every 100 000 stars costs about 4 ms: 112 fps at the budget, 44 at the whole catalogue, and the same under WebGL2; - drawn whole, the opening view turns into a grey wash that buries the labels and the host rings. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016jxMkwA2rbicdGxHosecYi |
||
|
|
0a0b301807 |
Name the brightest stars by walking one order, instead of sorting 60 000 five times a second
The star labels are refreshed every 0.2 s. Each pass filtered the whole catalogue to the stars within the label radius, sorted them by magnitude, and turned every one into a label object, all to place at most fifteen. At the opening view the radius holds about 60 000 stars, so each pass was a 55-70 ms task on the main thread. A CPU profile of the opening view, on a Ryzen 7700X with an RTX 4080, counted 29 tasks over 50 ms in 6.7 s, one every 230 ms; updateLabels took 23% of the main thread. That is the stutter the frame-time bench measured on every GPU and every render budget. The catalogue is now sorted by brightness once, when it loads. brightestWithin walks that order and hands stars over lazily, and spreadLabels already stopped once it had placed fifteen labels, so a pass reads only the stars it looks at. The output is the same as before: the same stars, in the same order, with ties in catalogue order, the selected star named wherever it is, and a star exactly on the radius included. The spec checks it against the filter-then-sort it replaces. Stars are no longer scanned at all when the view is at galactic scale, where the result was thrown away. Profiled again on the same view: 0 tasks over 50 ms, and the scene's per-frame work over the window dropped from 2 028 ms to 342 ms. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016jxMkwA2rbicdGxHosecYi |
||
|
|
efe6667b00 |
Route with A* over numeric cell keys, so a route can reach past the Sun's crowd
The route search widened evenly from the departure, Dijkstra-style, with a budget of 20 000 stars. On the Gaia catalogue those are all within about 40 pc of the Sun, so it found no route to anything farther at any range: Sol to Mirfak (155 pc) failed at 3, 8, 15 and 30 pc alike. Every failure then asked minimumRangeBetween what range would work. That search widened the same way with a 30 pc ceiling, and it ran for up to a minute on the main thread before giving up with nothing. routeBetween is now an A* search. Each star is queued by the distance travelled to it plus the straight line on to the destination, on a binary heap rather than a linear scan of the frontier. It heads for the destination instead of flooding the core around the departure. minimumRangeBetween bisects the range, one routeBetween per step, because whether a chain exists can only become truer as the range grows. Its answer is always the longest hop of a route actually found, so a range it names always opens one. Its ceiling is now the Routes panel's own maximum, MAX_JUMP_RANGE_PC: a range the control cannot be set to is no answer, and raiseTo already clamped any figure above it. The spatial index keys its cells by one number packed from their three indices instead of an "ix,iy,iz" string. A search visits up to 125 cells for every star it expands, and building those strings was half of what a route cost. forEachWithin hands neighbours over unsorted and uncollected, which was most of the other half; within is now that, gathered and sorted. The no-route line said nothing in the catalogue bridged the gap; it now says no chain of jumps up to the panel's maximum reaches the star, which is what was searched. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016jxMkwA2rbicdGxHosecYi |
||
|
|
4eb61ff58e |
Draw each HYG star at Gaia's distance, and keep the ones Hipparcos misplaced
HYG and Gaia were both cut at 250 pc, each on its own distance. A star Hipparcos put at 200 pc and Gaia at 300 was kept by the first, never downloaded from the second, and drawn at 200. That is where 83% of the 9 691 mid-magnitude HYG stars without a Gaia counterpart came from, and at the median Hipparcos had them a third too close. The mirror case, Hipparcos outside and Gaia inside, dropped the HYG row and left its Gaia entry anonymous. Gaia's own Hipparcos cross-match (hipparcos2_best_neighbour, a fixed DR3 table of 99 525 rows) gives a usable Gaia distance for 97 751 of them. placementDistancePc keeps a star either survey puts inside the cutoff, and draws every kept star at the better measurement, inside the cutoff or not. 57 121 HYG stars now sit at Gaia's distance. 6 833 of them are past 250 pc: Zet Per 230 -> 259 pc, 35 Ori 137 -> 330, 44 Cnc 223 -> 613, and the farthest, HIP 69445, at 8.7 kpc. 3 666 stars that Hipparcos put outside are now kept, and 3 656 of them give a Gaia entry its name. The cross-match is required rather than skipped when unreachable. Without it, every one of those stars would move back to its Hipparcos distance, and the published map would flip with the archive's availability. The ESA TAP answered it with a 500 at first and in 102 s on the next try. So fetches now retry 5xx and network failures twice, after 30 s and 120 s, in the fetch every source goes through. The refresh job also carries the Gaia DR3 responses from run to run in the Actions cache: the release is frozen, and a live re-fetch has already reproduced stars.bin byte for byte. 423 651 stars (+10), 61 168 HYG rows folded into Gaia entries (+3 656), 351 597 unnamed designations (-3 656). 10 886 HYG survivors and 23 unmerged pairs under an arcsecond, both inside the merge gate's ceilings. The same 1 972 exoplanets have a host; KELT-4 A b and MWC 758 c now sit on their named star. The HUD's "Radius" becomes "Survey radius": 250 pc is where Gaia is surveyed to, and no longer the edge of the map. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016jxMkwA2rbicdGxHosecYi |
||
|
|
037545d036 |
Answer the review: a name two stars answer to names neither, and NaN is not a proper motion
Three guards the matcher was missing, none of which changes a byte of the regenerated data — the ETL re-run after them is identical — and all three now have a test that fails without them. A proper motion that is not a number poisoned every comparison rather than one: NaN loses every `<` it appears in, so `cosine < minCosine` was false for every star, each one reached the distance guard, and the last one in catalogue order won — a confident wrong answer, order-dependent, where the honest answer is "no match". The archive's own parser never produces one (parseOptionalNumber maps a blank cell to undefined), but the matcher is exported for offline re-cross-referencing and a caller reaching for bare Number() is exactly the coercion the CSV helper documents as having caused two prior bugs. An unusable motion now reads as no motion. Normalizing a name strips the dot, so `Gl 55.2` and `Gl 552` — two stars 135 degrees apart — share one key, and the index kept whichever came last; 64 such groups exist in the catalogue, among them `Gl 84.1A`/`Gl 841A` and `HD 96600` twice. A name that names two stars names neither, so ambiguous keys are dropped and the query goes to the sky, where direction settles it. No archive hostname lands on one today, which is why the data is unchanged. And the cache is keyed by the whole request rather than the query alone, here and in gaia.ts: fetchTextCached records only that some response arrived, so an endpoint edit would have kept serving the old host's bytes — the same silent staleness the query hash was added to close. The tests now discriminate what the comments claim. Eight mutants, each caught: judging only the published position, only the carried-back one, judging each star on its worse epoch rather than its better, letting a distance-rejected star claim best-so-far and shadow the true host behind it, an unguarded proper motion, a last-wins name index, a fixed angular tolerance instead of a transverse one, and no distance guard at all. The GJ 887 test grew a decoy standing halfway along the star's own track: it is nearer than Lacaille 9352 at the published position and nearer at the worse of the two epochs, so it wins unless both epochs are tried and the better one decides — the property the test's comment had been claiming untested. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016jxMkwA2rbicdGxHosecYi |
||
|
|
080bbe16dc |
Match exoplanet hosts on the sky, at both epochs the archive might mean
The host cross-reference matched in 3D, nearest star within half a parsec. That is the wrong space for the same reason the star merge learned it: a direction is measured, a distance is inferred. At 170 pc half a parsec is a ten-arcminute cone, wide enough to hand the planets of stars our catalogue does not carry to whatever bright star floats nearest — HATS-6 b sat on HD 39500, seventy arcseconds away. At 60 pc it is tighter than the routine disagreement between the archive's Hipparcos distances and our Gaia ones, which is how four bright giants (7 CMa, HD 81688, omi UMa, xi Aql) lost their planets and GJ 15 A's landed on a neighbouring entry. Hosts are now resolved like stars are merged: by name first, then the nearest star on the sky within a transverse budget — angle times the archive's distance, 0.01 pc — whose distance does not flatly contradict the archive's (the merge's own 50 % ratio). The budget is transverse because the dominant error is proper motion over an epoch difference, a physical displacement that is the same in parsecs at every distance: as an angle it is 60" for Proxima and 2" for a host at 100 pc. Measured on the 504 hosts whose archive name matches a catalogue name outright, true pairs reach 3.4e-3 pc; shifting every host a quarter of a degree finds nothing else within 0.01 but Proxima's own entry, whose budget at 1.3 pc is wider than the shift. The archive never says which epoch a position is for, and they are mixed: alf Tau and GJ 273 publish J2000, HD 133131 and TOI-2459 publish Gaia's J2016. So the query asks for sy_pmra/sy_pmdec too, tries each position at both ends of those sixteen years, and judges a star on whichever is closer. Guess one epoch and a fast star's planets land on a companion: J2016 puts Aldebaran's on Gl 171.1B, J2000 puts GJ 15 A's on a Gaia entry 15.9" out. 1 972 of 6 354 planets now sit on a host, 1 548 before: 432 gained, 26 on a better star (GJ 15 A to Groombridge 34, GJ 676 A off its companion, HD 19994 to 94 Cet), 8 lost — six false 3D matches to stars the catalogue never contained, and GJ 273 b/c, whose archive row says 5.92 pc for Luyten's Star at 3.79: a distance in flat contradiction is exactly what the ratio guard exists to refuse, and the number to fix is upstream. The 2 pc "rematch" apparatus is gone. build.ts recomputed every match after fetchExoplanets had already written the file — at a different tolerance, so the log reported a match count the data did not contain — and the offline entry point that persisted it had no caller. One matcher, one set of constants, used once. The archive cache is now keyed by a hash of the TAP query, so a response cached before the proper-motion columns cannot serve rows without them, where a missing cell would quietly read as "does not move"; the row's astrometry is stored with each planet, which is what made these tolerances measurable offline in the first place. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016jxMkwA2rbicdGxHosecYi |
||
|
|
dc2ce08694 |
Answer the review: direction settles distance, brightness is one-sided, and a lost id stops the scene
Three findings from the adversarial review of the merge, all reproduced. The distance test was hiding 1 489 stars that sit under an arcsecond from their Gaia entry with a Hipparcos parallax off by half — thirty of them at a false few parsecs from the Sun (HIP 82724 at 3.7 pc, where Gaia has it at 62.8) — and the first audit did not see them because it counted residual doubles through the same 50 % filter. Under three arcseconds the distances are now not consulted: a coincidence of direction that close is never chance at this depth (the quarter-degree shift finds none), and the parallax is the thing to fix. Brightness keeps its say at any separation, and is now one-sided: a folded entry may be five magnitudes fainter (a red dwarf in V against G) but not one brighter, because an entry a magnitude brighter than what is already at that spot is a primary Gaia does not carry — Almach, Alfirk and Ashlesha had all been folded into their companions' entries, 93 in all. The sky grid wraps at 0h. The Gaia query orders by source_id after G, so the row order — and the ids assigned from it — is a function of the archive's content rather than of the server's plan for 20 064 ties; the cache key is a hash of the query. And a bookmark to a star id the catalogue no longer holds — 56 000 Gaia ids change with this — sent the scene through reconcileSelection, enterSystem, its decline, finishTransition and reconcileSelection again until the stack overflowed. The selection is cleared instead, at the one place every path goes through. Regenerated: 423 641 stars, 57 512 HYG identities on Gaia positions, no HYG id or name lost, no star within 20 pc left with an unclaimed Gaia entry under an arcsecond. 403 HYG survivors still have an unclaimed Gaia entry within 60": 13 under an arcsecond, where the brightness guard does not trust HYG's magnitude, and the rest components 3" to 60" from their counterpart. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QL6F9Bgfh8SgAiAAcPB9Hw |
||
|
|
08534279fb |
Bring Gaia to HYG's epoch before merging, and keep a star's name when it matches
Gaia DR3 gives positions for J2016.0, HYG for 2000.0, and the merge matched them on the sky to one arcsecond without propagating any proper motion. Sixteen years of motion is 62" for Proxima and 166" for Barnard's Star, so every star faster than ~62 mas/yr — most of the nearest ones — was kept twice, some 23 000 in all. The slow ones were matched, and lost: the merge kept Gaia's row whole, so 102 proper names, 1 336 Bayer/Flamsteed names and 32 000 spectral types became "Gaia DR3 <id>" and "Unknown", and 92 named exoplanet hosts handed their planets to their anonymous twin. Gaia is now asked for its proper motions and carried back to J2000 before it leaves the fetcher. HYG is placed from its own x/y/z columns, which are right where its `ra` is not: that column was carried from the Hipparcos epoch without the cos δ its motion needs, 17.9" off for Proxima. A match combines the two entries — Gaia's position, HYG's name, type, magnitude, colour and id — instead of choosing one. The tolerance is 15" with a five-magnitude guard, both set by measurement: 55 457 pairs sit under 1" once the epochs agree, the Gliese-only entries up to 12" (Ross 248), shifting every entry a quarter of a degree finds 16 chance neighbours at 15", and the guard keeps Sirius out of Sirius B's entry. Entries of one source are never merged with each other: the 1 411 Gaia doubles resolved under 1" are two stars, not one. Regenerated: 425 071 stars (was 447 410), 56 082 of them Gaia positions carrying HYG identities; no HYG id or name lost; the sixteen stars nearest the Sun carry no survey designation; 196 residual doubles, all components 17" or more from their counterpart. Five planets of four bright giants (7 CMa, HD 81688, omi UMa, xi Aql) lose their host link: their Gaia distance sits 0.7–1.1 pc from the archive's Hipparcos-based one, past the 0.5 pc the host match allows. Matching hosts on the sky rather than in space, as the merge does, is the follow-up. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QL6F9Bgfh8SgAiAAcPB9Hw |
||
|
|
f14e252b19 |
Name the neighbours that have a name, before the ones that only have a number
The neighbour ring exists to say where you are. Since the catalogue refresh it has been spending one of its four places in Sol on "Gaia DR3 5853498713190525696" -- a nineteen-digit survey id for the star printed beside it as Proxima Centauri, the same star twice -- and that duplicate row pushed Barnard's Star off the ring altogether. 91.9% of the refreshed catalogue is named that way. Named stars now come first, and survey designations fill in only where fewer than four named ones are in reach. The line between the two is the one the catalogue format already draws: a name is a designation when it is what the star's source would generate for it. Judged by the prefix rather than by rebuilding "prefix id" from the row, because the number after "Gaia DR3" is the survey's own id, which the 32-bit row id cannot hold -- a round trip through the id would have called every one of those stars named. The preference lives on the index as `nearestPreferring`: the preferred pass exhausts the search before the fill runs, so a named star is never outranked by a nearer unnamed one. That is the whole point of asking. The end-to-end spec names Barnard's Star again, on purpose. The four nearest named stars to the Sun are a fact about space, not about which catalogue was refreshed last, and without this change that is exactly the label that vanished -- checked by running the spec with the preference stashed: it fails on that line, and passes with it back. npm test 609/609, npx playwright test 16/16 under CI=true --workers=2. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014fcUfL82nvyh9VebX1Fz6w |
||
|
|
68a919bd84 |
Route between stars, through the crossings a chosen range allows
The map could say where a star is and what is near it, and nothing about getting from one to another. This adds the question and the answer: pick a departure and a destination, choose how far a single crossing may be, and get the chain — how many jumps, how far in total, and every star on the way, each one a step you can fly to. A jump link is not a feature of space. There are no corridors out there; a link is a question asked of the catalogue, which is why the range is the user's control rather than a constant. Two facts about that catalogue decide what the answers look like, and both are stated in the code because they read as defects otherwise. It is magnitude-limited, so it is dense around the Sun and thins with distance — within 50 pc a 3 pc range links 99% of it into one piece, while over the whole 250 pc reach the same range leaves most stars alone. And a gap in it is a gap in what has been catalogued, not in what is there. That is why "no route" is not the end of the answer. Where no chain exists at the range asked for, the panel says which range would open one — the chain whose longest hop is as short as possible, found by the same search with the cost of arriving somewhere being the worst hop taken rather than the sum — and offers that number as a control to accept. Departure defaults to wherever the view already is, so one field is usually enough. Sol to Vega at 3 pc: four jumps, 10 pc, by way of Barnard's Star, Struve 2398 B and HD 155876. Narrow it to 0.8 pc and it says 2.26 would reach. The graph is drawn as one buffer of line segments and the route as a second, brighter one over it, with the graph stepping back while a route is up: near the Sun the links are a haze, and a thread through a bright cloud is not a thread. Both fade out with the local layer, since from outside the Galaxy the graph is a smear. Two measurements shaped this. Asking the index for each star's neighbours in turn — sixty-eight thousand sorted lists, thrown away — took eight seconds; the grid now walks its own cells once and pairs them, which takes a quarter of one. And the range control emits per pixel dragged, so the rebuild waits for the hand to settle. Three defects fixed on the way, all older than the routing: hud-acquire animated with fill-mode `both`, which leaves its closing keyframe applied for good — and that keyframe carries a clip-path. Every panel wearing it has been clipping its own box ever since, so anything that had to escape one was cut away and could not even be clicked. Nothing had needed to escape until this panel's dropdown opened upward. The routing fields returned nothing when typed into before the catalogue finished loading, and stayed nothing until the next keystroke. The options are derived from the query and the index together now, so they appear when the second of the two arrives, whichever that is. And a link was `3-7` walking one way and `7-3` walking the other, which is two links to anything comparing them. Verified: build clean, 571/571 unit, 9/9 end-to-end including two new specs — one plotting Sol to Sirius, one narrowing the range until there is no route and accepting the one it names — design detector clean, screenshots at 1440x900 and 390x844. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016jxMkwA2rbicdGxHosecYi |
||
|
|
44c6a1f15e |
Name the neighbours, from inside the system
A system view could say everything about the star it was inside and nothing about where that star was. The four nearest catalogue stars are now named around the edge of it, each with its distance, each a button that flies there — so a chain of neighbours can be walked without pulling back out to the field between hops. These are bearings, not sky positions, and that is the one deliberate compromise here. A true direction was tried first and does not work: at this field of view the visible cone is about 30 degrees, so on average one neighbour in fifteen falls inside the frame — measured, not guessed, at one label of four in Sol and none at all after a small orbit. What survives the ring is the half of the direction a viewer can act on, which way to turn to face it, and the ring reads as instrument rather than as scene because it sits at a fixed radius. Real distance was never an option: Proxima is 268 000 AU from Sol, thirteen far planes out, so the distance goes on the type line. Proximity is answered by a new pure module rather than by a scan. A uniform grid over the catalogue answers both "the k nearest to this star" and "every star within n parsecs", the second being what the jump-link graph in the next PR is built from — one scan per node, and the quadratic would show. Its spec pins the grid against a brute-force sweep of a pseudo-random cloud, because a spatial index is an optimisation and never a different answer. Where the ring meets the HUD, the HUD wins: placement is given the boxes the readout, the strip and the object card occupy, and slides a name along the ring until it clears them, or drops it rather than print it half hidden. That rule is a pure function with its own spec. Four defects found while verifying this, three of them older than it: The dock's flex column was pointer-events-auto and as wide as its strip, so an invisible band above the strip swallowed every click in it — including, but not only, a neighbour's. The column is transparent now and each surface opts back in. The ring was sized against the frame's height alone, which on a phone held upright put it a viewport and a half wide: no neighbour was reachable on any portrait screen. It is sized against the shorter side. Picking a search result reopened the readout, which on a narrow viewport is a sheet over most of the scene — reopening it onto whatever was just flown to. Below sm it now folds away. A selectable label's two lines are adjacent spans, so it announced as "Sirius2.64 pc"; it carries an explicit label saying what it does. Verified: build clean, 558/558 unit, 7/7 end-to-end including a new spec that flies Sol to Barnard's Star by its label, design detector clean, screenshots at 1440x900 and 390x844 in Sol and Proxima Centauri, and the keyboard path walked: both names are in the tab order, focusable, with the accent ring. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016jxMkwA2rbicdGxHosecYi |
||
|
|
748cb8927e |
Rebuild the map chrome against the Star Citizen starmap
Until now this was built from memory — the reference site is blocked by this environment's egress policy, so the resemblance was asserted rather than checked. Five screenshots of the real thing arrived, and this is what comparing against them changed. Labels say what a thing is, not just what it is called. Every label is now two lines: the name, then its type in smaller, wider-tracked, dimmer capitals. This is the single most characteristic element of the reference and it appears in every frame of it. It also settles a real ambiguity — in a map that mixes scales, "Orion" is an arm, a nebula and a constellation, and nothing about a bare name said which one a label pointed at. For stars the type line distinguishes "System" from "Star", which is the one thing it can say that the map could not otherwise show: which points are somewhere you can actually go. The system view had no body labels at all, where the reference labels every planet. It does now, which turned out to need two supporting changes. The overlay had only ever added and removed labels, never moved them, because stars do not move; planets do, so an existing label is now repositioned rather than left where the body used to be. And the inner four planets printed on top of each other in exactly the clump the star labels were already spread to avoid — so that logic is now shared rather than duplicated, with system bodies ordered outermost-first. Closing in reverses it by itself: the outer orbits leave the frame, their labels drop, and the inner planets take the space. The chrome follows the reference's layout. The scale ladder is a row of chamfered tabs at the top left rather than a vertical list of diamonds at the middle left, and a nameplate across the top centre says what the view is holding. The centre reticle is a hexagon, which is how the reference locks onto a body, and stays distinct from the rectangular panel chrome. Not copied: the ARK/RSI logos, wordmarks, and the bottom-right tool tabs. The first two are someone else's brand, and the third would be four tabs opening features this app does not have. Two e2e assertions moved off bare text matches onto the readout panel's own title. The nameplate names the same thing the panel does, so "is 'Local Stars' on screen" became ambiguous — the assertion, not the design, was what had to give. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WaySiNst4HhDXBHnMy8p5G |
||
|
|
efa9e4084a |
Draw the whole catalogue, and build the aggregation the rest would need
Two things, one verified and one that cannot be. The render budget is now the whole catalogue: 68388 stars, one instanced draw call, which is what a GPU should be asked to do. The budget itself stays, because the catalogue is meant to grow past what any machine should draw at once — Gaia alone could contribute a million — and at that point the selection is what keeps the field legible rather than a grey wash. A `?stars=` override handles the machines that cannot, including the software rasterizer the end-to-end suite runs against, whose frame rate is two orders of magnitude below a real GPU's and which was measuring the rasterizer rather than the app. The aggregation is the second thing, and none of it has run. Every ESA, NOIRLab, SDSS and Euclid endpoint is unreachable from here — only GitHub raw is, which is why HYG and OpenNGC are the current sources. So this is infrastructure and a Gaia query written against the published DR3 schema, not data. What the framework encodes is that these surveys are not interchangeable. The distinction is not size but whether a catalogue knows how far away its objects are, because a 3D map cannot place a star it only has a direction for. Gaia is the only one of the five that can add stars here, because it is the only one that measures parallaxes. DECaPS2 has fifty times Gaia's object count and photometry alone — not one of its 3.32 billion objects can be placed in depth. Euclid's bulge is 8 kpc away, where a parallax is microarcseconds; its contribution would be imagery. SDSS-V and SAGA are keyed to stars something else already places, so they enrich rather than extend. Those roles are recorded as data the ETL prints, not as prose that can drift. Overlapping catalogues are reconciled on direction rather than on 3D proximity, which is the one non-obvious part. Two surveys agree on a star's direction to within an arcsecond and disagree on its distance by tens of per cent, so a star at 200 pc is 50 pc from itself between catalogues while being unmistakably the same object. Matching in 3D would need a tolerance so loose it swallowed real neighbours. The better parallax wins where both reach; where only one does, the star stays. Names become dense-with-holes with a source dictionary, because a survey catalogue has no proper names — writing "Gaia DR3 4472832130942575872" once per star would cost 25 MB per million to repeat what two adjacent fields already say. An empty entry costs three bytes and is regenerated on load. The Sun needed its own case in the merge: it sits at the origin, has no direction to compare, and appears in every catalogue. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WaySiNst4HhDXBHnMy8p5G |
||
|
|
29fd92d118 |
Widen the star catalogue, and separate what is drawn from what is known
The map held 8750 stars within 50 pc and rendered 371 systems. Both were lower than they needed to be, for different reasons. The star catalogue was capped by its own encoding as much as by the cutoff: one JSON object per star, eight key names repeated each time, 157 bytes a star. At the range HYG actually reaches that is 17 MB to download and parse before the first frame. So the numbers move into two binary column stores — positions in stars.bin, which the GPU is handed verbatim, and id/magnitude/colour/spectral index in stars-meta.bin — and the JSON keeps only the strings, with 2600 distinct spectral classifications collapsed to a dictionary. The layout is defined once, in star-catalog.ts, and the ETL and the app both use it, so the writer and the reader cannot drift. The cutoff then goes to 250 pc: 68388 stars, 7.8x as many for 1.7x the bytes. That is where HYG's measurements stop rather than a round number — 98.6% of its rows are Hipparcos, whose parallaxes are good to about a milliarcsecond, so beyond 250 pc it would be plotting noise. Drawing all of them is a separate question from knowing them, and it is answered separately. The field draws a budget: every star inside 25 pc, because the nearest are faint red dwarfs and Proxima Centauri is magnitude 11, then the brightest of everything beyond. Search, navigation and the planet cross-reference still see the whole catalogue. A real GPU would draw all 68388 without noticing; the budget is for the machines that would not, and it is one constant. Systems were limited by something else entirely. The archive data already shipped named 4735 host stars and only 388 resolved, because the rest lay outside a 50 pc catalogue — and the cross-reference kept only its own result, so redoing it meant re-downloading an archive that is not reachable from here. Host coordinates are now stored with each planet, and the match is re-resolved at build time against whatever catalogue the run produced. Even name matching alone, which needs no coordinates and so works on the records already shipped, rescues 335 planets across 238 systems: 371 renderable systems become 609. Two selection rules were tuned for a 50 pc bubble and no longer fit. Tethers followed the Sun's nearest neighbours, which are a speck at this range, and now follow the brightest; labels were ranked by proximity, which named whatever sat nearest the middle of the screen, and are now ranked by brightness — so the view names Canopus, Achernar and Spica rather than a clump of catalogue designations. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WaySiNst4HhDXBHnMy8p5G |
||
|
|
ac296f5133 |
Derive a surface for every body that was never photographed
Fifteen bodies here have a real photograph. Every exoplanet does not, and never will on current instruments — none has ever been imaged — and nor do several of the solar system's own moons. Those all shared one crude stand-in: a few noisy bands tinted by category, cached per colour, so every exoplanet in the app was literally the same picture. They now get a surface reasoned from what has actually been measured. The chain is standard at every link. A host star's luminosity comes from its catalogued apparent magnitude and its parallax distance — that pair is exactly an absolute magnitude — plus a bolometric correction for its spectral class. The correction is not optional: an M dwarf radiates most of its light in the infrared, so its visual magnitude understates it more than tenfold, and M dwarfs are what most nearby planet hosts are. Luminosity and the semi-major axis then give an equilibrium temperature, mass and radius give a bulk density, and size, temperature and density together give a class of world. Checked against the solar system the temperatures land on Earth 255 K, Jupiter 112 K, Neptune 46 K, all within a kelvin or two of published values, and 51 Pegasi b comes out at 1227 K against a published 1200. Each class carries a palette reasoned from its chemistry — methane absorbs red light, which is why the ice giants are blue — and a structure: zonal bands for a body with a fluid envelope, because a rapidly rotating atmosphere organises into them, and fractal terrain for one with a solid surface. Polar caps grow and shrink with the derived temperature, which is the clearest visible consequence of the whole chain. The generator samples three-dimensional noise along the sphere rather than a flat field, so there is no seam to stitch at the antimeridian and no pinching at the poles, and it writes into a byte array rather than a canvas — a pure function, testable, with no 2D context to be unavailable. Two things the derivation cannot do, both stated on screen next to the measurements it rests on. Equilibrium temperature ignores greenhouse warming and internal heat, so Venus comes out at 300 K against a real surface of 737 K and Io, kept molten by tides, classifies as ice. And these are illustrations: reasoned, but not observations. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WaySiNst4HhDXBHnMy8p5G |
||
|
|
2e525fb5c3 |
Open the map out to the whole Milky Way
The map stopped at the catalogued 50 pc around the Sun — 0.33% of the Galaxy's width — and looked like a point cloud with a search box. Adds the galactic scale above it and the heads-up display the reference map is built from. The Galaxy is not a third coordinate space. It is the same parsec space four orders of magnitude further out, so the model and the star field crossfade against camera distance instead of switching, and the Sun stays where it really is: 8.18 kpc out, on the Orion Spur, between the Sagittarius and Perseus arms. The depth range scales with that distance — one fixed near/far pair cannot both fly into a star and hold the Galaxy. The structure in shared/astro/galaxy.ts is measured: the directions of the centre and the north galactic pole, which fix the disc's 63 degree tilt against the celestial equator; the Sun's galactocentric distance; and a radius, azimuth and pitch angle per arm. The particles scattered around it are not, and cannot be — dust hides the disc, so no catalogue holds the Galaxy's stars. The view says so, and the model fades out before the camera reaches the 50 pc where the real stars are. The rest is the look: polar grids lying in the galactic plane with drop lines from the Sun's neighbours, a scale ladder, a readout panel, range, reticle and frame brackets. Two things had to give way for it. The deep-sky shell is the sky as seen from here, so it dissolves rather than letting the camera fly through a wall of nebulae, and so does the skybox, which is a photograph taken from inside the thing now being viewed from outside. Labels are picked by screen separation rather than distance alone: the Sun's fifteen nearest neighbours are all inside four parsecs and printed as one unreadable clump. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WaySiNst4HhDXBHnMy8p5G |
||
|
|
2293585940 |
Put orbits and stars in the same reference frame
The app's two sources disagree about which frame they are in, and nothing reconciled them. HYG star positions are equatorial J2000 — that is what raDecDistanceToXyz produces and what the galaxy view renders directly. Orbital elements come from JPL Horizons, whose default reference plane for element output is the ecliptic, and the ETL never overrides it. The two are tilted 23.4 degrees apart, so the orbits sat that far off the sky they are drawn against. Confirmed rather than assumed, from both ends: the Horizons request in lib/horizons.ts sets no REF_PLANE, and the resulting solar-system inclinations are 0 to 17 degrees with Earth exactly 0.00 — which is only true of the ecliptic, since Earth's orbit defines it. eclipticToEquatorial now rotates orbit positions into the scene frame, so a direction means the same thing in the galaxy view and the system view. The rotation is about the vernal-equinox axis, which both frames share. That exposed a presentation problem the old code had been hiding. The renderer mapped the propagator's z straight onto the scene's vertical, which silently redefined the frame but did make systems render flat. In a properly equatorial scene, orbital planes lie 23.4 degrees off the scene's own axes, so a system would be presented edge-on. Rather than rotate the world back into a comfortable pose — which would only put the orbits at odds with the sky again — the camera now settles relative to the orbital plane: a three-quarter view about 37 degrees off the ecliptic normal. The arrival still begins along the approach direction and swings round as it settles, so the transition stays continuous, and the framing is now the same every time rather than inherited from wherever the camera happened to be. Tests: 247 passing, up from 237. The frame tests are the discriminating kind — Earth's orbit must lie perpendicular to the ecliptic pole rather than to the scene's vertical, and must reach 23.4 degrees of declination a quarter orbit on, where it used to read zero. Verified in a browser against Sol and Gl 357. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WaySiNst4HhDXBHnMy8p5G |
||
|
|
f241b093eb |
Draw the 1509 exoplanets that were being silently dropped
The system renderer required both a semi-major axis and an eccentricity before it would place an exoplanet, even though resolveOrbitalElements already defaults every other missing element. The archive publishes an axis far more often than an eccentricity: 3895 records have one and only 2386 have both, so 1509 planets were dropped for want of a value that can simply be assumed. A missing eccentricity now defaults to 0, a circle. That is the conventional assumption for an orbit whose shape has not been constrained, and it is the only honest option available, since the axis alone says nothing about elongation. The effect is not subtle. 18 systems gain planets, and seven of them previously rendered as a bare star with nothing around it at all: Gl 357 goes from zero planets to three, HD 176986 likewise. Beyond the effect today, a user could already reach one of these planets through search and its detail page, then jump to its system and find it missing from the very system it belongs to. isPropagatableOrbit replaces the old inline guard and also rejects what the old one never checked: a non-positive axis, and an eccentricity of 1 or more. Those are escape trajectories that no ellipse describes, and propagating them anyway does not throw — it yields NaN, which reaches the vertex buffer and poisons the geometry's bounding sphere, disabling culling for the whole object rather than just the bad orbit. Being a type guard, it also lets the caller drop a seven-line field-by-field copy of the orbit. Fixes a label leak found while verifying this in the browser. Galaxy star labels were being cleared on entering system space but immediately recomputed, because the tick gated them on `currentStarId`, which is not assigned until the arrival flight finishes a second later — so parsec-scale names sat pinned over the system. Both label and orbit updates now gate on which group is actually visible, which is true throughout the transition rather than only at the end of it. Tests: 185 passing, up from 171. Verified in a real browser: GJ 1151 draws the orbit and marker it gained, and no labels survive into the system view. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WaySiNst4HhDXBHnMy8p5G |
||
|
|
8d8c65bdb2 |
Propagate exoplanets with their real orbital period
Every exoplanet was propagated with gmForParent(undefined) — the Sun's gravitational parameter — so the whole catalogue orbited as though each host were exactly one solar mass. Most hosts are red dwarfs far lighter than that, and a heavier central mass pulls harder and shortens the period, so their planets were whirling round much too fast: TRAPPIST-1 is 0.09 solar masses, and its planets were completing an orbit in roughly a third of the true time. pl_orbper was already in the TAP query and was being discarded on the way into the record. It is now kept, along with st_mass. A period and a semi-major axis together pin the host's gravitational parameter exactly, via GM = n^2 a^3 — no stellar model, no assumption, just the inverse of the orbitalPeriodDays helper that was already there. resolveGravitationalParameter picks the best available source: the measured period, else the published host mass, else one solar mass as before. A derived value implying something outside 0.01-150 solar masses is rejected and falls through, since a period and axis taken from disagreeing solutions would otherwise send a planet spinning at a visibly absurd rate. Note the direction of the error, which is the opposite of what it looks like: assuming a *heavier* host than reality makes a planet orbit *faster*. A test pins it, and caught me stating it backwards first. The NASA Exoplanet Archive is unreachable from this environment (egress policy returns 403 on CONNECT), so exoplanets.json cannot be regenerated here and still carries no periods. Behaviour is therefore unchanged until `npm run etl` is run somewhere with archive access, at which point every planet with a published period starts moving correctly with no further code changes. build.ts reports how many records gained a period, and rejects non-positive ones. Tests: 171 passing, up from 151, including a new end-to-end check that TRAPPIST-1 b with its real period completes exactly one orbit in 1.51088 days and sits a full diameter away at half that. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WaySiNst4HhDXBHnMy8p5G |
||
|
|
4aca223027 |
Regenerate the star catalogue, fixing 2331 names and 875 colours
Two ETL bugs, both fixed at the source and then re-run against HYG. Star ids,
ordering and positions are all unchanged, so stars.bin is byte-identical and
every exoplanet cross-reference still resolves.
Names. HYG's `gl` column already carries its own catalogue prefix ("Gl 581",
"GJ 3512"), unlike the bare numbers in `hd` and `hip`, so prefixing it again
produced 2331 of 8750 stars named "Gl GJ 1076". That corrupted three surfaces at
once: search, the on-screen labels, and exoplanet host-star name matching, which
compares normalised names and could never match "glgj1076" to "gj1076".
Colours. `Number(row['ci']) || 0` cannot tell a blank cell from a real zero, and
0 is a real B-V colour index meaning a hot blue-white A-type star. All 875
affected stars turned out to be blanks — the catalogue contains no genuine zero
inside the distance cutoff — so several hundred red dwarfs were rendering
blue-white. colorIndex is now `number | null` rather than defaulted, because any
numeric default is indistinguishable from a measurement.
Consumers resolve the gap from the spectral type instead. That needs real
parsing: HYG's `spect` column runs to 134 distinct spellings among the affected
stars alone, including a bare lowercase "m" for 354 of them, plus "k-m" ranges,
"dM4" luminosity prefixes and "K:" uncertainty flags. 622 of the 875 recover a
class this way — 497 of them M-class — and the remaining 253, which carry no
classification at all, fall back to neutral white.
The parse is anchored at the start of the string rather than scanning it. A scan
is the obvious implementation and is quietly wrong: the ETL writes the literal
"Unknown" for unclassified stars, that contains a K, and every one of those 253
would have been classified as an orange K-type. A test covers it.
Also lifts parseOptionalNumber out of fetchExoplanets into lib/csv, where both
fetchers now use it, and gives magnitude a faint default instead of 0 — no
current star is affected, but 0 would mean "as bright as Vega" and render an
unphotometered star as one of the largest points on the map.
Tests: 145 passing, up from 116, including the first coverage of
StarFieldRenderer. Build, both typechecks and the Playwright suite are green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaySiNst4HhDXBHnMy8p5G
|
||
|
|
06cf7d2a15 |
Fix four defects a user hits in the first minute
Found by surveying the codebase against the plan; each was verified against the
committed assets or the running app before being touched.
TRAPPIST-1 was orbiting the Sun. The Exoplanet Archive leaves sy_dist blank for
some systems, and fetchExoplanets.ts read it with bare Number() — Number('') is
0, which is finite, so it slipped past the Number.isFinite guard in
resolveHostStarId, placed the host at the origin, and matched Sol at distance
exactly 0. 127 records shipped with hostStarId 0, all seven TRAPPIST-1 planets
among them, and the system view filters on that id, so drilling into Sol drew
127 alien worlds inside the real solar system.
Fixed in three places: resolveHostStarId now rejects a non-positive distance
(the robust guard, covering every caller), fetchExoplanets.ts uses the
parseOptionalNumber that already sat unused in that same file for ra/dec/dist,
and validateExoplanets asserts nothing ever resolves to the Sun again — the Sun
has no exoplanets, so that tripwire costs nothing and is permanent.
The archive's endpoint is blocked by this environment's egress policy, so the
ETL cannot be re-run here. The committed asset was corrected in place instead,
which is safe because the outcome is deterministic: the name path runs first and
none of the 127 resolve by name, so all of them reached id 0 positionally and
the fixed pipeline yields null for exactly that set. Cross-referenced hosts drop
from 761 to 634; record count is unchanged.
Dragging to rotate selected stars. Selection was bound to the raw click event,
which browsers fire on release however far the pointer travelled and which
OrbitControls does not suppress — so any drag ending over a star launched a
camera flight, and in system view routed away to /body/:id. Now tracks
pointerdown and ignores a release more than 5 px from it.
Ghost systems accumulated on every star-to-star hop. SystemOrbitsRenderer.dispose
released geometries and materials but never detached its group, so old orbit
lines stayed parented forever — still traversed and re-uploaded each frame with
disposed geometries, drawn over the new system and unpickable. dispose() now
detaches and clears.
Galaxy star labels stayed pinned inside the system view. They are CSS2D objects
parented to the scene rather than to galaxyGroup, so hiding the group left up to
15 parsec-space names clumped over the system's star. Cleared on entry. Also
gated the per-frame Kepler propagation on actually being in a system; it ran in
galaxy view too, because the renderer is never nulled on exit.
Tests: 116 passing, up from 112. Build, both typechecks and the Playwright suite
are green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaySiNst4HhDXBHnMy8p5G
|
||
|
|
3a859360ba |
Add the deep-sky backdrop, the last unbuilt piece of the plan
The design doc scopes deep-sky objects as a galaxy-view backdrop and lists fetchDeepSky.ts, deepsky.json and deepsky.model.ts, but none of it existed — it was the only part of the plan with no implementation behind it. ETL: fetchDeepSky.ts pulls the OpenNGC catalog, classifies each object as a galaxy/nebula/cluster, and keeps the ~460 worth drawing (everything Messier, everything with a common name, and anything brighter than magnitude 9) out of ~12,000 mostly-anonymous rows. build.ts runs it and validates the output. Distances are the hard part: OpenNGC has no distance column, and both fallbacks fail for the best-known objects. M31, M33 and M42 are Local Group members whose redshift is negative or absent, and a galaxy's catalog parallax comes from a cross-matched foreground star — 6 mas for M31 would put a 780 kpc galaxy at 167 pc. So records store a unit direction on the celestial sphere rather than a position (the line of sight is always known precisely, and the objects are drawn on a fixed backdrop shell where true distance is unusable anyway), and distance is optional metadata carrying its own provenance. Parallax is trusted only for galactic objects, redshift only above z=0.003 where expansion outweighs peculiar velocity. 330 of 463 get a distance; the rest honestly report none. Rendering: DeepSkyRenderer paints the objects as soft additive billboards on a 2500 pc shell — clear of the 50 pc star field, beyond the camera's 2000 pc orbit limit, and inside its 5000 pc far plane. Size comes from real angular extent, so Andromeda is six times wider than the full Moon, clamped at both ends. Sprites rather than points because the WebGPU backend caps point primitives at one pixel; materials are shared per kind and brightness band, so 460 objects cost nine of them. The brightest dozen get permanent labels, which needed the label overlay to accept string ids alongside numeric star ids. The backdrop is decorative, so a failure to load its dataset is logged and the star field comes up regardless. Also documents the app in the README, which until now covered only the plugin marketplace. Tests: 112 passing, up from 54. Build, both typechecks and the Playwright suite are green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WaySiNst4HhDXBHnMy8p5G |
||
|
|
d7e8ea1d4d |
@
Add star-map Angular app, ETL pipeline, and caveman plugin Angular 3D star map (galaxy/system/body views, Three.js rendering, navigation store) plus the NASA ETL tooling that builds the star, exoplanet and solar-system datasets, Playwright e2e suite, and the cs:caveman Claude Code plugin (command, agent, skill). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> @ |