diff --git a/packages/bake/AGENTS.md b/packages/bake/AGENTS.md index 1f22cee15ba..5275eccf043 100644 --- a/packages/bake/AGENTS.md +++ b/packages/bake/AGENTS.md @@ -88,7 +88,7 @@ its validators accept); the renderer never imports the bake. writes and tests read back, with its prepared resource catalogue, validated against the prepared-presentation contract and the renderer's object controls, and the audits that read a prepared presentation and its authored runtime sources back against the descriptor (`check-prepared-presentation.ts`; the prepared format constant comes - from the renderer's `prepared-data/object-format.ts`). The audit reads the registry on first use, not at import. It imports + from `@cssearth/objects`). The audit reads the registry on first use, not at import. It imports `presentation`, `runtime-source` and `sources`. `packages/bake/cli/check-prepared-presentation.mts` is the audit's command, run for each object by the `audit` step of `prepare-object`. `prepared-object-pin.ts` pins a prepared object to its transport (the descriptor's `prepared` pin and page reference, and the inventory; the transports themselves are built from diff --git a/packages/bake/src/contract/check-prepared-presentation.ts b/packages/bake/src/contract/check-prepared-presentation.ts index d9a4db15b3e..e600a571785 100644 --- a/packages/bake/src/contract/check-prepared-presentation.ts +++ b/packages/bake/src/contract/check-prepared-presentation.ts @@ -9,7 +9,7 @@ import type { ObjectEntry } from '@cssearth/objects'; import { requirePreparedPresentation, PREPARED_OBJECT_RUNTIME_SCHEMA } from "../presentation/index.ts"; import { requireObjectRuntimeDefinition } from "./object-runtime-contract.ts"; import { requireAuthoredWorldFrame } from '../sources/index.ts'; -import { PREPARED_CSS_OBJECT_FORMAT } from '@cssearth/renderer/prepared-data/object-format.ts'; +import { PREPARED_CSS_OBJECT_FORMAT } from '@cssearth/objects'; import { requireObjectControls } from '@cssearth/renderer/runtime/shell-contract.ts'; import { nodeName, staticObjectProperties } from '../runtime-source/index.ts'; import type { RuntimeSourceReader } from '../runtime-source/index.ts'; diff --git a/packages/bake/src/contract/prepared-object-pin.ts b/packages/bake/src/contract/prepared-object-pin.ts index 7648f065aaf..e3e1b6aadee 100644 --- a/packages/bake/src/contract/prepared-object-pin.ts +++ b/packages/bake/src/contract/prepared-object-pin.ts @@ -8,7 +8,7 @@ import { dirname, resolve } from 'node:path'; import { requireRecord } from '@cssearth/core'; import { parseObjectDescriptor } from '@cssearth/objects'; import { inventoryPreparedAssets, readInventory } from '@cssearth/objects/node'; -import { PREPARED_CSS_OBJECT_FORMAT } from '@cssearth/renderer/prepared-data/object-format.ts'; +import { PREPARED_CSS_OBJECT_FORMAT } from '@cssearth/objects'; import { preparePageMetadata, writePreparedText } from '../delivery/index.ts'; import { requireObjectRuntimeDefinition } from './object-runtime-contract.ts'; diff --git a/packages/bake/src/objects/layers/material-composition/object.ts b/packages/bake/src/objects/layers/material-composition/object.ts index 4ea5051308f..05845850880 100644 --- a/packages/bake/src/objects/layers/material-composition/object.ts +++ b/packages/bake/src/objects/layers/material-composition/object.ts @@ -1,6 +1,6 @@ import {readAuthoredSources} from '../../sources/index.ts'; import {parse} from '@cssearth/core/schema'; -import {PREPARED_CSS_OBJECT_FORMAT} from '@cssearth/renderer/prepared-data/object-format.ts'; +import {PREPARED_CSS_OBJECT_FORMAT} from '@cssearth/objects'; import { layeredRecipe } from './layered-recipe.ts'; import { spectralRecipe } from './spectral-recipe.ts'; import { radialMotionRecipe } from './radial-motion-recipe.ts'; diff --git a/packages/objects/AGENTS.md b/packages/objects/AGENTS.md index 888dc9c7ca2..06d8004767f 100644 --- a/packages/objects/AGENTS.md +++ b/packages/objects/AGENTS.md @@ -15,6 +15,9 @@ layer-plan/report readers and render-element budgets; exported through the main volume and sampled recipes, emission-fit settings, physical slice formats and validation, compiler controls and star inputs, joint parameters, authored shape settings, observation photo data and prepared catalogue stars. Writers and readers import these from `@cssearth/objects`; sampling, fitting, cancellation and file I/O remain outside this area. +`src/prepared-data/` owns the prepared CSS object format identifier and compact world-summary/system table decoders, +exported through the browser-safe main entry. Full world-context validation still resides in renderer pending extraction of shared frame/presentation contracts +from camera/projection modules; do not import renderer implementation here. An object type describes supported behavior and data, not an individual planet. Do not ship per-object configuration, generated payload modules, shell content, or renderer code here. Keep one shared object contract; application discovery remains in the existing registry. diff --git a/packages/objects/README.md b/packages/objects/README.md index 43b4f29963c..a737002d319 100644 --- a/packages/objects/README.md +++ b/packages/objects/README.md @@ -89,3 +89,8 @@ per-planet code or data folders. Concrete CSS/DOM rendering belongs to the application renderer. Earth is a future consumer, outside this migration's scope. Prepared volume scene contracts and their pure validators/helpers live in `src/volume/`, exported by `@cssearth/objects`. This includes volume and sampled recipes, emission-fit settings, slice data and layer validation, compiler controls and star inputs, joint parameters, authored shape settings, observation photo data and prepared catalogue stars. Writers and readers import the same contracts here. Baking, fitting, sampling, cancellation and I/O remain in `@cssearth/bake`. + +The browser-safe main entry exports `PREPARED_CSS_OBJECT_FORMAT`, `expandWorldContextSummary()` and +`expandWorldSystem()` from `src/prepared-data/`. The decoders expand compact body columns, shared tables, +billboards and orbit centres before validation. Full world-context validation still lives in the renderer, +where its shared geometry parser depends on camera/projection implementation. diff --git a/packages/objects/src/index.ts b/packages/objects/src/index.ts index dc093a15cca..483e9443362 100644 --- a/packages/objects/src/index.ts +++ b/packages/objects/src/index.ts @@ -36,3 +36,5 @@ export * from './volume/sampled-emission-fit.js'; export * from './volume/sampled-recipe.js'; export * from './volume/volume-recipe.js'; export * from './volume/volume-slices.js'; +export { PREPARED_CSS_OBJECT_FORMAT } from './prepared-data/object-format.js'; +export { expandWorldContextSummary, expandWorldSystem } from './prepared-data/world-context-summary.js'; diff --git a/packages/renderer/src/prepared-data/object-format.ts b/packages/objects/src/prepared-data/object-format.ts similarity index 100% rename from packages/renderer/src/prepared-data/object-format.ts rename to packages/objects/src/prepared-data/object-format.ts diff --git a/packages/objects/src/prepared-data/world-context-summary.test.ts b/packages/objects/src/prepared-data/world-context-summary.test.ts new file mode 100644 index 00000000000..6f1737f713b --- /dev/null +++ b/packages/objects/src/prepared-data/world-context-summary.test.ts @@ -0,0 +1,48 @@ +import assert from 'node:assert/strict'; +import { test } from 'node:test'; +import { expandWorldContextSummary, expandWorldSystem } from './world-context-summary.js'; +import { PREPARED_CSS_OBJECT_FORMAT } from './object-format.js'; + +const summary = () => ({ + schema: 'cssearth-world-context-summary@2', + systemNames: ['Solar System'], discoveries: [{ method: 'imaging' }], + billboard: { size: 512, focalPixels: 256, distanceRadii: 8 }, + focus: { id: 'sun', positionM: [0, 0, 0], radiusM: 10 }, + bodies: { id: ['earth'], radiusM: [2], positionM: [[20, 0, 0]], system: [0], discovery: [0], + billboard: [{}], orbit: [{ centerBodyId: 'sun', bounds: { centerM: [0, 0, 0], radiusM: 20 }, lod: true }], optional: [null] }, + deferred: { id: ['moon'], host: ['earth'], system: [0], discovery: [0] }, +}); + +test('prepared format and compact world tables preserve the expanded transport shape', () => { + assert.equal(PREPARED_CSS_OBJECT_FORMAT, 'cssearth-css-object@5'); + assert.deepEqual(expandWorldContextSummary(summary()), { + schema: 'cssearth-world-context-summary@2', focus: { id: 'sun', positionM: [0, 0, 0], radiusM: 10 }, + bodies: [{ id: 'earth', radiusM: 2, positionM: [20, 0, 0], systemName: 'Solar System', discovery: { method: 'imaging' }, + billboard: { url: '/scenes/earth/earth-billboard.webp', size: 512, focalPixels: 256, distanceM: 16 }, + orbit: { centerBodyId: 'sun', centerPositionM: [0, 0, 0], bounds: { centerM: [0, 0, 0], radiusM: 20 }, + lod: { bounds: { centerM: [0, 0, 0], radiusM: 20 } } } }], + deferred: [{ id: 'moon', host: 'earth', systemName: 'Solar System', discovery: { method: 'imaging' } }], + }); + const expanded = { schema: 'cssearth-world-context-summary@2', bodies: [] }; + assert.equal(expandWorldContextSummary(expanded), expanded); +}); + +test('compact world tables reject unequal columns, missing entries and unknown orbit centres', () => { + const unequal = summary(); unequal.bodies.id.push('mars'); + assert.throws(() => expandWorldContextSummary(unequal), /has 1 entries, not 2/); + const missing = summary(); missing.bodies.system[0] = 1; + assert.throws(() => expandWorldContextSummary(missing), /not an index into its 1-entry table/); + const unknown = summary(); unknown.bodies.orbit[0]!.centerBodyId = 'missing'; + assert.throws(() => expandWorldContextSummary(unknown), /parent missing is not placed/); +}); + +test('deferred systems resolve shared orbit centres without overriding explicit billboard fields', () => { + const input = { schema: 'cssearth-world-system@1', id: 'earth', systemNames: [], discoveries: [], billboard: {}, + bodies: { id: ['moon'], radiusM: [1], billboard: [{ url: '/moon.webp', size: 32, focalPixels: 16, distanceM: 12 }], + orbit: [{ centerBodyId: 'earth' }] } }; + assert.deepEqual(expandWorldSystem(input, id => id === 'earth' ? [20, 0, 0] : undefined), { + schema: 'cssearth-world-system@1', id: 'earth', bodies: [{ id: 'moon', radiusM: 1, + billboard: { url: '/moon.webp', size: 32, focalPixels: 16, distanceM: 12 }, + orbit: { centerBodyId: 'earth', centerPositionM: [20, 0, 0] } }], + }); +}); diff --git a/packages/renderer/src/prepared-data/world-context-summary.ts b/packages/objects/src/prepared-data/world-context-summary.ts similarity index 98% rename from packages/renderer/src/prepared-data/world-context-summary.ts rename to packages/objects/src/prepared-data/world-context-summary.ts index 734399625c9..5345cee187f 100644 --- a/packages/renderer/src/prepared-data/world-context-summary.ts +++ b/packages/objects/src/prepared-data/world-context-summary.ts @@ -1,4 +1,6 @@ -import { array, positive, record, text, unique } from '../validation/guards.js'; +import { checks, failure } from '@cssearth/core'; + +const { array, positive, record, text, unique } = checks(failure('Prepared presentation: ')); /** `world-context-summary.json` and each `world-systems/.json` write what many bodies repeat once * (`summarizeWorldContext` in @cssearth/bake). These put each body back in the shape `parsePreparedWorldContextSummary` diff --git a/packages/renderer/AGENTS.md b/packages/renderer/AGENTS.md index d61ad0362c2..acc8d30dddc 100644 --- a/packages/renderer/AGENTS.md +++ b/packages/renderer/AGENTS.md @@ -4,7 +4,8 @@ Own the CSS renderer runtime: loading, decoding and validating prepared objects, navigation, retained CSS rendering of bodies, skies, stars, volumes, image layers and labels, and the universe context around the focused object. The site and the preparation tools reach it through `@cssearth/renderer` and its subpaths. The compilers that write the renderer's prepared formats are preparation code; they live in `@cssearth/bake` and import -this package. +this package. Shared format identifiers and compact world-summary/system table decoders live in +`@cssearth/objects`; camera/projection-dependent world-context validation remains here. ## The runtime contract diff --git a/packages/renderer/README.md b/packages/renderer/README.md index 944f6498c45..94e3d14000a 100644 --- a/packages/renderer/README.md +++ b/packages/renderer/README.md @@ -3,7 +3,9 @@ The CSS renderer runtime. It loads, decodes and validates prepared objects, keeps the one shared world camera and its navigation, and renders bodies, skies, star fields, volumes, image layers and labels as retained PolyCSS and CSS, with the universe context around the focused object. The site mounts objects through it; the preparation tools import its -formats and validators so that what they write is what the browser reads. +validators so that what they write is what the browser reads. The prepared CSS format identifier and compact +world-summary/system table decoders are shared through `@cssearth/objects`. The world-context parser remains here +because its full/summary validation shares geometry and camera/projection dependencies. It was `src/renderers/css` until 2026-09-26. The compilers that write its prepared formats stayed there (`src/renderers/css/preparation`) until they moved into `@cssearth/bake`; the object page stylesheets diff --git a/packages/renderer/src/index.ts b/packages/renderer/src/index.ts index 66cf1f7365f..97100946dfd 100644 --- a/packages/renderer/src/index.ts +++ b/packages/renderer/src/index.ts @@ -18,7 +18,7 @@ export type { PreparedAssets } from './rendering/prepared-residency.js'; export { resolvePreparedAssetUrl, rewritePreparedStyleUrls, parsePreparedAssetOrigin, preparedAssetGroup, preparedAssetGroupFile, createPreparedAssetResolver } from './rendering/prepared-asset-origin.js'; export type { PreparedAssetOrigin } from './rendering/prepared-asset-origin.js'; export { CANONICAL_PREPARED_IMAGE_DENSITY } from './rendering/prepared-object-assets.js'; -export { loadPreparedCssObject, loadPreparedDataset, PREPARED_CSS_OBJECT_FORMAT } from './loader.js'; +export { loadPreparedCssObject, loadPreparedDataset } from './loader.js'; export { adoptPreparedDatasetTables, splitPreparedDatasetTables, deferredDatasetIds, preparedDatasetReference } from './prepared-data/dataset-tables.js'; export type { PreparedDatasetTables } from './prepared-data/dataset-tables.js'; export type { PreparedCssTransport } from './loader.js'; diff --git a/packages/renderer/src/loader.ts b/packages/renderer/src/loader.ts index 6ad571aabc5..bd168c87040 100644 --- a/packages/renderer/src/loader.ts +++ b/packages/renderer/src/loader.ts @@ -3,7 +3,6 @@ import { decodePreparedCssObject, requirePreparedCssDescriptor } from './prepare import { decodePreparedObjectInWorker } from './prepared-object-worker-client.js'; import { adoptPreparedDatasetTables, preparedDatasetReference } from './prepared-data/dataset-tables.js'; -export { PREPARED_CSS_OBJECT_FORMAT } from './prepared-object-decoder.js'; export interface PreparedCssTransport { /** Return the exact bytes addressed by the prepared reference. The decoder * owns this buffer and may transfer it to a worker. */ diff --git a/packages/renderer/src/prepared-data/world-context.ts b/packages/renderer/src/prepared-data/world-context.ts index 2faf0b43e43..ca9fb3f30c9 100644 --- a/packages/renderer/src/prepared-data/world-context.ts +++ b/packages/renderer/src/prepared-data/world-context.ts @@ -8,7 +8,7 @@ import type { PreparedWorldCameraFrame } from '../navigation/world-camera.js'; import { cssViewFromOrientation, validateWorldRotation } from '../navigation/world-camera-math.js'; import type { LevelOfDetailPlan, OrbitLineFade } from '../navigation/types.js'; import type { PreparedOrbitStrokes } from '../solar-system/prepared-orbit-strokes.js'; -import { expandWorldContextSummary, expandWorldSystem } from './world-context-summary.js'; +import { expandWorldContextSummary, expandWorldSystem } from '@cssearth/objects'; import { parseClassificationViews, parseSystemView } from './world-system-view.js'; import type { PreparedSystemViewCandidate } from './world-system-view.js'; export { parsePreparedSystemView, type PreparedSystemViewCandidate } from './world-system-view.js'; diff --git a/packages/renderer/src/prepared-object-decoder.ts b/packages/renderer/src/prepared-object-decoder.ts index 14ea1d916f6..b15b8b4da85 100644 --- a/packages/renderer/src/prepared-object-decoder.ts +++ b/packages/renderer/src/prepared-object-decoder.ts @@ -3,9 +3,7 @@ import { parsePreparedObjectRuntime } from './validation/index.js'; import type { ObjectRuntimeDefinition } from './runtime/object-runtime-types.js'; import { record } from './validation/guards.js'; import { parsePreparedAssetOrigin } from './rendering/prepared-asset-origin.js'; -import { PREPARED_CSS_OBJECT_FORMAT } from './prepared-data/object-format.js'; - -export { PREPARED_CSS_OBJECT_FORMAT }; +import { PREPARED_CSS_OBJECT_FORMAT } from '@cssearth/objects'; export function requirePreparedCssDescriptor(input: unknown) { const descriptor = parseObjectDescriptor(input); diff --git a/site/asset-origin.mts b/site/asset-origin.mts index d4ba95f616b..45ac8cc3fe3 100644 --- a/site/asset-origin.mts +++ b/site/asset-origin.mts @@ -179,7 +179,7 @@ export async function rewriteSceneCss(css: string, root = process.cwd()): Promis } /** A world file (`world-context-summary.json` or `world-systems/.json`) with every billboard's published address. - * The file leaves a body's own `/scenes//-billboard.webp` implicit (world-context-summary.ts), and production serves + * The file leaves a body's own `/scenes//-billboard.webp` implicit (`packages/objects/src/prepared-data/world-context-summary.ts`), and production serves * no `/scenes`: the build writes the bucket's address for each. No-op when unset. */ export async function resolveWorldBillboards(text: string, root = process.cwd()): Promise { if (!assetOrigin()) return text;