From 17cf11b6b18826623cfed90de63e80c4c214de6a Mon Sep 17 00:00:00 2001 From: Senrokai Date: Wed, 30 Sep 2026 15:48:09 +0200 Subject: [PATCH] Tint a planet host in the star field at the archive's temperature, which its disc is drawn at 95ffb00 said the field took "the same function and the same temperature the disc is drawn with". It did for stars without planets, but a host's disc is drawn at the archive's st_teff (starSurfaceOf), and the field read the host's temperature off its colour. Of the 4 485 hosts with a temperature on the published catalogue, 1 755 were more than 0.1 in RGB from their own disc: Kepler-186 at 3 096 K in the field and 3 788 K on its disc, HD 97048 at 6 825 and 10 000. The scene now hands StarFieldRenderer each host's first published temperature among its planets, the rule starSurfaceOf uses (publishedTemperaturesK, beside it), and the field tints that star at it. Over the catalogue the hosts more than 0.1 off their disc go from 1 755 to 0. Measured in the running app at 1600x1000, field tint against the disc's starTint after entering each: Kepler-186 (1, 0.620, 0.326) against (1, 0.619, 0.326), where the field was (1, 0.498, 0.174); Teegarden's Star 0.001 apart; Proxima, HD 97048 and Sirius 0. The field's test compared colorIndexToRgb with the same formula. The new scene test enters Proxima, whose B-V 1.8 reads 3 070 K and whose disc is drawn at the archive's 2 900, and compares its field colour with the disc's. Controls, each failing that test alone: the field ignoring the map it is given, and the scene passing an empty one. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../features/body-detail/body-view-model.ts | 15 +++++++++++ .../galaxy-system-scene.component.spec.ts | 9 +++++++ .../galaxy-system-scene.component.ts | 3 ++- .../galaxy-system/star-field-renderer.ts | 26 ++++++++++++++----- 4 files changed, 46 insertions(+), 7 deletions(-) diff --git a/src/app/features/body-detail/body-view-model.ts b/src/app/features/body-detail/body-view-model.ts index dae4bb0..744b552 100644 --- a/src/app/features/body-detail/body-view-model.ts +++ b/src/app/features/body-detail/body-view-model.ts @@ -50,6 +50,21 @@ export interface StarSurface { luminosityDerived: boolean; } +/** + * The temperature each host's disc is drawn at where the archive gives one, by star id: the first + * among its planets, in their order, as {@link starSurfaceOf} takes it — for the star field, which + * tints every star the colour of its own disc. + */ +export function publishedTemperaturesK(exoplanets: readonly ExoplanetRecord[]): Map { + const temperatures = new Map(); + for (const { hostStarId, hostStarTemperatureK } of exoplanets) { + if (hostStarId !== null && hostStarTemperatureK && !temperatures.has(hostStarId)) { + temperatures.set(hostStarId, hostStarTemperatureK); + } + } + return temperatures; +} + /** * A star's radius, effective temperature and luminosity: the archive's `st_rad`, `st_teff` and * `st_lum` for a planet host, from any of its planets' rows, and otherwise derived — the diff --git a/src/app/features/galaxy-system/galaxy-system-scene.component.spec.ts b/src/app/features/galaxy-system/galaxy-system-scene.component.spec.ts index 199a358..a79b5e7 100644 --- a/src/app/features/galaxy-system/galaxy-system-scene.component.spec.ts +++ b/src/app/features/galaxy-system/galaxy-system-scene.component.spec.ts @@ -1016,6 +1016,15 @@ describe('GalaxySystemSceneComponent camera-flight transitions', () => { expect(scene.hudReadouts().find((readout) => readout.label === 'Radius')?.value).toBe('0.141 solar radii'); }); + it('tints a host in the star field the colour its disc is drawn in, at the temperature the archive gives it', async () => { + // Proxima's B−V 1.8 reads 3 070 K; its disc is drawn at the archive's 2 900. + const scene = await enter(PROXIMA); + const field = (scene as unknown as { starField: { catalogueColors: Float32Array } }).starField.catalogueColors; + const at = STARS.indexOf(PROXIMA) * 3; + const disc = scene.starTint.value; + expect([field[at], field[at + 1], field[at + 2]].map((channel) => channel.toFixed(4))).toEqual([disc.r, disc.g, disc.b].map((channel) => channel.toFixed(4))); + }); + it("warms a host's planets by the luminosity the archive gives it, in the system as on their own page", async () => { const scene = await enter(PROXIMA); const planet = (scene.systemRenderer as unknown as { members: { id: string; marker: THREE.Mesh }[] }).members.find((member) => member.id === PROXIMA_B.id)!; diff --git a/src/app/features/galaxy-system/galaxy-system-scene.component.ts b/src/app/features/galaxy-system/galaxy-system-scene.component.ts index 1d95359..3dcf282 100644 --- a/src/app/features/galaxy-system/galaxy-system-scene.component.ts +++ b/src/app/features/galaxy-system/galaxy-system-scene.component.ts @@ -58,7 +58,7 @@ import { type ScaleBar, } from '../../shared/format/scale-bar'; import { BodyDetailViewModel } from '../body-detail/body-detail.model'; -import { buildBodyViewModel, starSurfaceOf, StarSurface } from '../body-detail/body-view-model'; +import { buildBodyViewModel, publishedTemperaturesK, starSurfaceOf, StarSurface } from '../body-detail/body-view-model'; import { DEFAULT_HUD_DISPLAY, HudDisplay, @@ -762,6 +762,7 @@ export class GalaxySystemSceneComponent implements AfterViewInit, OnDestroy { positions, starRenderBudgetFromUrl(window.location.search), this.starsByBrightness, + publishedTemperaturesK(exoplanets), ); this.hostStars = Uint8Array.from(stars, (star) => this.starIdsWithBodies.has(star.id) ? 1 : 0, diff --git a/src/app/features/galaxy-system/star-field-renderer.ts b/src/app/features/galaxy-system/star-field-renderer.ts index fda38ce..860c263 100644 --- a/src/app/features/galaxy-system/star-field-renderer.ts +++ b/src/app/features/galaxy-system/star-field-renderer.ts @@ -101,9 +101,11 @@ const NEUTRAL_STAR_COLOR = new THREE.Color(1.0, 1.0, 1.0); /** * A star's tint in the field: the colour of a blackbody at its effective temperature against the - * display's white — the tint its own disc is drawn in, in its system. The temperature is the one - * the disc is drawn at (`effectiveTemperatureK`): off the dwarf sequence at the star's colour, in - * B−V or Gaia's BP−RP, off the type where there is no colour, and off the type for a giant. + * display's white — the tint its own disc is drawn in, in its system. For a star with no planets + * the temperature is the one the disc is drawn at (`effectiveTemperatureK`): off the dwarf sequence + * at the star's colour, in B−V or Gaia's BP−RP, off the type where there is no colour, and off the + * type for a giant. A host's disc is drawn at the archive's temperature instead, which the renderer + * is given apart and tints it at ({@link temperatureTint}). * * `colorIndex` is `null` for the ~10% of stars HYG never photometered. Those fall back to their * `spectralType`, and to neutral white only when the catalog records no classification at all — @@ -117,7 +119,11 @@ const NEUTRAL_STAR_COLOR = new THREE.Color(1.0, 1.0, 1.0); */ export function colorIndexToRgb(colorIndex: number | null, spectralType?: string, colorSystem?: 'B-V' | 'BP-RP'): THREE.Color { // The distance is only there to say the star is not the Sun; the temperature reads none of the rest. - const temperatureK = effectiveTemperatureK({ magnitude: 0, distancePc: 1, spectralType, colorIndex, colorSystem }); + return temperatureTint(effectiveTemperatureK({ magnitude: 0, distancePc: 1, spectralType, colorIndex, colorSystem })); +} + +/** The field's tint at a temperature: a blackbody against the display's white, neutral with none. */ +export function temperatureTint(temperatureK: number | null): THREE.Color { if (temperatureK === null) { return new THREE.Color().copy(NEUTRAL_STAR_COLOR); } @@ -291,7 +297,14 @@ export class StarFieldRenderer { private readonly catalogue: readonly StarRecord[], private readonly cataloguePositions: Float32Array, budget = STAR_RENDER_BUDGET, - brightness?: BrightnessIndex + brightness?: BrightnessIndex, + /** + * The temperature the archive gives each planet host, by star id, which its disc is drawn at + * (`publishedTemperaturesK`). Tinted off its colour instead, 1 755 of the 4 485 hosts differed + * from their own disc by more than 0.1 in RGB: Kepler-186 at 3 096 K in the field and 3 788 on + * its disc, HD 97048 at 6 825 and 10 000. + */ + publishedTemperaturesK?: ReadonlyMap ) { this.budget = budget; this.brightness = brightness ?? brightnessIndex(catalogue); @@ -305,7 +318,8 @@ export class StarFieldRenderer { this.catalogueColors = new Float32Array(catalogue.length * 3); this.catalogueSizes = new Float32Array(catalogue.length); catalogue.forEach((star, index) => { - const color = colorIndexToRgb(star.colorIndex, star.spectralType, star.colorSystem); + const published = publishedTemperaturesK?.get(star.id); + const color = published === undefined ? colorIndexToRgb(star.colorIndex, star.spectralType, star.colorSystem) : temperatureTint(published); this.catalogueColors[index * 3] = color.r; this.catalogueColors[index * 3 + 1] = color.g; this.catalogueColors[index * 3 + 2] = color.b;