Files
feuerwerk/src/core/commands/require.ts
T
rootandClaude Opus 5 ead649eec3 Resolve container content from the template instead of copying it
A scene now stores only the container object; its content is resolved from
the template on every read, with deviations kept as sparse instanceEdits on
the container object. This drops the propagation bookkeeping entirely --
templateSync.ts is gone and the three structural gaps (add, remove, reparent)
cannot diverge any more.

Values stay editable on an instance, structure does not; Unpack writes the
content into the scene, keeping ids. Play mode bakes containers into the
throwaway copy before entities are built. No migration: formatVersion is 2
and the seeders write the new format.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 21:09:53 +02:00

86 lines
3.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/** Lookups that throw a `CommandError` when the target does not exist. */
import { findNode, type GraphNode } from '../graph/Graph';
import { type NodeDef, nodeDef } from '../graph/NodeRegistry';
import type { Asset, BehaviorAsset, SceneAsset } from '../model/Asset';
import { findObject, type ObjectTree } from '../model/hierarchy';
import { isTemplateContent, objectRef, type ObjectRef } from '../model/instance';
import { findAsset, findObjectHolder, findScene, type ObjectHolder, type Project } from '../model/Project';
import type { SceneObject } from '../model/SceneObject';
import { propDef, type PropertyDef } from '../properties/PropertySchema';
import { CommandError } from './Command';
export function requireAsset(project: Project, assetId: string): Asset {
const asset = findAsset(project, assetId);
if (!asset) throw new CommandError(`Asset ${assetId} not found`);
return asset;
}
export function requireScene(project: Project, sceneId: string): SceneAsset {
const scene = findScene(project, sceneId);
if (!scene) throw new CommandError(`Scene ${sceneId} not found`);
return scene;
}
export function requireHolder(project: Project, holderId: string): ObjectHolder {
const holder = findObjectHolder(project, holderId);
if (!holder) throw new CommandError(`Scene or container ${holderId} not found`);
return holder;
}
export function requireObject(project: Project, holderId: string, objectId: string): SceneObject {
const object = findObject(requireHolder(project, holderId), objectId);
if (!object) throw new CommandError(`Scene object ${objectId} not found`);
return object;
}
/**
* The object a value edit works on, whether it is stored in the holder or resolved from a
* container template – the writes then land in the instance's edits.
*/
export function requireObjectRef(project: Project, holderId: string, objectId: string): ObjectRef {
const ref = objectRef(project, requireHolder(project, holderId), objectId);
if (!ref) throw new CommandError(`Scene object ${objectId} not found`);
return ref;
}
/**
* Rejects an object that only exists because a container template says so. Its structure – what
* exists, what it is parented to – is the template's business, not the instance's.
*/
export function requireStructural(project: Project, holder: ObjectTree, objectId: string): void {
if (!isTemplateContent(holder, objectId)) return;
// Not resolvable at all is a plain "not found", which the caller's own lookup words better.
const object = objectRef(project, holder, objectId)?.object;
if (!object) return;
const name = object.name || 'That object';
throw new CommandError(`${name} comes from a container template; open the container to change it`);
}
export function requireBehavior(project: Project, assetId: string): BehaviorAsset {
const asset = findAsset(project, assetId);
if (!asset || asset.type !== 'behavior') throw new CommandError(`Behavior ${assetId} not found`);
return asset;
}
export function requireNode(project: Project, assetId: string, nodeId: string): GraphNode {
const node = findNode(requireBehavior(project, assetId).graph, nodeId);
if (!node) throw new CommandError(`Node ${nodeId} not found`);
return node;
}
/**
* `NodeRegistry.requireNodeDef` throws a plain `Error`, which the editor does not catch; a command
* needs a `CommandError` so an unknown type becomes a notice instead of a crash.
*/
export function requireNodeDef(type: string): NodeDef {
const def = nodeDef(type);
if (!def) throw new CommandError(`Unknown node type "${type}"`);
return def;
}
export function requirePropDef(asset: Asset, key: string): PropertyDef {
const def = propDef(asset, key);
if (!def) throw new CommandError(`Property "${key}" is not defined for ${asset.type}`);
return def;
}