Follow-up to review on #3. The card and the detail page each built their own Measured/Derived split, kind label and provenance sentence — the drift buildBodyViewModel exists to prevent, re-forked one layer up, and the drift would have been in which side of the measured/derived line a quantity falls on, which is the distinction those panels exist to draw. One bodyReadouts(body) now returns both blocks and the sentence, and both templates iterate it. The two surfaces render identical rows as a result, and the card gains the inclination the detail page already showed. Also from that review: - KIND_LABELS was duplicated between the two panels; it now lives beside bodyReadouts. The third copy the review pointed at is a different union (search results are star/body/exoplanet, and label a body "Body"), so it stays where it is. - CardRow was HudReadout renamed. Both are now Readout, which HudReadout extends with its derived flag. - The enterable-systems count was a 21-line lazy memo over arrays that are already in hand; it is one expression where those arrays are assigned. - buildBodyViewModel now carries hostStarId, so the detail scene stops rescanning both catalogues for something the builder had already resolved. - heliocentricPeriodDays was called twice for the same body. - The superscript helper was a split/map/join; it is a replace. - info-panel had five computed() each wrapping one pure call with a non-null assertion, beside a template that inlined the same kind of call directly. They are gone with the shared readouts. - Dropped a tautological test that compared a pure function to itself. Replaced with one that asserts the host star id the builder now carries. - Removed the orphaned doc comment left behind when formatParsecs/formatAu moved out. And one the review raised as out of scope but is worth taking: formatRadiusKm grouped thousands above its decimal threshold and not below it, so 69,911 km sat beside a bare 6371 km. Both are grouped now. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WaySiNst4HhDXBHnMy8p5G
100 lines
4.3 KiB
TypeScript
100 lines
4.3 KiB
TypeScript
import { appearanceForBody, appearanceForExoplanet } from '../../shared/astro/body-appearance';
|
|
import { EARTH_RADIUS_KM } from '../../shared/astro/planet-appearance';
|
|
import { luminositySolar } from '../../shared/astro/stellar';
|
|
import { bodyTexturePath } from '../../shared/rendering/texture-catalog';
|
|
import { BodyRecord } from '../../shared/models/body.model';
|
|
import { ExoplanetRecord } from '../../shared/models/exoplanet.model';
|
|
import { StarRecord, SUN_STAR_ID } from '../../shared/models/star.model';
|
|
import { BodyDetailViewModel } from './body-detail.model';
|
|
|
|
/** Everything the view model is assembled from — the three catalogues, already loaded. */
|
|
export interface BodyCatalogues {
|
|
readonly bodies: readonly BodyRecord[];
|
|
readonly exoplanets: readonly ExoplanetRecord[];
|
|
readonly stars: readonly StarRecord[];
|
|
}
|
|
|
|
/**
|
|
* Bolometric luminosity of a star in solar units, from what the catalogue measured: apparent
|
|
* magnitude, parallax distance, and a bolometric correction read off the spectral type.
|
|
*/
|
|
export function luminosityOf(star: StarRecord | undefined): number | null {
|
|
if (!star) {
|
|
return null;
|
|
}
|
|
return luminositySolar({
|
|
magnitude: star.magnitude,
|
|
distancePc: Math.hypot(star.x, star.y, star.z),
|
|
spectralType: star.spectralType,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Builds the flattened view model for one body or exoplanet.
|
|
*
|
|
* Shared rather than duplicated per surface: the in-map card and the full detail page show
|
|
* overlapping subsets of the same quantities, and two independent assemblies of "what do we
|
|
* know about this world" is exactly the shape of bug where a planet reads 255 K in one panel
|
|
* and 254 K in the other.
|
|
*/
|
|
export function buildBodyViewModel(id: string, catalogues: BodyCatalogues): BodyDetailViewModel | undefined {
|
|
const body = catalogues.bodies.find((candidate) => candidate.id === id);
|
|
if (body) {
|
|
const hostStar = catalogues.stars.find((star) => star.id === body.systemStarId);
|
|
const periodDays = heliocentricPeriodDays(body);
|
|
return {
|
|
id: body.id,
|
|
name: body.name,
|
|
kind: body.kind,
|
|
hostStarName: hostStar?.name ?? 'Unknown star',
|
|
hostStarId: body.systemStarId,
|
|
radiusKm: body.radiusKm,
|
|
orbit: body.orbit,
|
|
appearance: appearanceForBody(body, catalogues.bodies, luminosityOf(hostStar)),
|
|
hasPhotography: bodyTexturePath(body.id) !== undefined,
|
|
orbitalPeriodDays: periodDays,
|
|
orbitalPeriodSource: periodDays === undefined ? undefined : 'derived',
|
|
};
|
|
}
|
|
|
|
const exoplanet = catalogues.exoplanets.find((candidate) => candidate.id === id);
|
|
if (!exoplanet) {
|
|
return undefined;
|
|
}
|
|
const hostStar = catalogues.stars.find((star) => star.id === exoplanet.hostStarId);
|
|
return {
|
|
id: exoplanet.id,
|
|
name: exoplanet.name,
|
|
kind: 'exoplanet',
|
|
hostStarName: exoplanet.hostStarName,
|
|
hostStarId: exoplanet.hostStarId ?? undefined,
|
|
radiusKm: exoplanet.radiusEarth ? exoplanet.radiusEarth * EARTH_RADIUS_KM : undefined,
|
|
massEarth: exoplanet.massEarth,
|
|
discoveryYear: exoplanet.discoveryYear,
|
|
orbit: exoplanet.orbit,
|
|
appearance: appearanceForExoplanet(exoplanet, luminosityOf(hostStar)),
|
|
hasPhotography: bodyTexturePath(exoplanet.id) !== undefined,
|
|
// `periodDays` is populated for none of the shipped records, and deriving one would need the
|
|
// host star's mass, which is equally absent. Left undefined rather than assuming a solar-mass
|
|
// host, which would silently mis-state the period of every planet around an M dwarf.
|
|
orbitalPeriodDays: exoplanet.periodDays,
|
|
orbitalPeriodSource: exoplanet.periodDays === undefined ? undefined : 'measured',
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Kepler's third law for a body orbiting the Sun: P² = a³ with P in years and a in AU, which
|
|
* holds exactly in these units because the Sun's mass is the unit of mass.
|
|
*
|
|
* Only for heliocentric orbits. A moon's elements are relative to its parent planet, whose mass
|
|
* the catalogue does not carry, so the same arithmetic there would be wrong by the ratio of the
|
|
* planet's mass to the Sun's — a factor of a thousand for Jupiter.
|
|
*/
|
|
export function heliocentricPeriodDays(body: BodyRecord): number | undefined {
|
|
if (body.parentBodyId !== undefined || body.systemStarId !== SUN_STAR_ID) {
|
|
return undefined;
|
|
}
|
|
const a = body.orbit.semiMajorAxisAu;
|
|
return a > 0 ? Math.pow(a, 1.5) * 365.25 : undefined;
|
|
}
|