# @skenora/scene-plan

Pure SceneBlueprint and ScenePatch contracts, validation, normalization, and
compilation for Skenora.

> This package snapshot is version `0.3.0`. Consume SDK, tooling and offline
> documentation from the same release. See
> [implementation status](../../docs/IMPLEMENTATION_STATUS.md) for dated
> verification and remaining limits.

This package is intentionally separate from @skenora/editor and @skenora/sdk:

- it contains serializable Blueprint/Patch data, parsing, normalization,
  validation, diagnostics, and compilation;
- it does not create an Editor, Runtime, Babylon scene, DOM element, or AI
  runtime;
- it does not own resource acquisition, workspace writes, approval, or policy;
- it compiles to the existing SceneDocumentData source of truth.

## Scene fragment extraction (CMP-01a)

The current source exports `extractSceneFragment(document, options, context?)`
and `validateSceneFragment(fragmentOrJson, context?)`. They return an `ok`
discriminant and path-addressed diagnostics. A successful result contains an
owned `SceneFragmentV1`; extraction additionally reports excluded entity/Flow
IDs and global domains. They do not insert into an Editor or load resources.

```ts
const result = extractSceneFragment(sourceDocument, {
  name: "Table set",
  rootEntityIds: ["table-set"],
  flowIds: ["table-click"], // Optional; omitted means no behavior is copied.
});
if (result.ok) {
  const portableJson = JSON.stringify(result.fragment);
}
```

Minimal selected roots must share a source parent. Extraction retains local
transforms and all descendants, collects bound/Flow-referenced materials,
texture tracks, model/material/particle assets, explicitly referenced camera
paths and initial variables. Logical resource declarations omit locators and
provenance. Models retain their asset-local material selectors; generated
shape selectors use the same identity helper as Runtime projection.

Flow reference roles live in the active `FlowNodeRegistry`. Custom nodes need
data-only `fragment` metadata. External entity references, dynamic target
inputs (including connected constants), opaque fields and absent variable
defaults fail explicitly. Shared viewport actions produce a warning; their
insertion requires an explicit world-space policy.

The V1 defaults limit fragments to 2 MiB, depth 64, 1,000 entities and bounded
material/asset/Flow collections. Source documents have a separate 32 MiB input
ceiling before normalization. Hosts can only tighten
`context.fragmentBudget`; `context.capabilityAvailability` checks actual
fragment requirements when supplied. Validation is detached from target
resource availability and does not prove rendering correctness.

Generated gallery adoption and package/browser evidence remain separate
deliveries. The native container validator is authoritative; no parallel record
schema is introduced. See the
[accepted CMP-01 design](https://github.com/zhangjiadi225/skenora/blob/main/docs/SCENE_FRAGMENTS_DESIGN.md).

## Scene fragment insertion planning (CMP-01b)

`planSceneFragmentInsertion(fragment, targetContext, options)` returns a guarded
ordinary `ScenePatch`, typed `idMap`, wrapper group ID, required existing asset
IDs, shared-material impacts and diagnostics. It returns no private candidate
document. The host supplies a document snapshot, `documentRevision` and
`documentHash` captured together. Planning carries the hash unchanged; it does
not independently verify the hash or acquire an Editor commit lock.

```ts
const planned = planSceneFragmentInsertion(fragment, capturedTargetContext, {
  namespace: "tableSetA",
  resourceBindings: { chairModel: "existingChairAsset" },
  // Optional parentId: an existing authored group.
  // Optional placement: a complete native local transform on the new wrapper.
});
if (!planned.ok) report(planned.diagnostics);
```

Resource and optional `reuseMaterials` mappings use stable IDs. Every declared
resource needs an explicit mapping, even when source and target IDs match.
Materials are copied by default; reuse requires equal normalized content after
texture remapping and reports existing bound entities. Independent texture
tracks cannot be duplicated onto a reused material. Missing declared integrity
produces an identity warning, not an assertion of matching asset bytes.

Generated IDs use the explicit namespace and record domain; collisions and
overlong IDs are rejected. Static event declarations in the target registry are
included in collision checks. Flow-local IDs, material-local animation IDs and
ordinary JSON payload values retain their local meaning. Shared camera effects
require `viewportEffects: "preserve-world"`; camera paths never move with the
wrapper. The output budget counts each variable write as an operation.

Existing ScenePatch field paths reject dot characters. A fragment with variables
therefore requires a dot-free namespace and variable names. Planning rejects
this combination explicitly instead of renaming it or changing Patch semantics.

This pure planner does not establish resource readiness, execute an insertion,
start preview, or guarantee a successful projection. The SDK's separate
`editor.insertFragment()` convenience method uses the existing executor with
Flow/resource preflight and preview guards. See [Editor usage](../../docs/EDITOR.md#insert-reusable-scene-fragments).

`getSceneFragmentInformation()` (also `getScenePlanInformation().fragments`)
returns bounded protocol semantics and repair guidance. It is not a Runtime
capability descriptor. The information bundle's `flowNodes` also includes each
node's data-only reference metadata.

`getSceneFragmentExamples()` ships five detached native fragments: a zero-asset
table set, explicit model reuse, a generated shape fill binding, shared material
reuse and an intentionally rejected dynamic target. Each includes insertion
options and host requirements. The [public SDK example](../../examples/scene-fragments/README.md)
checks two sequential commits and projections, leaving preview to the host.
These examples and execution changes have not yet been tested, packaged or
verified in a browser.

The package boundary and execution split are documented in:

- [Scene Plan](https://github.com/zhangjiadi225/skenora/blob/main/docs/SCENE_PLAN.md);
- [Scene Plan execution](https://github.com/zhangjiadi225/skenora/blob/main/docs/SCENE_PLAN_EXECUTION.md);
- [Scene Plan modules](https://github.com/zhangjiadi225/skenora/blob/main/docs/SCENE_PLAN_MODULES.md);
- [AI scene skill contract](https://github.com/zhangjiadi225/skenora/blob/main/docs/AI_SCENE_SKILL.md).

The SDK scene-plan executor may expose Gateway operations as a composition
entry point, but the pure package must remain usable without a Canvas, DOM,
Editor, Runtime, Workspace, or resource locator.

## Public information and examples

```ts
import {
  getScenePlanInformation,
  getSceneBlueprintExamples,
  getSceneBlueprintJsonSchema,
  applySceneRecipe,
  compileSceneBlueprint,
  toPublicScenePlanDiagnostics,
} from "@skenora/scene-plan";

// Data only: no Canvas, engine, model provider, resource acquisition or Editor.
const information = getScenePlanInformation();
const schema = getSceneBlueprintJsonSchema();
const example = getSceneBlueprintExamples()[0].blueprint;
const compiled = compileSceneBlueprint(example, {
  // Obtained from an initialized Runtime/Renderer or the host deployment.
  capabilityAvailability: observedHostCapabilities,
});
if (!compiled.ok) {
  console.log(toPublicScenePlanDiagnostics(compiled.diagnostics));
}
```

`checkSceneInput(input)` is the single pure preflight for native SceneDocument,
Blueprint, and Patch input. It parses JSON once, returns stable shared
diagnostics, capability requirements and counts, and marks whether a Blueprint
was fully compiled. Resource-bearing Blueprints are not compiled until the host
supplies resource bindings.

The information bundle includes versioned capability descriptions, input and
normalized-Blueprint schemas, native defaults, examples, safe repair guidance
and the `neutral-v1` / `studio-v1` recipe descriptions. The getters return
detached serializable source data. A new development package build will emit
them into `dist`; the checked-in pre-procedural `dist` is stale and is not
evidence for these additions. A host can select descriptions by `domains` or
`capabilityIds` and serialize only the relevant information for its own AI.

Do not turn `information.capabilities` into an availability snapshot. It
describes accepted syntax; it does not prove that a backend is installed,
enabled, or compatible with the active device.

The information bundle includes dedicated
`skenora.entity.procedural@1` and `skenora.material.program@1` descriptions,
their JSON Schemas, limits, diagnostics, and the complete `procedural-ai`
Blueprint example. A procedural Blueprint needs no resource binding when it
uses no assets, but declared required capabilities still need a matching host
capability snapshot.

`listSkenoraExamples()`, `searchSkenoraExamples()`, and
`getSkenoraExample()` expose the immutable generated public gallery. Blueprint
and Recipe entries contain ordinary Blueprint data; Renderer Lab entries carry
a bounded `SimulationProgram` and are intentionally excluded from persistent
scene compilation. The information bundle describes the simulation schema but
does not claim device availability.

`SceneBlueprintInput` expresses omitted arrays/settings; normalization fills
Blueprint defaults, then compilation fills native settings and owned materials.
Schema validation does not resolve references or prove rendering compatibility.
When known, pass the current host's `capabilityAvailability` snapshot in the
compiler context; unknown/disabled capabilities cannot satisfy required usage.

`applySceneRecipe(input, "studio-v1", options)` expands ordinary fields without
fetching assets. Explicit fields win, including a supplied empty light array.
Use `options.bounds` with world-space meters for pure camera framing; otherwise
let the renderer frame loaded geometry. Environment resources must be declared
and bound explicitly by the host.

New PBR input opts in with `creation: { version: 1, family: "pbr" }` and uses
explicit `materialBindings`. Legacy material overrides remain supported. Group
entities without `sourceNode` provide transform-only composition.

Owned PBR also accepts versioned `effects` and local `animations`. The metadata
contains their exact property allowlists, interpolation/loop rules and render-pass
restrictions. Public examples include `gradient-rim`, `animated-dissolve`,
`flow-material-animation`, `texture-motion`, `presentation-atoms`, and a grouped
tube/wall/polygon/polyline composition, plus `procedural-ai`. The host chooses
which information to give its AI; no provider, prompt runner or model
connection is included.

Contract tests, actual development-package imports, public declarations and
12 public WebGL2 rendering examples passed verification on 2026-08-28. This does
not cover the later procedural-program additions, claim other GPU/browser
support, or mean that an updated package was published.
