mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-13 06:42:23 +00:00
Define the renderer contract, with a stub renderer and a conformance suite
The renderer contract is what the application asks of a 3D renderer, so a renderer other than Cesium can draw the same application. It is nine ports (frame, globe, draw, camera, query, input, effects, credits, quality) plus the renderer itself, one strict .d.ts per port in src/render/contract/. PORTS lists every member at run time, and a TypeScript-compiler test fails when the list and the types disagree on one member. An optional member exists only with its capability; a required one answers null or empty without it. Nothing in the application uses the contract yet. The stub renderer (src/render/stub/) implements every port in memory and draws nothing. Its camera is a port of the default camera's maths: views, lookAt, flights with their height arc and easing, rectangle framing on the geodesic midpoint, the view rectangle with its horizon and pole cases, changed thresholds, follows, and flights that finish inside the call. The conformance suite (src/render/conformance/) runs 59 checks against any renderer through the contract alone, in Node or in a browser page. The camera checks compare traces recorded from Cesium's own Camera, headless on a virtual clock (scripts/record-camera-goldens.mjs), to 1 mm, 1e-9 and 1e-3 px. The other checks cover listener rules on every subscription, the frame loop, draw sets, picks, motion clocks, heights, leases, imagery, styles, credits and quality knobs. The stub passes all of them but the model check, which needs a glTF asset. Reference scenes draw one scene per primitive family, a city and a calibration path from orbit to the street, from contract calls only and with no network. /dev/conformance/ draws them on the Cesium scene renderer (src/render/cesium/sceneRenderer.js), records references through a dev-server-only, loopback-only endpoint, and compares captures with pixelmatch's antialias detection plus a solid-pixel count. Measured on Cesium: every MSAA setting passes, and a dropped 4 px point, a dropped 2 px line and a 1 px shift of the view fail. visual.js states the limits. The package exports ./render/contract, ./render/stub and ./render/conformance. Documented in docs/RENDERER-CONTRACT.md, docs/CODE-BOUNDARIES.md and docs/CURRENT-STATE.md.
This commit is contained in:
1 parent
fb30287898
commit
2fc99e7c19
65 files changed
+12761
-9
No files matched your search
@@ -0,0 +1,158 @@
|
||||
# Renderer contract
|
||||
|
||||
The renderer contract is what the application asks of a 3D renderer. A
|
||||
renderer other than the default Cesium one can draw the same application if
|
||||
it meets the contract. The contract is version `0`. Today the application
|
||||
still drives Cesium directly; later work moves the layers onto the contract
|
||||
one family at a time.
|
||||
|
||||
The package exports three modules:
|
||||
|
||||
| Export | What it is |
|
||||
| ---------------------------------- | ------------------------------------------------------------------------------ |
|
||||
| `gods-eye-view/render/contract` | The types, one `.d.ts` per port, plus the run-time member list and shape check |
|
||||
| `gods-eye-view/render/stub` | A renderer that draws nothing, for tests |
|
||||
| `gods-eye-view/render/conformance` | The conformance suite, reference scenes and visual comparison |
|
||||
|
||||
## Ports
|
||||
|
||||
A renderer is created by a `RendererPlugin` (`{ id, create(options) }`). It
|
||||
exposes nine ports, each typed in its own file in `src/render/contract/`:
|
||||
|
||||
| Port | File | Covers |
|
||||
| --------- | -------------- | ------------------------------------------------------------------------------ |
|
||||
| `frame` | `frame.d.ts` | The render-on-demand loop, frame phases, continuous holds, scene time |
|
||||
| `globe` | `globe.d.ts` | Base (photoreal, terrain, vector, imagery), imagery layers, split, sky |
|
||||
| `draw` | `draw.d.ts` | Draw sets: icons, points, lines, shapes, models, quads, meshes, fields, agents |
|
||||
| `camera` | `camera.d.ts` | Pose, views, flights, `lookAt`, follow, user controls, events, matrices |
|
||||
| `query` | `query.d.ts` | Picks, projection, rays, heights |
|
||||
| `input` | `input.d.ts` | Pointer events with their pick made, leases, stock behaviours |
|
||||
| `effects` | `effects.d.ts` | Screen styles, bloom, sharpen, screen modulation |
|
||||
| `credits` | `credits.d.ts` | Data credits owed |
|
||||
| `quality` | `quality.d.ts` | Quality knobs, device facts, frame signals, benchmark |
|
||||
|
||||
The renderer itself (`renderer.d.ts`) adds capture, pixel reads, settling,
|
||||
resize, context loss and `destroy`. `common.d.ts` holds the shared shapes
|
||||
(ECEF positions, colours, listener semantics) and the capability list.
|
||||
|
||||
The rules that hold everywhere:
|
||||
|
||||
- **Core keeps its own records.** It never reads a renderer to learn its own
|
||||
state, only what the renderer measures: picks, depth, heights and
|
||||
readiness.
|
||||
- **Capabilities.** An optional member (`?`) exists only with the capability
|
||||
its documentation names first. A required member that names a capability
|
||||
always exists, and without the capability it answers null or empty.
|
||||
Quality support is listed by `quality.knobs()`, not by capabilities.
|
||||
- **Heights are physical.** All heights are metres above the ellipsoid,
|
||||
unexaggerated. Only drawing follows exaggerated terrain.
|
||||
- **Listeners** run in subscription order. A listener removed during
|
||||
delivery does not run; one added during delivery runs from the next event.
|
||||
A listener that throws is reported to the renderer's `error` event, or to
|
||||
the platform's `reportError` when nothing listens for errors. The other
|
||||
listeners, and the frame, still run.
|
||||
- **The frame order is fixed:** camera flight → input → camera `changed` →
|
||||
follow → `preUpdate` → move detection → `preRender` → render →
|
||||
`postRender`.
|
||||
|
||||
`src/render/contract/index.js` lists every member of every port in `PORTS`,
|
||||
along with `CAPABILITIES`, `FRAME_STEPS` and `QUALITY_KEYS`.
|
||||
`checkRendererShape(renderer)` names each required member that is missing,
|
||||
and each capability member present without its capability.
|
||||
`contract.test.mjs` type-checks the contract under strict TypeScript, and
|
||||
fails when `PORTS` and the types disagree on a single member.
|
||||
|
||||
## The stub renderer
|
||||
|
||||
`createStubRenderer({ capabilities, viewport, time })` implements every port
|
||||
in memory and draws nothing. It has:
|
||||
|
||||
- the default camera's exact maths: views, `lookAt`, flights with their
|
||||
height arc and easing, `changed` thresholds and follows;
|
||||
- picks against what was drawn, or a scripted table;
|
||||
- scripted heights;
|
||||
- a virtual clock that nothing advances on its own.
|
||||
|
||||
`renderer.testing` drives it with `tick()`, `advance(ms)`, `pointer(type, x, y)`,
|
||||
`scriptPicks`, `setHeights` and `cacheHeight`, and records its calls.
|
||||
A listener that throws with nothing subscribed to `error` goes to
|
||||
`reportError`; Node has none, so the stub rethrows it on a microtask and
|
||||
`node:test` fails the running test. A test that expects a throw subscribes
|
||||
to `error`.
|
||||
The stub passes the whole conformance suite (`src/render/stub/stub.test.mjs`).
|
||||
|
||||
## Conformance suite
|
||||
|
||||
`runConformance(harness)` runs every check against a renderer through the
|
||||
contract alone and reports each one as passed, failed or skipped. It runs in
|
||||
Node and in a browser page. A harness tells the suite how to drive the
|
||||
renderer under test:
|
||||
|
||||
- `create({ viewport, capabilities })`;
|
||||
- `frame(r)`: one frame, 1/60 s on a virtual clock;
|
||||
- `now(r)` and `toEcef(p)`;
|
||||
- `tolerance` and `flightPaths`.
|
||||
|
||||
Optional members widen what is checked: `pointer` for synthetic input,
|
||||
`scriptHeights`, and `assets.model`.
|
||||
|
||||
There are two layers of checks.
|
||||
|
||||
**Logic checks** run in CI. They cover:
|
||||
|
||||
- the shape check;
|
||||
- listener semantics and the frame loop;
|
||||
- draw-set bookkeeping, picks and the `meta` round-trip;
|
||||
- projection, rays and heights;
|
||||
- leases and click picks;
|
||||
- imagery order, sky, styles, credits and quality knobs;
|
||||
- the camera traces.
|
||||
|
||||
**Camera traces.** `cameraScripts.js` lists camera scripts in the contract's
|
||||
terms. `scripts/record-camera-goldens.mjs` runs them on Cesium's own
|
||||
`Camera`, headless, on a virtual clock, and writes
|
||||
`src/render/conformance/goldens/camera.json`. A renderer under test must
|
||||
match each recorded pose:
|
||||
|
||||
- within 1 mm in position, 1e-9 in unit vectors and angles, and 1e-3 px on
|
||||
screen;
|
||||
- with every flight ending within one frame of the recording.
|
||||
|
||||
`src/render/cesium/cameraGoldens.test.mjs` fails when the installed Cesium
|
||||
no longer records the committed traces. After a Cesium upgrade or a script
|
||||
change, run `node scripts/record-camera-goldens.mjs` and review the diff.
|
||||
|
||||
**Visual checks** run in the browser. The reference scenes
|
||||
(`src/render/conformance/scenes.js`) are built from contract calls only:
|
||||
|
||||
- one scene per primitive family;
|
||||
- a city scene;
|
||||
- a calibration path from orbit down to the street.
|
||||
|
||||
The scenes load nothing from the network.
|
||||
|
||||
`npm run dev`, then open `/dev/conformance/`. The page can:
|
||||
|
||||
- **Run logic suite (stub)** runs the logic checks in the browser.
|
||||
- **Record references** draws every scene the chosen renderer supports.
|
||||
It saves each view to `src/render/conformance/goldens/visual/`, through a
|
||||
dev-server-only endpoint.
|
||||
- **Check scenes** draws the scenes again and compares each capture with its
|
||||
reference. Pixels are compared as pixelmatch compares them: by YIQ
|
||||
difference (threshold 0.1), with antialiasing in either image excused. A
|
||||
capture matches when at most 250 pixels are unexcused and at most 2 are
|
||||
solid (a differing pixel whose four neighbours differ too). Between them
|
||||
the two catch a dropped 4 px point, a dropped 2 px line and a 1 px shift
|
||||
of the view, and pass any MSAA setting. A change nowhere 3 px thick (a
|
||||
thin line gone, an item moved a pixel or two) that covers at most 250 px
|
||||
passes. `visual.js` has the measurements.
|
||||
The page shows the capture, the reference and the difference side by side.
|
||||
|
||||
`window.conformance.runScenes()` and `runLogic()` do the same from a script.
|
||||
Browser checks use the chrome-devtools MCP against a page in its own window.
|
||||
|
||||
The Cesium scene renderer (`src/render/cesium/sceneRenderer.js`) draws the
|
||||
icon, point, line, shape, model and city scenes, and the references were
|
||||
recorded with it. It has only the members the scenes need. The quad, mesh,
|
||||
field and agent scenes have no reference yet. Each family's reference is
|
||||
recorded when the Cesium renderer gains that family.
|
||||
Reference in new issue
Block a user