Let a value be a bundle of named fields

PropertyValue was number | string | boolean | list. A struct joins it, which
costs two things the typechecker found rather than I did.

An object on a data edge used to mean a live entity, so asEntity would have
taken a struct for one and asString would have read .name off it. EntityHandle
now carries a symbol brand – a symbol so no field name can impersonate it, and
it never serializes, which suits a thing that exists only while the game runs.
It is built in exactly one place.

String(value) would have written a struct as [object Object] at 39 call sites.
They go through textOf(), which is identical for primitives.

normalizeGraph dropped everything but primitives from node params, so the List
node lost its value on every save ([3,1,4] round-tripped to undefined).
Variables were spared: they have a schema, params have none.
This commit is contained in:
root committed 2026-09-10 08:14:18 +02:00
1 parent 3b3f374e10
commit 2cf788eedd
34 files changed
+284 -82

No files matched your search

+2 -2
View File
@@ -1,4 +1,4 @@
import type { PropertyBag } from '../model/Properties';
import { type PropertyBag, textOf } from '../model/Properties';
import { type DeclaredPort, declaredPortType, type Graph, type GraphNode } from './Graph';
import { nodeDef, type NodeDef, type PortDef } from './NodeRegistry';
@@ -74,7 +74,7 @@ export function resolveDef(node: GraphNode, lookup: BehaviorLookup): NodeDef | u
*/
export function callProblem(node: GraphNode, lookup: BehaviorLookup, ownerId = ''): string | null {
if (node.type !== RUN_BEHAVIOR) return null;
const id = String(node.params[BEHAVIOR_PARAM] ?? '');
const id = textOf(node.params[BEHAVIOR_PARAM] ?? '');
if (!id) return 'No behavior picked';
if (id === ownerId) return 'A behavior cannot run itself';
const program = lookup(id);