From 1078ebbd523f8efbe8938b7e2b412b49b7428aac Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 19 May 2026 21:35:11 +0000 Subject: [PATCH 1/3] Raise T1/T2 reveal thresholds so initial fly-in stays uncluttered MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit T1 was firing at the initial d=10R fly-in on 4K displays (screenPx≈274 at d=10R, above the old T1=200 threshold) — every named place was visible before the user had zoomed in at all. Push T1 to 400 so all common viewport sizes show only T0 on arrival, and push T2 to 700 so secondary cities stay hidden until the user is at "almost landed" range (d≈1.8R on 1080p). --- js/scene/Places.js | 23 ++++++++++++----------- js/scene/Places.test.js | 12 ++++++------ js/scene/places.md | 23 +++++++++++++---------- 3 files changed, 31 insertions(+), 27 deletions(-) diff --git a/js/scene/Places.js b/js/scene/Places.js index 9cfef86..3761dae 100644 --- a/js/scene/Places.js +++ b/js/scene/Places.js @@ -10,18 +10,19 @@ import {labelTextColor} from '../shared.js' // T0 marquee names appear when the body is a small recognizable disc; // finer tiers reveal as the camera closes in. Indexed by entry's `t` field. // -// At the default FOV of 45° on a 900-pixel-tall viewport, the body -// subtends roughly: -// d=10R → 114 px (only T0) -// d=5.5R → 200 px (T1 reveals) -// d=2.3R → 500 px (T2 reveals — "close orbital view", whole hemisphere) -// d=R → 900 px (camera at the surface) +// At the default FOV of 45° and the initial fly-in distance d=10R, the +// body subtends: +// 900-px viewport → 113 px +// 1080-px viewport → 137 px +// 1440-px viewport → 183 px +// 2160-px viewport → 274 px (4K — T1's earlier 200 fired here on goTo) // -// Earlier T2 was 1500 which never fired at the default FOV (max is ~900 -// at d=R), making T2 entries effectively dead unless the user narrowed -// FOV via the `,` key. T3 (8000) remains reserved for a possible future -// zoom-only level. -const DEFAULT_TIER_PX = [30, 200, 500, 8000] +// T1 (400) keeps it suppressed at the initial view across all common +// viewport sizes and reveals as the user zooms in past d≈3R on 1080p +// (or d≈7R on 4K). T2 (700) requires closer zoom still — d≈1.8R on +// 1080p — for picking specific cities at "almost landed" range. T3 +// (8000) is reserved for a future zoom-only level. +const DEFAULT_TIER_PX = [30, 400, 700, 8000] // Per-entry altitude (`a`, in m) is preserved as-is. We don't add a fixed // surface lift any more: the surface-visibility SpriteSheet shader uses diff --git a/js/scene/Places.test.js b/js/scene/Places.test.js index f9ff3cc..35a65d8 100644 --- a/js/scene/Places.test.js +++ b/js/scene/Places.test.js @@ -51,13 +51,13 @@ describe('Places.shouldShowTier', () => { expect(places.shouldShowTier(0, 30)).toBe(true) expect(places.shouldShowTier(0, 1000)).toBe(true) }) - it('reveals T1 above 200 px', () => { - expect(places.shouldShowTier(1, 199)).toBe(false) - expect(places.shouldShowTier(1, 200)).toBe(true) + it('reveals T1 above 400 px', () => { + expect(places.shouldShowTier(1, 399)).toBe(false) + expect(places.shouldShowTier(1, 400)).toBe(true) }) - it('reveals T2 above 500 px', () => { - expect(places.shouldShowTier(2, 499)).toBe(false) - expect(places.shouldShowTier(2, 500)).toBe(true) + it('reveals T2 above 700 px', () => { + expect(places.shouldShowTier(2, 699)).toBe(false) + expect(places.shouldShowTier(2, 700)).toBe(true) }) it('returns false for a tier with no threshold', () => { expect(places.shouldShowTier(99, 1e9)).toBe(false) diff --git a/js/scene/places.md b/js/scene/places.md index 4cf6df3..2c498a4 100644 --- a/js/scene/places.md +++ b/js/scene/places.md @@ -54,22 +54,25 @@ per-frame in `Places._installLODHook`'s `onBeforeRender`: | Tier | screenPx ≥ | UX intent | |---|---|---| | T0 | 30 | small recognizable disc → only marquee names | -| T1 | 200 | planet fills ~half the screen | -| T2 | 500 | close orbital view — whole hemisphere visible | +| T1 | 400 | one step past the initial fly-in — major cities | +| T2 | 700 | "almost landed" zoom — secondary cities | | T3 | (Phase 2 lazy chunks) | — | `screenPx` is the body's apparent *radius* in viewport pixels (see -`Places.screenPx`). At the default FOV (45°) and a 900-px-tall viewport, -the camera distance → screenPx mapping is roughly: +`Places.screenPx`). At the default FOV (45°) and a 1080-px-tall +viewport, the camera distance → screenPx mapping is roughly: | Camera distance | screenPx | Visible tiers | |---|---|---| -| 10 R | 114 | T0 | -| 5.5 R | 200 | T0, T1 | -| 2.3 R | 500 | T0, T1, T2 | -| R (surface) | 900 | T0, T1, T2 | - -(Where `R` is the body's surface radius.) +| 10 R (initial fly-in) | 137 | T0 | +| 3.3 R | 400 | T0, T1 | +| 1.8 R | 700 | T0, T1, T2 | +| R (surface) | 1080 | T0, T1, T2 | + +(Where `R` is the body's surface radius.) Earlier T1 was 200 which let +the T1 sheet fire at the initial fly-in on 4K displays (screenPx≈274 at +d=10R); T1=400 keeps the initial view clean across all common +viewports. Per-tier SpriteSheets are lazy-instantiated the first time their threshold is crossed — most users browsing the solar system will never From 6867e7d0907a284e5146f220f2c8f6cc76abefc4 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 19 May 2026 21:53:10 +0000 Subject: [PATCH 2/3] Tier reveal thresholds: switch to viewport-relative diameter fractions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous absolute-pixel thresholds (T1=400 px radius, T2=700) read very differently across viewports — T1 at 400 was "planet fills 74 % of screen" on a 1080p display but only ~18 % on 8K, so users on larger displays saw T1 names appear at the initial d=10R fly-in. New table is planet apparent DIAMETER as a fraction of viewport height, so the visual size at which each tier reveals is identical across 720p / 1080p / 4K / 8K monitors: T0: 0.06 (small disc — marquee only) T1: 0.75 (planet fills ~3/4 of screen — major cities) T2: 1.30 (planet bigger than screen — "almost landed" picking) `Places.diameterFraction(camera, vph)` is the new viewport-relative metric driving the LOD hook; `screenPx` is retained for tests and debugging. Tier reveal distances (independent of viewport): T0 fires at d ≈ 42 R T1 fires at d ≈ 3.3 R T2 fires at d ≈ 1.8 R --- js/scene/Places.js | 74 +++++++++++++++++++++++++---------------- js/scene/Places.test.js | 56 +++++++++++++++++++++++++------ js/scene/places.md | 49 ++++++++++++++++----------- 3 files changed, 121 insertions(+), 58 deletions(-) diff --git a/js/scene/Places.js b/js/scene/Places.js index 3761dae..1571ffa 100644 --- a/js/scene/Places.js +++ b/js/scene/Places.js @@ -6,23 +6,18 @@ import {latLngAltToBodyFixed} from '../coords.js' import {labelTextColor} from '../shared.js' -// Tier reveal thresholds: planet apparent RADIUS in viewport pixels. -// T0 marquee names appear when the body is a small recognizable disc; -// finer tiers reveal as the camera closes in. Indexed by entry's `t` field. +// Tier reveal thresholds: planet apparent DIAMETER as a fraction of +// viewport height. Viewport-relative so the visual size at which each +// tier reveals stays consistent across 720p / 1080p / 4K / 8K monitors — +// an earlier absolute-pixel threshold (T1=400 px radius) gave a "planet +// fills 74 % of screen" reveal on 1080p but only ~18 % on 8K, so users +// on larger displays saw T1 cities at the initial d=10R fly-in. // -// At the default FOV of 45° and the initial fly-in distance d=10R, the -// body subtends: -// 900-px viewport → 113 px -// 1080-px viewport → 137 px -// 1440-px viewport → 183 px -// 2160-px viewport → 274 px (4K — T1's earlier 200 fired here on goTo) -// -// T1 (400) keeps it suppressed at the initial view across all common -// viewport sizes and reveals as the user zooms in past d≈3R on 1080p -// (or d≈7R on 4K). T2 (700) requires closer zoom still — d≈1.8R on -// 1080p — for picking specific cities at "almost landed" range. T3 -// (8000) is reserved for a future zoom-only level. -const DEFAULT_TIER_PX = [30, 400, 700, 8000] +// T0: tiny disc — marquee-only. T1: planet has become a substantial +// feature, fills ~3/4 of the screen vertically. T2: planet fills the +// screen and overflows — "almost-landed" picking zoom. T3 reserved for +// a future surface-detail level. +const DEFAULT_TIER_FRAC = [0.06, 0.75, 1.3, 8.0] // Per-entry altitude (`a`, in m) is preserved as-is. We don't add a fixed // surface lift any more: the surface-visibility SpriteSheet shader uses @@ -41,17 +36,19 @@ const DEFAULT_TIER_PX = [30, 400, 700, 8000] * raw body-fixed XYZ from latLngAltToBodyFixed, no per-frame transform work. * * LOD: per-tier SpriteSheets are lazy-instantiated the first time their - * reveal threshold (planet apparent radius in pixels) is crossed. Per-frame - * visibility toggling is driven by an invisible placeholder Points whose - * onBeforeRender computes screenPx for the parent body. + * reveal threshold (planet diameter as fraction of viewport height) is + * crossed. Per-frame visibility toggling is driven by an invisible + * placeholder Points whose onBeforeRender computes the apparent diameter + * for the parent body. */ export default class Places extends Group { /** * @param {string} bodyName For node naming + debugging * @param {number} planetRadius Body radius in meters - * @param {number[]} [tierThresholds] Override DEFAULT_TIER_PX + * @param {number[]} [tierThresholds] Override DEFAULT_TIER_FRAC. Each + * entry is planet apparent diameter as a fraction of viewport height. */ - constructor(bodyName, planetRadius, tierThresholds = DEFAULT_TIER_PX) { + constructor(bodyName, planetRadius, tierThresholds = DEFAULT_TIER_FRAC) { super() this.name = `${bodyName}.places` this.bodyName = bodyName @@ -88,6 +85,8 @@ export default class Places extends Group { /** * Compute the body's apparent radius in viewport pixels at the camera. + * Kept for external use (tests, debugging) — internally + * `diameterFraction` is what drives tier reveal. * * @param {object} camera Three.js PerspectiveCamera (uses .fov) * @param {number} viewportH Viewport height in pixels @@ -110,19 +109,38 @@ export default class Places extends Group { /** - * Visibility test for tier `t` at a given screenPx. Exposed for tests so - * we don't need a real renderer/camera to verify the LOD logic. + * Compute the body's apparent DIAMETER as a fraction of viewport height. + * Viewport-relative so a single threshold reads the same on 1080p, 4K, + * 8K, etc — the basis for tier reveal. + * + * @param {object} camera Three.js PerspectiveCamera (uses .fov) + * @param {number} viewportH Viewport height in pixels + * @returns {number} fraction (1.0 means body diameter equals screen height) + */ + diameterFraction(camera, viewportH) { + if (viewportH <= 0) { + return 0 + } + // screenPx is the apparent RADIUS in pixels; diameter / vph = 2*sp/vph. + return (2 * this.screenPx(camera, viewportH)) / viewportH + } + + + /** + * Visibility test for tier `t` at a given diameter fraction. Exposed + * for tests so we don't need a real renderer/camera to verify the LOD + * logic. * * @param {number} t - * @param {number} screenPx + * @param {number} fraction body diameter / viewport height * @returns {boolean} */ - shouldShowTier(t, screenPx) { + shouldShowTier(t, fraction) { const threshold = this.tierThresholds[t] if (threshold === undefined) { return false } - return screenPx >= threshold + return fraction >= threshold } @@ -168,12 +186,12 @@ export default class Places extends Group { // Hook runs only when the placeholder itself isn't culled. It sits at // the parent's origin, so it's visible whenever the planet is in frame. placeholder.onBeforeRender = (renderer, _scene, camera) => { - const px = this.screenPx(camera, renderer.domElement.clientHeight) + const frac = this.diameterFraction(camera, renderer.domElement.clientHeight) for (let t = 0; t < this.tierThresholds.length; t++) { if (!this.byTier[t]) { continue } - const want = this.shouldShowTier(t, px) + const want = this.shouldShowTier(t, frac) if (want && !this.tierGroups[t]) { this._buildTier(t) } diff --git a/js/scene/Places.test.js b/js/scene/Places.test.js index 35a65d8..2454a11 100644 --- a/js/scene/Places.test.js +++ b/js/scene/Places.test.js @@ -45,19 +45,21 @@ function placesAt(parentPos = new Vector3(0, 0, 0), radius = EARTH_R) { // ─── tests ──────────────────────────────────────────────────────────────── describe('Places.shouldShowTier', () => { + // Thresholds are body-diameter / viewport-height fractions; the + // default table is DEFAULT_TIER_FRAC = [0.06, 0.75, 1.3]. const {places} = placesAt() - it('reveals T0 above 30 px', () => { - expect(places.shouldShowTier(0, 29)).toBe(false) - expect(places.shouldShowTier(0, 30)).toBe(true) - expect(places.shouldShowTier(0, 1000)).toBe(true) + it('reveals T0 above 0.06 (~6% of screen)', () => { + expect(places.shouldShowTier(0, 0.059)).toBe(false) + expect(places.shouldShowTier(0, 0.06)).toBe(true) + expect(places.shouldShowTier(0, 5)).toBe(true) }) - it('reveals T1 above 400 px', () => { - expect(places.shouldShowTier(1, 399)).toBe(false) - expect(places.shouldShowTier(1, 400)).toBe(true) + it('reveals T1 above 0.75 (planet ≥ 75% of screen)', () => { + expect(places.shouldShowTier(1, 0.749)).toBe(false) + expect(places.shouldShowTier(1, 0.75)).toBe(true) }) - it('reveals T2 above 700 px', () => { - expect(places.shouldShowTier(2, 699)).toBe(false) - expect(places.shouldShowTier(2, 700)).toBe(true) + it('reveals T2 above 1.3 (planet larger than screen — almost landed)', () => { + expect(places.shouldShowTier(2, 1.299)).toBe(false) + expect(places.shouldShowTier(2, 1.3)).toBe(true) }) it('returns false for a tier with no threshold', () => { expect(places.shouldShowTier(99, 1e9)).toBe(false) @@ -65,6 +67,40 @@ describe('Places.shouldShowTier', () => { }) +describe('Places.diameterFraction', () => { + // The metric that actually drives tier reveal — verifies it's viewport- + // relative so a single threshold reads identically across screen sizes. + const cam = new PerspectiveCamera(45, 16 / 9, 1, 1e12) + + it('returns 0 when viewport height is zero or negative', () => { + const {places} = placesAt() + expect(places.diameterFraction(cam, 0)).toBe(0) + expect(places.diameterFraction(cam, -1)).toBe(0) + }) + + it('reads the same fraction on 1080p and 4K at the same camera distance', () => { + // Regression: an earlier absolute-pixel threshold gave wildly different + // visual sizes across viewports. With the fraction-based metric, + // the visual size at which a tier reveals is viewport-independent. + const {places} = placesAt(new Vector3(0, 0, 0)) + cam.position.set(0, 0, EARTH_R * 3) + cam.updateMatrixWorld(true) + const frac1080 = places.diameterFraction(cam, 1080) + const frac2160 = places.diameterFraction(cam, 2160) + expect(frac1080).toBeCloseTo(frac2160, 4) + }) + + it('= 2 × screenPx / viewportH', () => { + const {places} = placesAt(new Vector3(0, 0, 0)) + cam.position.set(0, 0, EARTH_R * 5) + cam.updateMatrixWorld(true) + const vph = 1080 + expect(places.diameterFraction(cam, vph)).toBeCloseTo( + (2 * places.screenPx(cam, vph)) / vph, 6) + }) +}) + + describe('Places.screenPx', () => { // 1080p viewport, 45 deg FOV const cam = new PerspectiveCamera(45, 16 / 9, 1, 1e12) diff --git a/js/scene/places.md b/js/scene/places.md index 2c498a4..3fd4d79 100644 --- a/js/scene/places.md +++ b/js/scene/places.md @@ -51,28 +51,37 @@ of homogeneous coordinates. Visibility = f(tier, planet apparent screen radius in pixels). Computed per-frame in `Places._installLODHook`'s `onBeforeRender`: -| Tier | screenPx ≥ | UX intent | +| Tier | diameterFraction ≥ | UX intent | |---|---|---| -| T0 | 30 | small recognizable disc → only marquee names | -| T1 | 400 | one step past the initial fly-in — major cities | -| T2 | 700 | "almost landed" zoom — secondary cities | -| T3 | (Phase 2 lazy chunks) | — | - -`screenPx` is the body's apparent *radius* in viewport pixels (see -`Places.screenPx`). At the default FOV (45°) and a 1080-px-tall -viewport, the camera distance → screenPx mapping is roughly: - -| Camera distance | screenPx | Visible tiers | +| T0 | 0.06 | small recognizable disc — only marquee names | +| T1 | 0.75 | planet fills ~3/4 of the screen — major cities | +| T2 | 1.30 | planet bigger than screen — "almost landed" picking | +| T3 | (8.0, reserved) | — | + +`diameterFraction` is the body's apparent diameter as a fraction of +viewport height, e.g. `0.75` means the body's screen diameter ≈ 75 % +of the viewport's vertical extent. Viewport-relative on purpose: an +earlier absolute-pixel threshold (T1 = 400 px radius) gave a "planet +fills 74 %" reveal on 1080p but only ~18 % on 8K, so users on larger +displays saw T1 names at the initial d=10R fly-in. + +At the default FOV (45°), the camera-distance → diameter-fraction +mapping is roughly: + +| Camera distance | diameter / vph | Visible tiers | |---|---|---| -| 10 R (initial fly-in) | 137 | T0 | -| 3.3 R | 400 | T0, T1 | -| 1.8 R | 700 | T0, T1, T2 | -| R (surface) | 1080 | T0, T1, T2 | - -(Where `R` is the body's surface radius.) Earlier T1 was 200 which let -the T1 sheet fire at the initial fly-in on 4K displays (screenPx≈274 at -d=10R); T1=400 keeps the initial view clean across all common -viewports. +| 10 R (initial fly-in) | 0.25 | T0 | +| 3.3 R | 0.75 | T0, T1 | +| 1.8 R | 1.30 | T0, T1, T2 | +| R (surface) | 2.0 | T0, T1, T2 | + +(Where `R` is the body's surface radius.) The mapping is independent +of viewport size — `Places.diameterFraction` divides by viewport +height, so the table holds for 720p / 1080p / 4K / 8K alike. + +`Places.screenPx` is still exposed for tests and debugging (it returns +the absolute pixel-radius), but tier reveal goes through +`diameterFraction`. Per-tier SpriteSheets are lazy-instantiated the first time their threshold is crossed — most users browsing the solar system will never From f57bdc931798b6c6a0e0b7f965deb4fa012cad4a Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 19 May 2026 22:36:00 +0000 Subject: [PATCH 3/3] Push all tier reveals back: nothing shows until planet is 75% of screen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User report: the marquee names (Cairo, Mt. Everest, Sydney…) were already on screen by the time the planet was a tiny disc — fraction 0.06 was way too eager. Push the entire sequence into the "planet is large" regime: T0: 0.75 (planet fills ~3/4 of screen — marquee names, was 0.06) T1: 1.00 (planet fills the screen — major cities, was 0.75) T2: 1.50 (planet 1.5× screen — close zoom for all cities, was 1.30) Corresponding camera-distance progression at default FOV: nothing until d ≈ 3.3 R, T0 reveals there, T1 at d ≈ 2.4 R, T2 at d ≈ 1.5 R. --- js/scene/Places.js | 19 ++++++++++--------- js/scene/Places.test.js | 20 ++++++++++---------- js/scene/places.md | 18 ++++++++++-------- 3 files changed, 30 insertions(+), 27 deletions(-) diff --git a/js/scene/Places.js b/js/scene/Places.js index 1571ffa..e50d007 100644 --- a/js/scene/Places.js +++ b/js/scene/Places.js @@ -8,16 +8,17 @@ import {labelTextColor} from '../shared.js' // Tier reveal thresholds: planet apparent DIAMETER as a fraction of // viewport height. Viewport-relative so the visual size at which each -// tier reveals stays consistent across 720p / 1080p / 4K / 8K monitors — -// an earlier absolute-pixel threshold (T1=400 px radius) gave a "planet -// fills 74 % of screen" reveal on 1080p but only ~18 % on 8K, so users -// on larger displays saw T1 cities at the initial d=10R fly-in. +// tier reveals stays consistent across 720p / 1080p / 4K / 8K monitors. // -// T0: tiny disc — marquee-only. T1: planet has become a substantial -// feature, fills ~3/4 of the screen vertically. T2: planet fills the -// screen and overflows — "almost-landed" picking zoom. T3 reserved for -// a future surface-detail level. -const DEFAULT_TIER_FRAC = [0.06, 0.75, 1.3, 8.0] +// The whole reveal sequence sits in the "planet fills most of the +// screen" regime — earlier tunings let T0 fire at fraction ≈ 0.06 +// (planet a small disc), which made even the marquee names crowd the +// view long before the user was close enough to read them. Now nothing +// reveals until the planet is ~3/4 of the screen height. +// +// T0 reveals at d ≈ 3.3 R, T1 at d ≈ 2.4 R, T2 at d ≈ 1.5 R. T3 +// reserved for a future surface-detail level. +const DEFAULT_TIER_FRAC = [0.75, 1.0, 1.5, 8.0] // Per-entry altitude (`a`, in m) is preserved as-is. We don't add a fixed // surface lift any more: the surface-visibility SpriteSheet shader uses diff --git a/js/scene/Places.test.js b/js/scene/Places.test.js index 2454a11..fb11c21 100644 --- a/js/scene/Places.test.js +++ b/js/scene/Places.test.js @@ -46,20 +46,20 @@ function placesAt(parentPos = new Vector3(0, 0, 0), radius = EARTH_R) { // ─── tests ──────────────────────────────────────────────────────────────── describe('Places.shouldShowTier', () => { // Thresholds are body-diameter / viewport-height fractions; the - // default table is DEFAULT_TIER_FRAC = [0.06, 0.75, 1.3]. + // default table is DEFAULT_TIER_FRAC = [0.75, 1.0, 1.5]. const {places} = placesAt() - it('reveals T0 above 0.06 (~6% of screen)', () => { - expect(places.shouldShowTier(0, 0.059)).toBe(false) - expect(places.shouldShowTier(0, 0.06)).toBe(true) + it('reveals T0 above 0.75 (planet ≥ 75% of screen)', () => { + expect(places.shouldShowTier(0, 0.749)).toBe(false) + expect(places.shouldShowTier(0, 0.75)).toBe(true) expect(places.shouldShowTier(0, 5)).toBe(true) }) - it('reveals T1 above 0.75 (planet ≥ 75% of screen)', () => { - expect(places.shouldShowTier(1, 0.749)).toBe(false) - expect(places.shouldShowTier(1, 0.75)).toBe(true) + it('reveals T1 above 1.0 (planet just fills the screen)', () => { + expect(places.shouldShowTier(1, 0.999)).toBe(false) + expect(places.shouldShowTier(1, 1.0)).toBe(true) }) - it('reveals T2 above 1.3 (planet larger than screen — almost landed)', () => { - expect(places.shouldShowTier(2, 1.299)).toBe(false) - expect(places.shouldShowTier(2, 1.3)).toBe(true) + it('reveals T2 above 1.5 (planet 1.5× screen — close zoom)', () => { + expect(places.shouldShowTier(2, 1.499)).toBe(false) + expect(places.shouldShowTier(2, 1.5)).toBe(true) }) it('returns false for a tier with no threshold', () => { expect(places.shouldShowTier(99, 1e9)).toBe(false) diff --git a/js/scene/places.md b/js/scene/places.md index 3fd4d79..30cd30a 100644 --- a/js/scene/places.md +++ b/js/scene/places.md @@ -53,9 +53,9 @@ per-frame in `Places._installLODHook`'s `onBeforeRender`: | Tier | diameterFraction ≥ | UX intent | |---|---|---| -| T0 | 0.06 | small recognizable disc — only marquee names | -| T1 | 0.75 | planet fills ~3/4 of the screen — major cities | -| T2 | 1.30 | planet bigger than screen — "almost landed" picking | +| T0 | 0.75 | planet fills ~3/4 of the screen — marquee names appear | +| T1 | 1.00 | planet fills the screen — major cities add | +| T2 | 1.50 | planet 1.5× screen — close-zoom picking, all cities | | T3 | (8.0, reserved) | — | `diameterFraction` is the body's apparent diameter as a fraction of @@ -65,14 +65,16 @@ earlier absolute-pixel threshold (T1 = 400 px radius) gave a "planet fills 74 %" reveal on 1080p but only ~18 % on 8K, so users on larger displays saw T1 names at the initial d=10R fly-in. -At the default FOV (45°), the camera-distance → diameter-fraction -mapping is roughly: +The whole reveal sequence is intentionally in the "planet is large" +regime — at the default FOV (45°), the camera-distance → +diameter-fraction mapping is: | Camera distance | diameter / vph | Visible tiers | |---|---|---| -| 10 R (initial fly-in) | 0.25 | T0 | -| 3.3 R | 0.75 | T0, T1 | -| 1.8 R | 1.30 | T0, T1, T2 | +| 10 R (initial fly-in) | 0.25 | (none) | +| 3.3 R | 0.75 | T0 | +| 2.4 R | 1.00 | T0, T1 | +| 1.5 R | 1.50 | T0, T1, T2 | | R (surface) | 2.0 | T0, T1, T2 | (Where `R` is the body's surface radius.) The mapping is independent