Metis · Architecture

Hephaestus — The Explorable Runtime

A focused page within the Metis Architecture documentation. The full map and every sibling page live in the Architecture hub.

11sections15 minread1diagram2tables

On this page

Hephaestus is the second of V9's three net-new structures (after the Atlas concept graph): the runtime that turns a concept node into a manipulable surface whose state is computed, not animated, and then proves the surface is actually drivable to a defined target before it is allowed to ship. The whole package — @oshun/v9-hephaestus — is deliberately tiny: three source files plus an index (libs/v9/hephaestus/src/{nyx-sky-explorable,kalika-orbit-explorable,explorable-runtime}.ts), tagged scope:v9, layer:domain, type:lib, depending on exactly three workspace packages (@nyx/ephemeris, @oshun/contracts, @oshun/v9-lesson-explorables). That smallness is the point. Hephaestus introduces no new physics and no new ephemeris; it is a typed binding layer that composes the real, already-shipped Oshun kernels and attaches a demonstrably-reached success state to each binding. Its module doc-comment states the contract plainly: a P1 lesson must "ship ≥1 computed-kernel explorable (a Nyx sky / Kalika sim), not a static asset," and that explorable must have "a DEMONSTRABLY-REACHED success state (successState.reachable === true), proven by the kernel output — a generated-but-unverified explorable never ships" (libs/v9/hephaestus/src/explorable-runtime.ts:1).

Why build it this way? Because an interactive widget is the easiest place in a lesson to lie. A static diagram can be wrong; an interactive diagram can be wrong in motion — a slider that animates a plausible-looking but fictional sky, a "physics" knob that tweens between hand-drawn frames. V9's answer is a hard rule: the only explorables that count toward the lesson's Definition of Done are ones where moving a parameter re-runs a real kernel, and where the reachable flag on the success state was set by reading that kernel's output rather than asserted by the generator. This makes the interactive claim of a lesson as trustworthy as its grounded prose — both trace to a verifiable computation. Hephaestus is consumed by the Prometheus forge at Stage 5 and feeds the G4 completeness gate (libs/v9/prometheus/src/gates.ts:12), so an explorable that cannot prove its success state does not merely render a warning — it blocks the lesson. This page is the deep companion to the architecture set; the monolith hub is ../V9_ARCHITECTURE.md, and the orientation page is ./overview.md.

What ships, honestly#

Implemented and tested (real today). The two computed-kernel explorable builders are actual computation, not descriptions of computation. The Nyx sky explorable integrates the genuine @nyx/ephemeris Sun position across a date slider (libs/v9/hephaestus/src/nyx-sky-explorable.ts:13); the Kalika orbit explorable runs the genuine @kalika/symplectic velocity-Verlet integrator over a two-body Kepler system (libs/v9/lesson-explorables/src/orbit-explorable.ts:117). The DoD/G4 runtime (evaluateExplorableDoD, explorableDoDToGateVerdict, assertComputedExplorableDoD) is real and wired into the Prometheus gate composer. The V9Explorable contract is a real Zod schema with enforced invariants. The hephaestus test suite (hephaestus.spec.ts) is 7/7 green and asserts domain facts — the Sun's right ascension sweeps ≈ 360° over a year, the symplectic integrator's relative energy drift stays below 1e-3 — not just that objects are non-null.

Real logic behind an injected boundary. The third explorable family — generative widgets for ideas without a kernel — lives in Theia, not Hephaestus (libs/v9/theia/src/generative-widgets.ts). verifyGenerativeWidget is real and correctly fail-loud: it refuses to mint an explorable unless a HeadlessWidgetRunner reports both compiled and reachedSuccess, and it throws WidgetRunnerNotConfiguredError when no runner is wired. But the headless runner itself — the real browser/runtime check — is an injected boundary; this repo does not bundle a browser harness, so the verifier ships its policy, not a live sandbox.

Spec-described / planned — corrected against the monolith. Three monolith claims are aspirational relative to what the code wires, and this page says so:

  • The game-bridge kind is types-only. V9ExplorableKindSchema enumerates game-bridge (libs/contracts/src/v9/explorable.ts:21), but there is no game-bridge builder anywhere in libs/v9/ — the only builders that exist are buildNyxSkyExplorable, buildKalikaOrbitExplorable, and the underlying buildOrbitExplorable. The V2-racing-as-applied-physics path is deferred, as the monolith itself admits.
  • The Kalika orbit computes in TypeScript, not WASM. The §4 prose says the Kalika kernels are "compiled to WASM"; a real wasm-bindgen crate (kalika-cas-wasm) does exist under libs/kalika/cas-engine. But the shipped orbit explorable does not call it — it computes in pure TypeScript via @kalika/symplectic's integrateCanonical. The result is correct-by- construction either way; the deployment story (WASM in a browser) is the part that is planned.
  • Hephaestus binds the ephemeris compute, not the planetarium renderer. The "WebGL planetarium, 10M+ stars at 60fps" is the real, separate libs/nyx/renderer lib (a WebGPU renderer with WebGL2 fallback). Hephaestus imports only @nyx/ephemeris — the numeric Sun position — and never the renderer. A V9Explorable is a binding + proof + provenance, not a rendered canvas; the on-screen widget is a downstream delivery concern.

The Explorable contract#

The single source of truth is V9ExplorableSchema (libs/contracts/src/v9/explorable.ts:104), the typed expression of the monolith's { conceptId, kind, params[], successState, groundingOrKernelRef, provenance }. Each field carries enforcement, not just shape:

Field Type What it enforces
conceptId V9ConceptId The Atlas node this surface explores (cn:<64-hex> or <namespace>:<slug>).
kind computed-kernel | generative-widget | game-bridge The trust tier (below).
title 1–300 chars Human label.
params V9ExplorableParam[] (max 32, default []) The manipulable knobs; each {min ≤ max, default ∈ [min,max]}.
successState V9ExplorableSuccessState The reachable target (G4) — the heart of the contract.
groundingOrKernelRef kernel-ref | Sophia-pin union Where the surface's truth comes from.
provenance { contentHash, generator, c2paSigned } sha256 of the computed output + what produced it.

Three invariants make the schema refuse to encode a dishonest explorable. First, parameter bounds keep the slider inside the verified regime: the V9ExplorableParamSchema superRefine (explorable.ts:43) rejects min > max and a default outside [min, max], so a knob can never be initialized into an un-computed corner. Second, a computed-kernel explorable must be backed by a kernel, not a citation: the schema-level superRefine (explorable.ts:115) raises an issue if kind === 'computed-kernel' while groundingOrKernelRef.kind !== 'kernel' — you cannot label a surface "computed" and then ground it in a text pin. Third, and most important, the success state cannot be asserted blind.

The V9ExplorableSuccessStateSchema (explorable.ts:68) is { description, predicate, reachable, evidence? }. The doc-comment is explicit: "reachable is set true only after the success state was actually demonstrated (by the kernel result or the headless verifier)." predicate is a machine-checkable id ("sunRaSweepDeg ≈ 360·days/yr", "energy.relativeDrift < maxEnergyDrift"); evidence is the measured value that made it pass ("swept 360.9° (expected 360.0°)"). The one helper the contract exports, isV9ExplorableShippable (explorable.ts:128), is a one-liner — return ex.successState.reachable; — and every gate decision routes through it. That tiny function is the seam where "we computed it and it worked" is separated from "we hope it works."

The groundingOrKernelRef union (explorable.ts:82) discriminates on kind: 'kernel' vs kind: 'grounding'. A kernel ref carries a V9KernelRef (libs/contracts/src/v9/primitives.ts:152): a kernel drawn from the enum {nyx, kalika, nisaba, demeter} plus a callable ref string like nyx:ephemeris#sun.position and an optional unit. (Only nyx and kalika have builders today; nisaba and demeter are reserved enum members.) The Lesson artifact embeds this contract directly — explorables: z.array(V9ExplorableSchema).min(1).max(8) (libs/contracts/src/v9/lesson.ts:209) — so the "≥1 explorable" floor is a schema constraint, while the stronger "≥1 reached computed-kernel explorable" rule is the Hephaestus DoD enforced at the gate.

Three kinds — a trust gradient#

The kind enum is ordered by increasing generative risk, and the runtime treats the tiers very differently:

Kind Trust Built by How the success state is proven Status
computed-kernel P1 (highest) buildNyxSkyExplorable, buildKalikaOrbitExplorable A real kernel runs; reachable is read from the computed samples. Shipped, 7/7 tests green
generative-widget P1/P2 Theia verifyGenerativeWidget A HeadlessWidgetRunner must report compiled && reachedSuccess; else blocked or throws. Real verifier; runner injected
game-bridge P2/P3 (no builder) Would reuse a V2–V8 substrate as a lab; not implemented. Types-only / deferred

Only the first tier satisfies the DoD. The runtime counts computed-kernel explorables specifically (explorable-runtime.ts:25), so a lesson made entirely of generated widgets still fails G4 until a kernel-backed surface is added. This is the codified opinion that the cheapest-to-fake surfaces earn the least trust.

Computed-kernel explorables#

Nyx sky — "the wandering Sun"#

buildNyxSkyExplorable (nyx-sky-explorable.ts:61) takes a cosmology concept and a time-travel window (startUnixMs, days default 365, samples default 366) and walks a date slider, calling the real @nyx/ephemeris kernel at each step: dateToJd(...) then calculateSunPosition(jd) to get the Sun's true apparent right ascension and declination. That ephemeris is not a toy — calculateSunPosition computes Earth's heliocentric ecliptic position, takes the antipode, and rotates ecliptic→equatorial with a time-dependent obliquity (libs/nyx/ephemeris/src/generator.ts:842).

The success state is a measured astronomical fact: over the slider, the Sun's RA should sweep forward by ≈ 360·days / 365.24219 (one tropical year per full circle). The builder sums the forward RA deltas across all samples and checks the result against the span-scaled expectation with a 10%-of-a-circle tolerance:

text
expectedSweep = 360 · days / TROPICAL_YEAR_DAYS
reachable = every sample finite  AND  |sweep − expectedSweep| / 360 < 0.1

Because the expectation scales with the span, a 30-day window honestly reaches its own scaled success state (~30°), not a hard-coded 360° — the test 'does NOT reach … for a half-year span flagged as a full sweep' exercises exactly this, asserting a 30-day window sweeps 25–35° and is still reachable (hephaestus.spec.ts:40). The explorable binds a single dayOffset slider over [0, days], grounds itself with kernelRef { kernel: 'nyx', ref: 'nyx:ephemeris#sun.position', unit: 'deg' }, and stamps provenance with a sha256 over the rounded RA/Dec series and generator 'nyx:ephemeris'. The returned NyxSkyExplorable also hands back the raw samples and the measured sunRaSweepDeg — the time-series the renderer would draw lives in that return value, not inside the V9Explorable (the contract stores the proof, not the trajectory).

Kalika orbit — "a planet you can perturb"#

buildKalikaOrbitExplorable (kalika-orbit-explorable.ts:37) wraps @oshun/v9-lesson-explorables' buildOrbitExplorable, which is where the real physics lives. That function constructs the 2-D Kepler gravity system as a separable Hamiltonian H = p²/2 − GM/|q| (orbit-explorable.ts:71), starts at perihelion (r₀, 0) with a purely tangential vis-viva speed v₀ = √(GM(1+e)/r₀), derives the bound-orbit period from the semi-major axis a = r₀/(1−e) (Kepler's third law), and hands the system to integrateCanonical(..., { method: 'velocity-verlet' }) — the genuine @kalika/symplectic geometric integrator (orbit-explorable.ts:117). It then reads back the trajectory and the integrator's backward-error energy diagnostic via analyzeEnergyBehavior.

The success state is the measured fact that a symplectic integrator conserves energy and the orbit closes:

text
reachable = orbit.computed
          AND Number.isFinite(orbit.energyRelativeDrift)
          AND |orbit.energyRelativeDrift| < maxEnergyDrift   // default 1e-3

orbit.computed is itself honest — it is true only when the integrator produced a finite, multi-sample trajectory (orbit-explorable.ts:155). The explorable binds two sliders whose bounds keep the surface inside the verified regime: eccentricity over [0, 0.89] step 0.01 (the underlying integrator rejects e ≥ 0.9, so the slider's max stays safely below the singularity) and gravitationalParameter (GM) over [0.1, 10]. It grounds with kernelRef { kernel: 'kalika', ref: 'kalika:symplectic#kepler.integrate' }, reuses the orbit's sha256 contentHash, and sets generator 'kalika:symplectic'. The test 'conserves energy on a circular orbit' asserts |energyRelativeDrift| < 1e-3 and reachable === true (hephaestus.spec.ts:57) — a test that would fail against a hand-tuned or random energy value, which is what makes it a real test rather than a shape check.

The DoD and the G4 gate#

explorable-runtime.ts is the policy layer over those builders. evaluateExplorableDoD (explorable-runtime.ts:24) filters a lesson's explorables to the computed-kernel ones, then filters those through isV9ExplorableShippable, and returns a structured report:

text
ExplorableDoDReport {
  ok: reachableComputedKernel > 0,
  total, computedKernel, reachableComputedKernel,
  reasons: [ "no computed-kernel explorable …", 'explorable "X" never reached its success state: …' ]
}

The reasons array is the honest failure ledger — it names each unreached explorable and quotes its success-state description, so a blocked lesson explains itself. explorableDoDToGateVerdict maps the report to a V9GateVerdict ({pass, evidence}); assertComputedExplorableDoD throws computed-explorable DoD not met: … for callers that want a hard stop at build time.

At Stage 7, composeGates (libs/v9/prometheus/src/gates.ts:132) makes the DoD be G4. It calls evaluateExplorableDoD(input.explorables) and emits the G4 verdict whose evidence reads, e.g., "1/1 computed explorable(s) reached success state". The other six gates are sourced elsewhere — G1/G2/G6 from Aletheia, G3 pedagogy and G5 quality and G7 provenance computed alongside — but G4 is Hephaestus. The lesson is publishable only when all seven pass (isV9LessonPublishable, libs/contracts/src/v9/lesson.ts:246), so a single unreached explorable blocks delivery and caching.

The three gate-level tests close the loop: the DoD passes when a real Kalika explorable reaches its state; it blocks an empty explorable list (assertComputedExplorableDoD([]) throws /DoD not met/); and it blocks an explorable whose reachable has been forced to false, with reasons[0] matching /never reached its success state/ (hephaestus.spec.ts:78).

Where it sits in the Prometheus pipeline#

Hephaestus is Stage 5 of the nine-stage forge. The pipeline does not hard-code a builder; it accepts an injected explorableBuilder: (conceptId) => V9Explorable (libs/v9/prometheus/src/pipeline.ts:76) and invokes it on the concept the plan chose to anchor (pipeline.ts:147). Today the forge ships exactly one explorable per lesson (explorables = [explorable]), well under the contract's max of 8. The end-to-end test wires the real Kalika builder as that seam (buildKalikaOrbitExplorable({ conceptId, orbit: { eccentricity: 0, steps: 360 }}), pipeline.spec.ts:95), so the green pipeline run is genuinely integrating real symplectic physics into a gated artifact.

flowchart TD A["Stage 0–4: resolve · ground · plan · write · media"] --> B B["Stage 5 · Hephaestus<br/>explorableBuilder(conceptId)"] --> C C{kind?} C -->|computed-kernel| D["buildNyx / buildKalika<br/>run real kernel → samples"] C -->|generative-widget| E["Theia verifyGenerativeWidget<br/>HeadlessWidgetRunner"] D --> F["read kernel output →<br/>successState.reachable"] E --> F F --> G["evaluateExplorableDoD()<br/>count reached computed-kernel"] G --> H{ok?} H -->|yes| I["G4 verdict pass →<br/>composeGates → compile artifact"] H -->|no| J["G4 verdict fail →<br/>lesson BLOCKED, not cached"]

A worked trace for "how does a two-body orbit conserve energy?": Prometheus resolves the wonder to a Kalika concept, the explorable builder runs buildKalikaOrbitExplorable, which integrates 360 velocity-Verlet steps of a circular orbit; the integrator's backward-error diagnostic reports a relative energy drift around 1e-91e-12; that is below the 1e-3 tolerance, so reachable is set true with evidence relative energy drift 3.x e-10 (< 0.001); N samples; evaluateExplorableDoD counts 1/1 reached; G4 passes; the explorable — eccentricity and GM sliders, kernel ref, content hash — is sealed into the V9LessonArtifact.

Generative-widget verification (Theia)#

For ideas with no kernel, the P2 path (libs/v9/theia/src/generative-widgets.ts:53) verifies a generated widget before it can clear G4/G5. verifyGenerativeWidget takes a GenerativeWidgetSpec (the generated source, params, success description and predicate) and a HeadlessWidgetRunner. It is structured to be impossible to spoof: with no runner it throws WidgetRunnerNotConfiguredError (generative-widgets.ts:38); with a runner, it returns { shipped: false, reason } unless the runner reports both compiled and reachedSuccess. Only then does it mint a generative-widget V9Explorable with successState.reachable: true, a Sophia-style pin id derived from the widget's content hash, and generator 'iris:widget-gen'. The Theia spec exercises all three outcomes — shipped on success, blocked when reachedSuccess is false, and thrown when no runner is wired (theia.spec.ts:92). The boundary is honest: the verifier is real; the browser that actually runs the widget is the injected seam.

Edge cases and failure modes#

  • Validation is a throw, not a warning. Both builders call V9ExplorableSchema.parse(...), so a malformed param (default out of bounds) or a computed-kernel explorable mis-grounded with a pin raises a Zod error at build time. buildNyxSkyExplorable additionally guards days > 0 and samples ≥ 2 integer; buildOrbitExplorable guards GM > 0, r₀ > 0, e ∈ [0, 0.9), steps ≥ 2 integer.
  • Unreached ≠ thrown. A legitimately computed but failing surface (energy drift over tolerance, a NaN in the series) yields reachable: false, not an exception. That is correct: the explorable is real but not shippable, and the DoD reports it by name rather than crashing.
  • Empty / widget-only lessons block. No computed-kernel explorable ⇒ report.ok === false with the reason "a P1 lesson needs ≥1 Nyx/Kalika computed explorable." The contract's min(1) only requires an explorable; the DoD requires a reached computed-kernel one.
  • The contract stores proof, not data. The V9Explorable carries the success evidence and a content hash, but the actual sample series lives in the builder's return type (NyxSkyExplorable.samples, KalikaOrbitExplorable.orbit.samples). A consumer that needs the trajectory must call the builder, not read the artifact — the artifact is a verifiable binding, kept small and cacheable.
  • c2paSigned defaults false. Every builder emits c2paSigned: false; cryptographic signing is G7's job downstream, so an explorable straight out of Hephaestus is provenance-hashed but not yet signed.

Configuration surface#

There is little to configure, by design. The meaningful knobs are the builder parameters: Nyx's startUnixMs / days / samples window, and Kalika's orbit shape (gravitationalParameter, perihelion, eccentricity, steps) plus a tunable maxEnergyDrift (default 1e-3) that sets how strict the energy-conservation success state is. Tightening maxEnergyDrift or raising steps makes the success state harder to reach but the orbit truer; both are honest trade-offs the caller owns. The pipeline-level seam is ForgeLessonInput.explorableBuilder, which lets a deployment swap in a different kernel binding without touching the gate logic.

How it connects to neighbouring systems#

Hephaestus sits in the middle of the V9 reuse stack: it consumes substrate kernels and feeds the gate plane. Upstream, the Atlas graph supplies the conceptId and (for STEM disciplines) the kernelRef that says a value must be computed rather than stored — see ./atlas-knowledge-graph.md and the Invariant-2 STEM rule in ./six-layer-reuse-stack.md. Sideways, it reuses the @nyx/ephemeris and @kalika/symplectic kernels exactly as catalogued in ./subsystem-map-and-reuse-ledger.md. Downstream, the explorable is one field of the ./lesson-artifact.md and its DoD is G4 inside ./seven-gates-and-aletheia.md; the Prometheus forge that orchestrates Stage 5 is detailed in ./prometheus-lesson-pipeline.md. Finally, the rendered, learner-facing surface and the cross-cutting provenance/caching concerns are covered in ./delivery-and-cross-cutting.md — a reminder that Hephaestus produces the verified binding, while drawing it on a real WebGL canvas is a delivery-layer job.