# Compilation (Authoring)

The HIR pipeline turns user-authored TypeScript into checked, source-spanned buffer programs. Scenario code retains a separate same-realm compiler hardened against accidents, not attackers.

Inputs
  Code strings stored on the SDCPN document (lambda, kernel, dynamics, metric, scenario)
Outputs
  Versioned HIR artifacts for runtime code; `InitialMarking` for scenarios
Errors
  Thrown at compile time with the offending item’s ID (`SDCPNItemError`) → surfaced as protocol `error` messages / result objects
Runs on
  LSP worker (HIR compilation) · simulation workers (buffer programs) · main thread (scenarios and timeline metric evaluation)

## The compilation pipeline (module-style code)

Dynamics, lambdas, kernels, and metrics all compile through the HIR. The first three are authored as `export default Wrapper(fn)` modules; metrics are authored as function bodies.

User source `export default Lambda((input, parameters) => …)` (TypeScript allowed)
→
Lower to HIR Parse the supported TypeScript subset with exact source spans
→
Check + analyze Validate against places, token layouts, parameters, and output shapes
→
Emit artifact Generate buffer-native JS with offsets and strides baked in
→
Instantiate in runtime Validate v4 fingerprint, then bind parameters, pool, and ABI views
The LSP virtual files (`lsp/generate-virtual-files.ts`) are the type-level twin of this pipeline: they generate the `Color_*`, `Parameters`, `Dynamics`, `TransitionKernel`… declarations that Monaco checks against, so editor diagnostics and runtime artifacts are produced from the same HIR checks and emitters.

## What gets compiled, where it runs

Surface | Authored as | Compiled by | Executed in | Signature at runtime
--- | --- | --- | --- | ---
Transition lambda | module, `Lambda(fn)` | `compileHirArtifacts` in the LSP worker | Worker, per enablement check | `(f64, u64, u8, placeBases, indices) → boolean | rate`
Transition kernel | module, `TransitionKernel(fn)` | `compileHirArtifacts` in the LSP worker | Worker, per firing | `(views, placeBases, indices, outputViews, sink) → void`
Dynamics | module, `Dynamics(fn)` | `compileHirArtifacts` (colours with a real element) | Worker, per step per place | `(placeBytes, tokenCount) → Float64Array`
Metric | plain function body (no module) | `compileHirArtifacts` in the LSP worker | Main thread (timeline) · MC worker (experiments) | `(f64, u64, u8, placeCounts, placeOffsets) → finite number` — NaN/∞ throw
Scenario | per-place expressions or one code-mode body | `authoring/scenario/compile-scenario.ts` | Main thread, before `createSimulation` | → `InitialMarking` (+ scenario parameter values)
Place visualizer | module, `Visualization(fn)` returning JSX | Host UI package (`@hashintel/petrinaut`, `ui/lib/compile-visualizer.ts` — the one inherently React-based surface, since visualizers author JSX) | Main thread render | `({tokens, parameters}) → ReactElement`

## Scenario compilation → InitialMarking

#### per_place mode

Uncoloured place → expression string Evaluated with `parameters` and `scenario` in scope; result rounded and clamped >= 0 (a token count).
Coloured place → row arrays Rows in colour element order; each row coerced through `coerceTokenRecord` (typed defaults for missing columns, extra columns throw).

#### code mode

One function body Returns `{ PlaceName: count | TokenRecord[] }` keyed by place name (per_place uses place IDs — a known asymmetry). Unknown names are silently dropped.
Result: InitialMarking (JSON) Keyed by place ID. Fed to `createSimulation`, packed to binary in `buildSimulation`.

## Metric compilation and evaluation

compileHirArtifacts Lower, typecheck, and emit a buffer-native metric program.
→
createHirMetricEvaluator Bind referenced place ordinals and the per-run string pool.
→
Per-frame evaluation Quick sim: evaluated on the main thread against stored frames. Experiments: evaluated inside the MC worker per run per frame (`metrics/specs.ts`).

## Scenario sandboxing (`authoring/sandbox.ts`)

- Shadowed globals: scenario expressions declare `var window, document, globalThis, self, fetch, XMLHttpRequest, importScripts, Function, setTimeout, setInterval, queueMicrotask` — name lookups resolve to `undefined`.
- `runSandboxed(action)`: blocks the constructor-chain escape (`({}).constructor.constructor` → `Function`) for the duration of the call, since shadowing only stops identifier lookup.
- Same realm by design: user code executes with `new Function` in the worker/main realm. Treat this as robustness hardening (typos, accidental API use), not isolation.

## Distributions (deferred sampling)

HIR-emitted kernels construct small runtime values for `Distribution.Gaussian / Uniform / Lognormal`. Kernels defer sampling through the ABI sink; the engine samples each value once per token with the seeded RNG (`sample-distribution.ts`), so chained `.map(fn)` transforms share a single draw and runs stay reproducible. When stochasticity is disabled, the runtime isn’t injected and plain values are required.

Compatibility seam. HIR artifacts contain a version and fingerprint of their sanitized compilation input. Any schema, code, or extension change requires recompilation before the engine will run them.