67a21b4a102c39df62d1eddf65b9d74142f58a03
23
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
67a21b4a10 |
Keep the date on screen on a phone after Go, and hand focus back to the tab that folded
|
||
|
|
09eaf9532f |
Keep the Display panel shorter on a phone, and fold it away once a date is set
The date form made the Display panel 400 px tall at 360x640 (from 257) and 363 at 390x844 (from 220), and the Sun's system sat behind it: every orbit at 360x640, 81 per cent of their points at 390x844. A reader who set a date could not see what it did without closing the panel. The window's description is one line, "AD 1 to AD 3000, where the planets' elements hold." (the sentence on how far each moon's orbit strays is on each card and in the system note), and below sm the field shrinks so Go stays on its line. Measured on :4301 with the panel open: 318 px at 360x640, Go at y 501 beside the field at 500; 318 px at 390x844, where 9 per cent of the orbits' points are behind it and 37 of the 39 lines show. At 360x640 the system is framed behind even the old 257 px sheet, so a date submitted with Go on a narrow viewport now folds the sheet, as choosing a search result already does. Measured: after Go at both sizes the panel is gone and the strip reads "Date 2020-12-21". Wide screens keep it open. Tests: the sheet folds after Go on a narrow viewport, and stays open on a wide one; jsdom has no matchMedia, so the spec gives the dock one it can turn narrow. Guarded mutants: no fold fails the first, a fold on every screen the second (and "jumps the clock to the date submitted", which then cannot find Back to now). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> |
||
|
|
74535accc5 |
Show the date and the clock on a body's page, and open it on the side of the equator its Sun lights
Since the page turns a body as it stands at the map's date, it has been drawn for a date it never showed, at a rate it gave no way to change: set to 2032 at a day a second in the system view, Saturn's page ran on at that rate with only Search and Bookmarks on its dock, and at a month a second Earth turned about 183 degrees a frame at 60 Hz. The dock now takes `clock`, which offers the clock without the layers, as a tab named Clock, and the page binds it with the date strip the system view already has. Measured on :4301 at 2032-06-01: the strip reads "Date 2032-06-01", the tabs are Search, Bookmarks and Clock, and the Clock panel has the four rates and the date field; Back to now clears the strip. The camera opened 11.3 degrees north of the equator whatever the season, which since 2025 put Saturn's page on the unlit face of its rings, and will until 2039. When a body is shown, the next frame puts the camera on the side of the equator the Sun is on: measured on Saturn's page, the Sun at -26.71 degrees on 2032-06-01 and the camera at -11.31; Earth's in June stays north. Tests, each proved by a guarded mutant that fails it: - the page follows the clock after its first frame (frozen at the first frame: 90 degrees out); - a body with no IAU model shown after Earth gets the page's own light back (left at Earth's Sun); - the page's dock shows the date and a Clock tab, and no date at the present (date never set, or the dock bound without the clock); - the dock offers the clock alone as a Clock tab (no tab, or a tab still named Display); - Saturn's page in 2032 opens with the camera and the Sun both south (camera held north). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> |
||
|
|
3453cd5d9f |
Say which cards give how far their orbit strays, since Pluto's does not
The date field's description read "Each moon's and dwarf planet's card says how far its orbit strays from 1950 to 2100." Pluto is a dwarf planet on its card, but it moves on Standish's planet elements, and the ETL measures only moons and the SBDB bodies against Horizons: its card ends "Orbit: JPL approximate mean elements (Standish), fit for 3000 BC to AD 3000." and gives no stray figure. In bodies.json, Ceres (7.2), Eris (0.1), Haumea (0.4), Makemake (0.3) and every moon carry one; Pluto does not. The description now reads "AD 1 to AD 3000, where the planets' and Pluto's elements hold. Each moon's card, and Ceres's, Eris's, Haumea's and Makemake's, says how far its orbit strays from 1950 to 2100." The CLOCK_WINDOW comment and the scene's note comment make the same distinction. Test: hud-dock 'opens the date field on the clock's date...' asserts the new sentence. Control: putting the old sentence back fails that test only (1 failed, 836 passed). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> |
||
|
|
a281f22f34 |
Measure every moon and dwarf planet against Horizons from 1950 to 2100, and say on its card how far it strays
The clock reaches AD 1 to AD 3000, but only the planets' cards named a span their elements hold
over; the 25 moons and the four SBDB dwarf planets gave a source and an epoch, though
BodyRecord.orbitSource is documented as "the span they hold over". And the worst offsets the ETL
stated came from twelve New Year's Days: Nereid's year is 360 days, so all twelve fell far from
its periapsis, where a mean ellipse is furthest out. The one date the ETL checked, 2025-01-01, saw
Nereid at 2.6 degrees; it reaches 11.19.
For each moon and each SBDB dwarf planet the ETL now fetches Horizons' ICRF vectors from 1950 to
2100, every other day (daily for Nereid, at an eccentricity of 0.75, and Hyperion, whose row's
eccentricity is a quarter of its real one: every other day gave it 22.14, daily 22.23), and
measures how far the mean elements stray, at the same TDB dates. The card appends it: "JPL SBDB
osculating elements, epoch 2026 Jun 9, within 7.2 degrees of Horizons from 1950 to 2100". Worst
offsets on the real catalogue: the Moon 2.62 (2010 March 27), Phoebe 2.58 (1969, where a comment
claimed "within 2.0"), Phobos 1.26, Mimas 7.43, Iapetus 10.34, Nereid 11.19 (2039 Nov 1),
Hyperion 22.23 (2055 Feb 26), Ceres 7.12 (1953); Io 0.07, Titan 0.06, Eris 0.06.
build.ts recomputes each from the same Horizons positions and fails if an orbit other than
Standish's names no span, if a card states less than it strays, or if a body passes its ceiling:
3 degrees, and Hyperion 23, Nereid 12, Iapetus 11, Mimas 8 and Ceres 8, each explained. The
2025-01-01 check stays for reading errors, its comment no longer passing one date's offsets off as
worst ones. The Sun's note says the moons' and those four's elements were checked from 1950 to
2100, and the date field's description that each card says how far its orbit strays over that span.
In the running app Ceres's, Phobos's and Nereid's cards end "within 7.2", "1.3" and "11.2 degrees
of Horizons from 1950 to 2100".
The CLOCK_WINDOW comment also had the calendars the wrong way at AD 1: proleptic Gregorian dates
are two days behind the Julian calendar there, level from AD 200 to 300, and ten days ahead by
1582. It now says so, and names Ceres's drift where it named Phobos's, which its orbit now carries.
Controls: the ETL measuring nothing fails ("Ceres's orbit ... names no span it holds over"),
rounding the stated figure down fails on Ceres (7.1 against 7.12), and Nereid held to the general
ceiling fails at 11.19; the note and the date field without the span fail their named tests.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
||
|
|
ac6a3bb1ea |
Let the reader set the clock to a date, and run it backwards
The clock from #33 could only run forwards from now. The Display panel's clock now has a Date (UTC) field, a native datetime-local in a form, so Enter submits it and the browser holds it to its min and max. It jumps the clock to that date, and the clock carries on from there at the rate it was running at. A Backwards toggle (aria-pressed) runs the same four rates the other way. The radios still pick the rate's size and keep the direction when it changes. The window is AD 1 to AD 3000. The end is where Standish's Table 2 stops being fitted (3000 BC to AD 3000; every planet within 0.29 degrees of Horizons at each date measured out to 3000). The start is the date input's own floor. TimeStore.setDate refuses anything outside it, and NaN, and leaves the clock where it was. The field is read as UTC. Its dates are proleptic Gregorian, as a Date is, so before 1582 they run up to ten days ahead of the Julian-calendar dates history gives. The window is written beside CLOCK_WINDOW, with the moons' shorter reach (Phobos 11 degrees out by 2100). The system note now names the date it is drawn for, to the minute: "... to 2020-12-21 18:00 UTC.", or "to now, <date> UTC." at the present. Measured in the app (port 4311, keyboard only: fill, Enter): - Set to 2020-12-21 18:00 UTC, Jupiter and Saturn seen from Earth's drawn position are 0.113 degrees apart. Horizons gives 0.102 geocentric (geometric 0.1017, astrometric 0.1018). Distances: 5.9267 and 10.8296 AU against Horizons' 5.9258 and 10.8270. - 3001-06-01 is refused by the form (validity false) and the clock does not move. - Backwards at 1 d/s: -2.011 days in about 2 s. - The field's accessible name is "Date (UTC)" and its description is the window. Its colour-scheme is dark, so the picker icon shows on the HUD. - Back to now puts the field back on the present too. Unit suite 799 -> 805: two tests for the store, three for the dock, one for the scene note. Eleven guarded mutants; each changed its file and made its named test fail. Among them: the window check dropped, the wall clock not re-anchored on a jump, the radio dropping the direction, the field read as local time, and the note not naming the date. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> |
||
|
|
c38a42cbcb |
Fix what the review of this branch found, starting with the pick rule it only claimed
The off-screen rule for clicks was described in
|
||
|
|
468c98b14a |
Surface the system view with the photographs it already had, lit by its star
Every body in the system view was an unlit sphere wearing a 32 by 16 pixel procedural texture — the size chosen when a marker was a few pixels across and what survived was its average colour. The thirteen real photographs in `src/assets/textures/bodies/` were used only by the detail page. So Mars was a pale grey ball with invented polar caps while its own NASA mosaic sat unread in the repository, and nothing had a day side or a night side. Each marker now takes its own photograph where one exists, at the size the detail page uses, and the derived texture only where none does — the five moons no probe mapped, and every exoplanet, none of which has ever been imaged. The material is lit, and the light is a point at the star, so each world shows the terminator where it really falls. The light does not fall off with distance. Under the inverse square that real light obeys, Neptune receives a thousandth of what Mercury does and reads as black; the map is a set of worlds to look at rather than a light meter, so each is lit as a photograph of it would be. That is the same concession the pixel floor makes for size, and it is only about brightness: the *direction* is real. Spheres are 32 by 24 rather than 16 by 12, since at true scale a body is drawn anywhere from a pixel to the whole frame and the old silhouette was visibly faceted at the near end. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
4b276e44a5 |
Give the map a clock, so the sky it computes can be watched
The orbits and the rotations are both functions of a date, and the only date the map ever asked for was this instant. So a view built on propagated ephemerides showed a still picture: Earth turns 15 degrees an hour and takes a year to go round, and a reader watching for a minute saw nothing move at all. `TimeStore` is that date, at a rate the reader sets: real time, an hour a second, a day a second, a month a second. It is read once a frame rather than held in a signal — it changes continuously, and a signal changing sixty times a second would ask the whole HUD to re-render for a number nothing is watching. Changing the rate re-anchors rather than rewinding, so speeding up and slowing down never jumps the sky, and "Back to now" returns to the world's own time. Measured in the app, three seconds of watching in the Sun's system: | rate | sky elapsed | Earth turned | Jupiter moved | |---|---|---|---| | real time | 0 | 0 | 0 | | 1 h/s | 3.0 h | 45.12 deg | 0.0009 AU | | 1 d/s | 3.0 d | (three full turns) | 0.0222 AU | 45.12 degrees in three hours is 15.04 an hour, which is Earth's own sidereal rate, and Jupiter's 0.0222 AU in three days is its own orbital speed. The rates are radio buttons, not toggles: they are one of four, and the native control carries that to a screen reader and to the arrow keys with no script. The date joins the strip only while the clock is running faster than the world, since at real time it is today's, which the reader's machine already says. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
365252f534 |
Let the Routes panel be clicked as soon as it is back, and stop it departing from elsewhere
From the review of #24. Two defects, both in a real browser. The panel keeps its entries across a trip to another tab, but still replayed the acquire wipe on the way back, and for the 380 ms that runs, its clip path swallows clicks: type "Siri", leave for Readout, come back and click the Sirius suggestion, and the click lands on the star field behind it — measured, the element under the pointer is the canvas, and the field stays "Siri". The wipe is gone from this one panel: it is not acquiring anything it did not already have. The departure field fell back to the star the view is in whenever nothing had been chosen, text in the field or not. So a field reading "Sol" that was never resolved plotted from Barnard's Star: "1 Barnard's Star, 2 Sol, 3 Sirius", the panel naming one departure and the route leaving from another. Text nobody chose is no longer a departure, and the button waits until it is one or the field is empty again. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016jxMkwA2rbicdGxHosecYi |
||
|
|
c77d3ed1d9 |
Keep the Routes panel's entries across a trip to another tab
The panel was unmounted with its tab, so leaving it reset departure, destination and range. The range reset was also a lie: the slider came back at 3 pc while the scene kept drawing the graph at the range last chosen. The panel now stays mounted and is hidden while another tab is open, which still replays the acquire wipe when it is shown again. 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 |
||
|
|
687bc3b01c | Merge branch 'feat/hud-bookmarks' into feat/hud-2d | ||
|
|
d397aaa7e0 |
Let the Solar System be kept
The Sun's catalogue id is 0, and the readout's keep control was shown by `@if (keepableStarId(); as starId)` — which reads zero as "there is no star here". Of the six hundred and thirty-four systems the map can be inside, the one nobody could keep was Sol. Checked against null now, with a test that keeps star zero, because this is the sort of thing that comes back. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016jxMkwA2rbicdGxHosecYi |
||
|
|
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 |
||
|
|
307fd41be8 |
Keep a place, and come back to it
Restores the commit reverted off the routing branch, which is where it was committed by mistake. The change is unmodified; only its branch is. The map had no memory. Every visit started at the same overview, and a system worth returning to had to be found again by name each time. A mark on the readout and on a body's panel now keeps it, a Bookmarks tab lists what has been kept, and choosing one goes there — a star by flying into its system, a body by opening its page. Local storage, not an account. This map asks nobody to sign in, and a list of stars somebody liked is not worth a server. Every read of that store is defensive, because it is a string a person can edit, another tab can write, and a browser can refuse to hand over at all: a bad entry is skipped rather than losing the rest, duplicates are collapsed since two entries for one place would each toggle the other's control, the list is bounded so a hand-edited store cannot decide how much this renders, and where storage is denied outright the bookmarks still work for the visit — they just do not outlive it. The name is stored alongside the id rather than looked up, so the list reads before the catalogues have loaded, and a bookmark to something a later catalogue no longer holds still says what it was instead of decaying into a bare number. The tab is offered even when it is empty, and says what the mark does: a tab that appears only once you have already found the feature is a tab that never taught anyone anything. Choosing a kept place hands the panel back to the readout, the same move as choosing a search result and for the same reason. That behaviour is what the end-to-end spec caught missing — the readout it asserted on did not exist, because the panel just used was still covering it. Verified: build clean, 587/587 unit, 11/11 end-to-end, 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 |
||
|
|
9cdd8f9388 |
Revert "Keep a place, and come back to it"
This reverts commit
|
||
|
|
bd3a5a48c9 |
Keep a place, and come back to it
The map had no memory. Every visit started at the same overview, and a system worth returning to had to be found again by name each time. A mark on the readout and on a body's panel now keeps it, a Bookmarks tab lists what has been kept, and choosing one goes there — a star by flying into its system, a body by opening its page. Local storage, not an account. This map asks nobody to sign in, and a list of stars somebody liked is not worth a server. Every read of that store is defensive, because it is a string a person can edit, another tab can write, and a browser can refuse to hand over at all: a bad entry is skipped rather than losing the rest, duplicates are collapsed since two entries for one place would each toggle the other's control, the list is bounded so a hand-edited store cannot decide how much this renders, and where storage is denied outright the bookmarks still work for the visit — they just do not outlive it. The name is stored alongside the id rather than looked up. That way the list reads before the catalogues have loaded, and a bookmark to something a later catalogue no longer holds still says what it was instead of decaying into a bare number. The tab is offered even when it is empty, and says what the mark does. A tab that appears only once you have already found the feature is a tab that never taught anyone anything. Choosing a kept place hands the panel back to the readout, which is the same move as choosing a search result and for the same reason: the panel has done its job and the thing to look at is now the scene. That behaviour is what the end-to-end spec caught missing — the readout it asserted on did not exist, because the panel that had just been used was still covering it. Verified: build clean, 587/587 unit, 11/11 end-to-end including a spec that keeps Earth, leaves the page, comes back to it from the list and forgets it, and one that keeps Proxima Centauri, flies out to the field and flies back in by what was kept. 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 |
||
|
|
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 |
||
|
|
7591bcc0ea |
Point at what is selected: arcs, a leader to the card, rings on the systems
Four things the scene did not yet say, all about where to look. Selection. Hovering or pinning a body raised its card in the corner, but nothing in the scene said which point the card was about. Two hairline arcs now bracket the body — the one mark borrowed from the ARK's control disc — and a leader runs from their rim to the card's near edge, in screen space, once per frame, because the body moves and the card's height depends on its content. The selected body's own label swaps to the left of its point, since the leader leaves the right and would otherwise cross the text. Labels choose a side. Right by default; left when the text would run off the right of the view, or into the reach of a label already placed to the right, and never left when that would run off the left. The overlay hangs the label's near edge on the point either way, so the hairline still meets the star. Rings on the systems. A faint accent ring on every star known to host planets — the one binary fact about a point of light worth reading at a glance from the neighbourhood, since it is the one thing that says "there is somewhere to go here". Drawn the way the star field draws stars, as unattenuated instanced sprites with the ring a band of the quad's own uv, so they sit on the field's points at any zoom; a first cut as three.js Points rendered nothing at all under the WebGPU renderer. 634 of them are a lot at the overview, so they are faint, small, fade with the local layer, and have their own toggle — Systems — in the dock's Display tab. Verified: build clean, 537/537 unit, 6/6 end-to-end, design detector clean, screenshots at the overview, at 90 pc, and in Sol and Proxima with a body hovered and pinned. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016jxMkwA2rbicdGxHosecYi |
||
|
|
8b507427d8 |
Answer the review: sky toggles in system view too, one media query, idempotent openSearch
Three findings from the automated review, all confirmed before acting. The Sky toggle did nothing in system view. Its write lived only in the galaxy crossfade, which the tick parks while the system group is up — and the sky is still on screen there. It is now also written on toggle, in applyDisplay. The suggested form of that fix crashed the scene on mount: the effect that calls applyDisplay fires once at construction, before the engine has a scene, and getScene() throws — the dock rendered no tabs at all. Guarded on engine.isInitialized; verified with a screenshot of Proxima with the sky off. isWideViewport built a fresh MediaQueryList on every document pointer-down; one module-level query is read instead. And openSearch in the e2e support clicked the Search tab unconditionally, which would fold it closed if a test ever called it while already open; it now checks aria-selected first. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016jxMkwA2rbicdGxHosecYi |
||
|
|
4a1cc5240a |
Dock the HUD: every tool and readout on one rail along the bottom
The overlay had grown by accretion: a search box floating top-centre, a readout panel bottom-left, a range readout bottom-right, and nothing that said these were parts of one instrument. This puts them on one rail across the bottom of the viewport — the dock — with a tab strip pinned to the bottom edge and whichever panel is open growing upward from it. The top of the screen keeps only the scale ladder and the nameplate, so the map itself is what fills the frame. Three tabs. SEARCH is the old search, with its field pinned to the bottom of the panel and the results growing upward above it, so the thing being typed into never moves while the list grows. READOUT is the old bottom-left panel. DISPLAY is new: five layer toggles — labels, orbits, grid, deep sky, sky — each a real scene object switched by visibility, except the ones the galaxy crossfade already rewrites every frame, whose toggles fold into that crossfade instead of fighting it. The range readout sits on the strip itself, so it is readable whatever is open. Behaviour worth stating: choosing a search result hands the panel straight back to the readout, since the thing to look at is now the scene. `/` opens the search from anywhere. Below `sm` the dock is the strip alone; a tap opens a panel as a sheet, a tap on the scene folds it away. The body-detail page gets the same dock with only the search — the info panel is its reading. Two things found on the way. CSS2DRenderer gives every label its own z-index for depth order, and the label host created no stacking context, so labels painted over every HUD panel; `isolate` on the host keeps them under. And starmap-hud's readout tests were really tests of the panel that moved, so they moved with it. Verified: build clean, 535/535 unit, 6/6 end-to-end, design detector clean, screenshots at 1440×900 and 390×844 across galaxy, galactic, system, body-detail, all three tabs and the layers-off state. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016jxMkwA2rbicdGxHosecYi |