f8582b78d0c8ba576429981ee08f74d67fd6252e
33
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
f8582b78d0 |
Test the star's disc itself: its photograph, its colour and its darkening toward the limb
|
||
|
|
17cf11b6b1 |
Tint a planet host in the star field at the archive's temperature, which its disc is drawn at
|
||
|
|
69a052730f |
Test that the system view warms a host's planets by the luminosity the archive gives it
|
||
|
|
8f99335bae |
Give a luminosity below a hundredth of the Sun's two figures, so Proxima reads 0.0015 L☉ not 0.002
formatLuminosity printed three decimals from a thousandth to 1 L☉, which leaves one figure below
a hundredth.
|
||
|
|
a8f394cf57 |
Read a giant's temperature off its type as well as its correction, so a radius has one source
|
||
|
|
18faa6d0b2 |
Classify a star for the rows that list it, not every star each time the index is built
The search index and the route index gave every one of the 455 571 stars a subtitle up front through spectralClassification, which filtered the dwarf table afresh on each call; the star field's tints read the same table once per BP−RP star. Both indices now carry the star itself and classify it only for the rows shown (entrySubtitle): eight search results, a few route options. The two filtered columns of the table are built once. Node, over the shipped catalogue, five runs: classifying every star 121-167 ms before, 56-62 after hoisting the table alone; reading the sequence at every BP−RP colour 187-216 ms, now 111-127. In the app on :4302, two cold loads each, the long task when the Search tab opens was 264-380 ms (median 280) before and 164-276 ms (median 171) after; the longest boot task 863 and 875 ms before, 654 and 741 after. Tests: the search spec checks its index holds no classification and the row still reads "Star · ~M8"; the scene spec now reads the route options, and checks its index holds none either. Controls: classifying every star in the search index, doing so in the route index, and route options printing the index's subtitle each fail the named test. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> |
||
|
|
f8af0ecf6e |
List stars in search and routes by the type their colour gives them, and say where archive positions come from
|
||
|
|
d1aa22ed61 |
Frame a giant so its disc sits inside the ring its neighbours are named on
The system view names a star's neighbours on a ring at 0.74 of the view's tighter half-extent, whatever the star. A giant was framed to fill the frame, 2.4 radii out, which the three-radius closest approach then overrode, so it settled at 3 radii with its disc projecting to 0.758 of the half-extent: in the app, Betelgeuse's disc was 379 px on a 1 000 px view against the 370 px ring, and HD 39374 and HD 38118 were printed on it at about 1.3:1 contrast. The star's term in the framing distance now places its silhouette, asin(R / d), at half the tighter half-extent, which puts the camera 4.4 radii out on a 50° view. Measured on :4302 at 1600 by 1000: Betelgeuse settles at 13.9 AU (closest approach 9.5) with a 250 px disc and Antares at 14.1 AU; the nearest corner of any neighbour's name is 292 px from the centre, so none is on either disc (it was two and one of them before). Closing in to the closest approach can still bring the disc over the names; that is the viewer's choice, not the arrival's. This also gives the star's term a job: at 2.4 radii it never exceeded the closest approach, so dropping it changed nothing (the previous commit's one surviving control). Controls, each failing its named test: the star left out of the framing (1 of 788 failed), the star framed to fill the frame (2 of 788), and the disc taken as flat (1 of 788). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> |
||
|
|
5aac46de08 |
Draw a star nothing gives a size or temperature for as a grey point, not as the Sun
The system view drew every star without a radius at the Sun's radius, and every star without a temperature in the Sun's colour. PSR J1719-1438, a neutron star 10 km across, came out 1 R☉ wide, wider than its planet's 0.0044 AU orbit, and b spent nine tenths of its orbit inside it; Procyon B, a white dwarf of 0.012 R☉ with a type the parser does not read and no colour, was a Sun 81 times too wide in the Sun's colour. Such a star is now drawn at 150 km, under the three-pixel floor from anywhere the camera can go, so the floor's point is what shows, and its disc keeps the photograph's own grey instead of the Sun's tint. Its light stays white, which is the photographs' own light rather than a claim about the star. 2 858 stars take this after the clamping in the previous commit, against 3 077 drawn at the Sun's radius before it. In the app on :4302: PSR J1719-1438's star is drawn at 1.7×10⁻⁴ AU (the floor, 168 times its geometry) with b 0.00417 AU from its centre, outside it; Gl 280B at the floor, tint (1, 1, 1) and no radius on its card; Proxima at 0.141 R☉, tint (1, 0.459, 0.135), light (1, 0.523, 0.165), card "Luminosity 0.002 L☉" unmarked and "Radius 0.141 solar radii". The scene's use of the star's surface had no test: every star in the scene spec was drawn at the Sun's radius, and drawing all of them so, lighting planets white, tinting every disc as the Sun or closing the camera to the Sun's clearance all passed. The spec now gives Proxima its planet's archive row and adds Antares and Procyon B, and three tests enter them. Controls, each failing its named test: every star at the Sun's radius (3 of 787 failed), an unmeasured one at it, the light without the temperature, the closest approach for the Sun, the Sun's tint for a host, the Sun's tint for a star without a temperature, and the card without the surface (1 of 787 each). Dropping the star from the framing distance was not caught: its term, 2.4 radii, never exceeds the three-radius closest approach the camera is clamped to, so it cannot change where the camera settles. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> |
||
|
|
56870fd9fe |
Merge pull request #31 from avalon-vanguard/fix/grid-rings
Size the distance rings by what the frame reaches, and keep their labels off the star names |
||
|
|
0c5efec505 |
Measure the ring span along the plane the rings lie in
The span went to `distanceRings` as the target's straight-line distance from the Sun, but a ring of radius r passes within |r - p| of the view's centre, where p is how far out that centre is *along* the galactic plane. For a target above the plane the two differ by its height, so the band was centred on a radius no ring has — and `ringLabels` picks its bearing by comparing its own in-plane distance against the innermost ring, a comparison the new first ring quietly broke. Two comments and a constant, from the same review. A frame short of the survey edge gets its callout only when its last ring overshoots it: 245 pc does, 235 pc does not, which is now a test rather than a sentence. The ring count can reach 16, not 14, now that the span need not start at the Sun. And a ring label was measured as 135 px of star name when "50 pc" is a third of that, which rejected rungs a hand's breadth clear of the name: RING_LABEL_REACH_NDC, 0.23, is the widest of them — "1.5 kpc" with "Survey edge" under it. Three mutants, three caught. Measured again in the app: unchanged for a star in the plane (4 labels at 20 pc above it, 7 at 2 pc), and the rings now follow the plane for one 195 pc above it rather than ringing a place the grid does not reach. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016jxMkwA2rbicdGxHosecYi |
||
|
|
7fba48c808 |
Answer the review: size the rings to the band the frame covers, and clear the text
The rings are centred on the Sun and the frame need not be. Sizing their step from how far the frame reaches — 210 pc for a star at 190 with the camera 20 pc back — gives 20 pc rings at 180 and 200, both outside a frame 19 pc deep, so a view away from the Sun still had no ring on it and no ladder of labels either. `distanceRings` now takes the span the frame covers rather than its far edge, and the step is a fifth of that: 5 pc rings from 165 to 210 for the same view. Measured in the app, centred on a star 187 pc out in the galactic plane: 4 ring labels drawn 20 pc above the plane and 7 from 2 pc, against 1 and none before. The clearance was a radius around the anchor, and a label is a line of text hanging 135 px to one side of its anchor: at 0.065 NDC apart, past the radius, "50 pc" printed inside "Alpha Centauri". It is now tested against the span the name occupies, on the side it hangs, with the radius kept for the pair whose text runs the other way. Also from the review: the ladder in the clearance test was built at exactly the constant it tests, so 1057 of 2000 camera poses would have decided it by float round-trip error — the rungs now sit 0.02 either side of the rule. And two comments that were wrong: a frame one step short of the survey edge does get its callout, and CSS2DRenderer hides a label behind the camera rather than drawing it at the page edge. 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 |
||
|
|
5dec528cee |
Size the distance rings by what the frame reaches, and keep their labels off the star names
From the review of #19. The rings are distances from the Sun, but their step was taken from `effectiveDistance`, which under the plan view means the extent of the frame rather than how far the camera is from the Sun. Centred on a star 200 pc out and flipped to 2D, the grid became rings of 2 to 20 pc: not one of them on screen. The step now comes from where the view is centred plus how far the camera is orbiting it, which is the same distance under either projection. The set was also rebuilt while the grid was hidden, and every rebuild disposes the rings and builds every vertex again; it now happens only while the grid is drawn. The ring labels went straight to the overlay: never culled to the frame, and free to land on a star's name. They now have to be on screen and clear of the names already placed, by half the separation two names keep — they are a ladder up one ray a twentieth of the screen apart, and holding them apart from each other would take "Survey edge" off the map. 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 |
||
|
|
9631ddf0a4 |
Answer the review: keep up with flights frame by frame, respect portrait frames, and let links follow an orbit
- Flights: the drawn stars were checked once a label pass, and a flight outruns that. Leaving a system jumps the camera to face another way, then zooms out forty-fold in a second: 74-83% of the stars that belong on screen were missing on the first frames back in parsec space, 33-50% before each re-choice on the way out. While the rig animates, the check now runs every frame. Probe on real flights (Gl 806, Barnard's Star, out): 0.90-1.00 of a fresh choice drawn on screen in flight, 1.00 on the frame of the jump; frame p95 12.2 ms, no long tasks. Choosing for the whole sky during flights was tried first and measured worse (0.15-0.18). - Portrait frames: the turn and pan limits use the narrower half-extent, not the height. - Links: a new drawn set arms the rebuild timer only when none is pending, so a continuous orbit gets a graph at most every 250 ms instead of never; an unchanged set asks for nothing. - The centre-move test lets the first pass happen before moving the centre. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016jxMkwA2rbicdGxHosecYi |
||
|
|
6cd0666067 |
Draw what the camera shows: the budget goes to the stars in view
The drawn set was two spheres, around the view's centre and around the Sun, then the brightest stars anywhere, so most of the budget sat behind or beside the camera: at 30 pc from the Sun 15.8% of the drawn stars were on screen, at 5 pc 9.1%, in a plan view zoomed to 10 pc 3.8%. The same tiers are now taken only from the camera's frame, widened by a quarter (VIEW_MARGIN), with the planet hosts in view drawn first after the pinned stars, so every ring circles a star that can be clicked. The set is chosen again at the label cadence once the view has turned, zoomed or moved half the margin, switched projection or been resized, and once for the whole sky on the way out to the Galaxy. Drawn stars on screen: 74-76% at 30 pc, 73-75% at 5 pc, 66% in the zoomed plan view, 91.5% at the opening view. 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 |
||
|
|
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 |
||
|
|
d9bd913458 |
Draw it flat: an orthographic plan view
A perspective camera leans everything away from the centre of the frame. In a system that means the orbits are ellipses whose shape depends on where they happen to sit on screen, so two planets on the same circular orbit do not look like they are on the same circle. Plan view, in the Display panel, swaps the projection for a parallel one and swings to look down the plane the current scale is read against — the galactic plane out in the field, this system's own orbital plane inside one. Circles are circles again, wherever they are. Both halves are the feature and neither alone is it. The projection is what makes the shape honest; the swing is what makes it worth looking at. Orbiting still works afterwards, so a plan is where the view starts rather than a cage. The engine now holds both cameras and keeps them in step, rather than making one on demand: a camera that exists only while it is being looked through is a camera whose pose is always one swap out of date. The orthographic frustum is derived, never stored — it is the perspective camera's own frustum at the current orbit distance, made parallel — which is why the camera flights work through it untouched. They move the camera; the frame follows. Three things had to be taught that a projection had changed. Sprites. three.js turns an angular size into a world size only when it is compiling against a perspective camera (SpriteNodeMaterial: `camera .isPerspectiveCamera && sizeAttenuation === false`). Under a parallel one that step is silently skipped and every star in the field collapses to a thousandth of a parsec. The same arithmetic is now done in the node graph behind a uniform, so one material serves both cameras without being recompiled — and picking follows it exactly, since a star has to be clickable where it is drawn. Depth. A parallel camera does not back away as its frame grows, so at galactic framing the backdrop shell and half the Milky Way sit behind its own plane. Its depth range is symmetric about it instead, which a linear depth buffer can afford and a perspective one could not. And distance. Half the map was keyed on how far back the camera was pulled — the scale ladder, the crossfade, the label radius, the range readout — which under a parallel projection says nothing at all, because the frustum sets the extent. They all read one honest equivalent now: the distance a perspective camera would need to frame the same thing. Two defects found while verifying, both mine, both from this change: The per-frame work was computed against the camera captured at bootstrap while the renderer drew through the other one, so after a swap every label was projected by a camera nobody was looking through. And the zoom limits were derived from the orbit limits, which are in whichever unit space the view is in. Reading them on the frame the scene swaps parsecs for astronomical units pinned the zoom at the ratio between the two, and leaving a system landed the view three kiloparsecs out. Zoom is a plain multiplier on a frame the distance already sets, so it is bounded by a factor. Verified: build clean, 595/595 unit including a new spec for the projection arithmetic, 13/13 end-to-end including two that flatten a system and check the ladder still knows how far out it is, design detector clean, screenshots of both scales in both projections. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016jxMkwA2rbicdGxHosecYi |
||
|
|
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 |
||
|
|
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 |
||
|
|
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> @ |