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-bridgekind is types-only.V9ExplorableKindSchemaenumeratesgame-bridge(libs/contracts/src/v9/explorable.ts:21), but there is nogame-bridgebuilder anywhere inlibs/v9/— the only builders that exist arebuildNyxSkyExplorable,buildKalikaOrbitExplorable, and the underlyingbuildOrbitExplorable. 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-bindgencrate (kalika-cas-wasm) does exist underlibs/kalika/cas-engine. But the shipped orbit explorable does not call it — it computes in pure TypeScript via@kalika/symplectic'sintegrateCanonical. 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/rendererlib (a WebGPU renderer with WebGL2 fallback). Hephaestus imports only@nyx/ephemeris— the numeric Sun position — and never the renderer. AV9Explorableis 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:
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:
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:
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.
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-9–1e-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.buildNyxSkyExplorableadditionally guardsdays > 0andsamples ≥ 2integer;buildOrbitExplorableguardsGM > 0,r₀ > 0,e ∈ [0, 0.9),steps ≥ 2integer. - 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 === falsewith the reason "a P1 lesson needs ≥1 Nyx/Kalika computed explorable." The contract'smin(1)only requires an explorable; the DoD requires a reached computed-kernel one. - The contract stores proof, not data. The
V9Explorablecarries 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. c2paSigneddefaultsfalse. Every builder emitsc2paSigned: 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.
Related#
- V9 Architecture Overview and the monolith hub ../V9_ARCHITECTURE.md
- Atlas — The Unified Knowledge Graph and the Six-Layer Reuse Stack
- Subsystem Map and Reuse Ledger — the Nyx and Kalika kernels Hephaestus binds
- Prometheus — The Lesson Pipeline (Stage 5) and The Lesson Artifact (the embedded explorable)
- The Seven Gates and Aletheia — the DoD is G4
- Delivery and Cross-Cutting Concerns