Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,10 @@ unsupported versions rather than silently relabelling data.
sessions; Source-qualified remains selectable. See [RFC 0007](docs/rfcs/0007-illustrative-planet-looks.md) for review
status and limits, and the [rollout review](docs/PLANET_ROLLOUT_REVIEW_20260925.md)
for local evidence and outstanding delivery steps.
Saturn's Illustrative look also uses the recovered Look Lab color response and
inspection lighting to reveal polar colors. Its body card offers Sun-directed
lighting for seasonal illumination and moon shadows at the modeled date.
Saturn's Illustrative look keeps the recovered Look Lab color response and defaults
to sunlight shared with its rings and moons. Shadows follow the modeled Sun as the
camera moves. Its body card also offers an explicit Look Lab inspection light to
reveal polar colors, with moon-transit shadows disabled in that mode.
- **Earth Look Lab appearance (candidate):** the latest published Sites Earth
joins Illustrative look: NASA July 2004 surface, dark-ocean grading, 1.6 display
exposure, and the recovered cloud-volume/texture-lighting recipe. Source-qualified
Expand Down
4 changes: 2 additions & 2 deletions apps/web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -286,8 +286,8 @@ <h2 id="destinationTitle" aria-live="polite" aria-atomic="true"></h2>
<div id="orreryPlanetPhenomena" hidden></div>
<div id="orrerySaturnControls" class="physical-source-controls" hidden>
<label for="orrerySaturnLighting">Saturn lighting</label>
<select id="orrerySaturnLighting" class="sky-input"><option value="look-lab" selected>Look Lab · reveal color and poles</option><option value="sun-directed">Sun-directed · modeled date</option></select>
<p>Look Lab uses an inspection light near the camera. Choose Sun-directed for seasonal illumination and moon shadows.</p>
<select id="orrerySaturnLighting" class="sky-input"><option value="sun-directed" selected>Sun-directed · default</option><option value="look-lab">Look Lab · inspection light</option></select>
<p>The Sun lights Saturn, its rings and moons at the modeled date. Look Lab replaces Saturn's sunlight with a camera-relative inspection light and hides moon-transit shadows.</p>
</div>
<div id="orrerySolarControls" class="physical-source-controls" hidden>
<label for="orrerySolarMode">See the Sun in</label>
Expand Down
2 changes: 1 addition & 1 deletion apps/web/js/illustrativeAppearance.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export function illustrativeDescription(body, state = {}) {
: 'Focus or zoom in to load this look. ';
const light=body==='Saturn'&&status==='ready'?(state.saturnLighting==='look-lab'
? 'Look Lab inspection lighting reveals surface color and polar detail. Lighting and ring shadows are illustrative; Sun-directed restores illumination and moon shadows for the modeled date. '
: 'Saturn uses Sun-directed lighting with the Look Lab color response; an unlit pole remains dark. '):'';
: 'The Sun lights Saturn, its rings and moons at the modeled date. The Look Lab color response is retained; shadows follow the Sun and an unlit pole remains dark. '):'';
return `${readiness}${light}Illustrative look suppresses the registered surface for this planet. Solar System Scope / INOVE · CC BY 4.0. Artistic color and reconstructed coverage; not a registered observation, calibrated color or current weather. Measured terrain relief stays suspended while this look is selected, including loading, failure, and when the map is not retained.`;
}

Expand Down
2 changes: 1 addition & 1 deletion apps/web/js/orrery.js
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ const state = (store.orrery = {
showOrbits: true, showSky: true, showConst: false, showLabels: true, showSunEq: false, useTextures: true, galaxy: false,
earthNight: true, earthWeather: true, earthIce: false, earthCloudSource: 'composite', venusRadar: false,
appearanceStatus: {}, planetLook: 'illustrative', illustrativeStatus: {},
saturnLighting: 'look-lab',
saturnLighting: 'sun-directed',
illustrativeDemandBodies: /** @type {string[]} */ ([]),
illustrativeVisibleFocused: /** @type {string[]} */ ([]),
earthLookStatus: 'deferred', earthLookPhase: 0,
Expand Down
12 changes: 7 additions & 5 deletions docs/SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,13 @@ adds the default fresh-session artistic mode for seven planets. Source-qualified
remains selectable; the artistic maps carry no geographic, color or observation qualification.
Measured terrain stays suspended for the whole time Illustrative look is selected,
including loading, failure, and eviction, not only while an artistic map is shown.
Saturn's artistic material uses the recovered Look Lab color response. Its default
inspection light follows the camera to reveal polar detail; Sun-directed lighting
remains selectable without changing positions, epoch, scale or IAU orientation.
Inspection ring shading uses the same light as the globe; real-Sun moon transit
shadows are shown only in Sun-directed mode. Source-qualified keeps Sun lighting.
Saturn's artistic material uses the recovered Look Lab color response and defaults
to Sun-directed illumination, shared with its rings and moons. Camera movement MUST
NOT change the Sun direction or shadow geometry. Moon lighting uses physical offsets,
not enlarged display positions. The optional Look Lab inspection light follows the
camera for both globe and rings; real-Sun moon transit shadows are disabled only in
that mode. Neither mode changes positions, epoch, scale or IAU orientation.
Source-qualified keeps Sun lighting.
Venus with Magellan radar keeps that registered ground and draws the artistic
atmosphere above it. Moons retain their existing materials. The Solar System Sun follows
[RFC 0010](rfcs/0010-approved-sun-look.md): the approved illustrative v2 recipe
Expand Down
17 changes: 11 additions & 6 deletions docs/rfcs/0007-illustrative-planet-looks.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,13 @@ It also retained astronomical sunlight, whereas the lab used a 22-degree
camera-relative key light. At dates when the north pole is unlit, those views
cannot show comparable polar colors even with identical image bytes.

Saturn now uses the recovered material in Illustrative look. Its body card offers
Look Lab inspection lighting (default) and Sun-directed lighting. The inspection
light illuminates both globe and rings consistently. It does not reuse real-Sun
moon-transit shadows; those return with Sun-directed lighting. No positions,
Saturn uses the recovered material in Illustrative look. Sun-directed lighting is
the default: the globe, rings and moons share the modeled Sun as their light source.
Orbiting the camera changes the view of the shadows, not their direction. Moon
lighting uses physical offsets rather than the enlarged display positions.
The body card retains optional Look Lab inspection lighting. That light follows
the camera for both globe and rings and disables real-Sun moon-transit shadows.
It is an explicit appearance inspection mode, not the solar-system default. No positions,
rotation, physical/display sizes or simulation time change. Other planets and
Saturn's Source-qualified path retain their existing materials and lighting.
Map loading/failure retains the original fallback. No texture bytes, new polar
Expand All @@ -79,8 +82,10 @@ The recovered source is recorded in
[the Sites v7 provenance](../validation/sites-earth-v7/source.json).
`tools/saturn_look_probe.mjs` checks literal lab color samples through the real
GPU sphere shader, including linear composition, and checks both poles at four
viewing angles plus Sun-directed rollback. Runtime tests verify the material
cannot leak to moons, pending maps, disabled textures or Source-qualified.
viewing angles plus the default Sun-directed mode. Runtime tests verify solar
illumination stays fixed while the camera moves and that a synthetic moon transit
is preserved. They also verify the material cannot leak to moons, pending maps,
disabled textures or Source-qualified.

### Existing appearance controls

Expand Down
2 changes: 1 addition & 1 deletion tests/web/helpers/orreryHarness.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ export async function orreryHarness(t, options = {}) {
orreryAnimate: node({ checked: true }), orreryNow: node(),
};
if (options.controls) {
nodes.orrerySaturnControls=node();nodes.orrerySaturnLighting=node({value:'look-lab'},'select');
nodes.orrerySaturnControls=node();nodes.orrerySaturnLighting=node({value:'sun-directed'},'select');
for(const id of ['InspectSun','PhysicalStatus','SolarControls','SolarMode','SolarPlay','SolarRestart','SolarTime','SolarEpoch','SunLookControls','SolarArchiveControls','SunResolution','SunProminences','SunLookPlay','SunLookRestart','SunLookStatus','Terrain','Optics'])nodes[`orrery${id}`]=node();
if(options.phenomenonImage)nodes.orreryPlanetPhenomena=node();
for (const id of ["Backend", "MetadataEpoch", "ScaleStatus", "SelectedEpoch", "SelectionStatus", "Detail", "Labels", "Positions", "Search", "ObjectGroup", "FocusSelected", "Time", "Size", "TrueScale", "Speed", "SpeedLabel", "SpeedExtras", "SpeedEntry", "SpeedUnit", "SpeedPresets", "ShowOrbits", "ShowSky", "ShowConst", "ShowLabels", "ShowSunEq", "ShowSmall", "ShowMoons", "DeepSky", "Textures", "PlanetLook", "EarthNight", "EarthWeather", "EarthIce", "EarthLayerStatus", "IceLegend", "IceLegendCaption", "TopDown", "Anchor", "FreeFly", "Galaxy", "Local"]) {
Expand Down
57 changes: 57 additions & 0 deletions tests/web/saturnLookRuntime.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,62 @@ import test from 'node:test';
import assert from 'node:assert/strict';
import {orreryHarness} from './helpers/orreryHarness.mjs';
import {BODY} from '../../apps/web/js/bodyData.js';
import {MOONS} from '../../apps/web/js/moons.js';
import {MOON_ELEMENTS} from '../../apps/web/js/moonelements.js';
import {moonOffsetAU} from '../../apps/web/js/moonorbits.js';
import {SYSTEM_ORDER} from '../../apps/web/js/systemContract.js';

test('fresh Saturn uses the Sun for its globe, rings and moons, independent of the camera',async t=>{
// Synthetic alignment, not a claimed historical transit: retain real moon offsets
// and place Saturn opposite Tethys so its shadow must reach the globe.
const tethys={...MOONS.find(m=>m.n==='Tethys'),...MOON_ELEMENTS.Tethys};
const h=await orreryHarness(t,{controls:true,catalogues:'ready',useProductAppearanceDefault:true,
illustrativeMap:async()=>({width:2048,height:1024,close(){}}),
systemPositions(unix){
const offset=moonOffsetAU(tethys,unix),distance=Math.hypot(...offset);
return Float64Array.from(SYSTEM_ORDER.flatMap((name,i)=>name==='Saturn'
?offset.map(value=>-9.5*value/distance):name==='Sun'?[0,0,0]:[i+1,0,0]));
}});
await h.enterOrrery();t.after(()=>h.leaveOrrery());h.setAnimate(false);
await h.settleCatalogues();h.input('orreryAnchor','Saturn','change');
await h.settle();await h.settle();
assert.equal(h.state.saturnLighting,'sun-directed');
assert.equal(h.nodes.orrerySaturnLighting.value,'sun-directed');
const body=h.state.bodies.find(b=>b.name==='Saturn'),position=[body.x_au,body.y_au,body.z_au];
const direction=pos=>pos.map(value=>-value/Math.hypot(...pos));
const near=(actual,expected)=>expected.forEach((value,i)=>assert.ok(Math.abs(actual[i]-value)<1e-6));
const paint=()=>{const start=h.gpuSubmissions.length;h.check('orreryTextures',true);return h.gpuSubmissions.slice(start);};
const surface=frame=>frame.find(draw=>draw.uniforms.u_bodyRadiusKm===BODY.Saturn.radiusKm&&draw.uniforms.u_mode===0).uniforms;
const epoch=h.state.renderUnix,positions=JSON.stringify(h.state.bodies);
const checkSolar=()=>{
const frame=paint(),globe=surface(frame);
near(globe.u_light,direction(position));
assert.equal(globe.u_saturnLook,1,'recovered colors remain enabled under sunlight');
assert.equal(globe.u_useTex,1);
assert.ok(globe.u_moonShadowCount>0,'the synthetic Tethys transit must not be suppressed');
const ring=frame.find(draw=>draw.kind==='arrays'&&draw.uniforms.u_prad!==undefined
&&draw.uniforms.u_center.every((value,i)=>Math.abs(value-position[i])<1e-6));
assert.ok(ring,'Saturn rings must be submitted');
assert.deepEqual(ring.uniforms.u_light,globe.u_light,'globe and ring shadows share the solar direction');
for(const name of ['Tethys','Titan']){
const moon=h.moons.find(m=>m.n===name),offset=moonOffsetAU(moon,epoch);
const draw=frame.find(d=>d.uniforms.u_bodyRadiusKm===moon.r&&d.uniforms.u_mode===0);
assert.ok(draw,`${name} must be drawn`);
near(draw.uniforms.u_light,direction(position.map((value,i)=>value+offset[i])));
assert.equal(draw.uniforms.u_saturnLook,0);
}
return globe;
};
const initial=checkSolar();h.state.az+=Math.PI/2;h.state.el=-.8;h.state.radius*=1.2;
const orbited=checkSolar();
for(const key of ['u_light','u_lightObj','u_moonShadowCount','u_moonShadowPos[0]','u_moonShadowAxis[0]'])
assert.deepEqual(orbited[key],initial[key],`${key} must not follow the camera`);
h.input('orrerySaturnLighting','look-lab','change');
const inspection=surface(paint());assert.notDeepEqual(inspection.u_light,initial.u_light);
assert.equal(inspection.u_moonShadowCount,0,'inspection remains an explicit separate mode');
h.input('orrerySaturnLighting','sun-directed','change');checkSolar();
assert.equal(h.state.renderUnix,epoch);assert.equal(JSON.stringify(h.state.bodies),positions);
});

test('only a ready illustrative Saturn map receives the recovered display material',async t=>{
let complete;
Expand All @@ -13,6 +69,7 @@ test('only a ready illustrative Saturn map receives the recovered display materi
const saturn=draw=>draw.uniforms.u_bodyRadiusKm===BODY.Saturn.radiusKm&&draw.uniforms.u_mode===0;
const paint=()=>{const start=h.gpuDraws.length;h.check('orreryTextures',h.state.useTextures);return h.gpuDraws.slice(start);};
assert.ok(paint().filter(saturn).every(draw=>draw.uniforms.u_saturnLook===0),'pending maps keep the existing fallback');
h.input('orrerySaturnLighting','look-lab','change');
complete({width:2048,height:1024,close(){}});await h.settle();await h.settle();
const epoch=h.state.renderUnix,positions=JSON.stringify(h.state.bodies),frame=paint();
assert.ok(frame.some(draw=>saturn(draw)&&draw.uniforms.u_saturnLook===1&&draw.uniforms.u_useTex===1));
Expand Down
Loading