Refactoring: one file per node, consistent comments, shared helpers, split large modules

- Graph: nodes.ts split into nodes/<category>/<Type>.ts (62 files) with
  shared.ts factories and index.ts registry; NodeContext/values/NodeRegistry
  separated, writeTyped() replaces duplicated typed writes
- Comments: one style (JSDoc on non-obvious exports, // for decisions and
  traps), wrong/stale comments fixed or removed, ids.ts now real 10 base-36 chars
- Core: chainOf, shared worldPosition/editorPosition core, commands/require.ts,
  commands/naming.ts, util/list.ts, setBagValue/toPosition/propDef; dead code
  removed (isBehaviorAsset, SCENE_OBJECT_SCHEMA, CommandStack.subscribe,
  Input.axis, setMode, titleText, World.worldPosition, unused ctor params)
- Editor: Inspector split into inspector/ sections, App.ts into Editor.ts +
  debugHook.ts + shortcuts.ts, Viewport into items.ts + symbols.ts, shared
  "Add collider" action, unified type labels/swatches
- UI toolkit: Widget.trackHover/onPointerRelease, drawControlBox, math helpers,
  Popup.openBelow, Menu/Dialog/ColorPicker/ListRow in own files, named Theme sizes
- Tools: seed-demo/smoke-demo and demo-check project removed, tools/lib
  (harness, projects, builders), e2e.mjs as runner over tools/e2e/*.mjs sections
- Tests: tests/helpers.ts replaces copied fixtures and NodeContext doubles

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
rootandClaude Fable 5.1 committed 2026-09-08 08:37:22 +02:00
1 parent 3234eb480f
commit 1eb28314cf
189 files changed
+6231 -5543

No files matched your search

+25 -25
View File
@@ -42,6 +42,11 @@ export class BehaviorSystem implements System {
this.syncInstances(world);
}
stop(): void {
this.instances = [];
this.world = null;
}
/**
* One instance per behavior entity: entities that appeared (placed while playing) get an
* interpreter and their `On start`; instances whose entity is gone are dropped. A behavior
@@ -56,7 +61,10 @@ export class BehaviorSystem implements System {
if (!asset || asset.type !== 'behavior') continue;
const trace: Trace = { edges: new Map(), nodes: new Map() };
const interpreter = new GraphInterpreter(asset.graph);
interpreter.trace = this.traceListener(trace, world);
interpreter.trace = {
onEdge: (id) => trace.edges.set(id, world.time),
onNode: (id) => trace.nodes.set(id, world.time),
} satisfies TraceListener;
const instance = { entity, interpreter, trace };
this.instances.push(instance);
this.run(instance, (i, ctx) => i.fire('OnStart', ctx), 0);
@@ -117,22 +125,21 @@ export class BehaviorSystem implements System {
fire('OnCollisionExit', this.collisions.exits);
}
stop(): void {
this.instances = [];
this.world = null;
}
/** Live highlight data for the graph editor: merged over all instances of the asset. */
traceFor(assetId: string): Trace {
const merged: Trace = { edges: new Map(), nodes: new Map() };
const latest = (into: Map<string, number>, from: Map<string, number>) => {
for (const [id, t] of from) into.set(id, Math.max(t, into.get(id) ?? -Infinity));
};
for (const instance of this.instances) {
if (instance.entity.assetId !== assetId) continue;
for (const [id, t] of instance.trace.edges) merged.edges.set(id, Math.max(t, merged.edges.get(id) ?? -Infinity));
for (const [id, t] of instance.trace.nodes) merged.nodes.set(id, Math.max(t, merged.nodes.get(id) ?? -Infinity));
latest(merged.edges, instance.trace.edges);
latest(merged.nodes, instance.trace.nodes);
}
return merged;
}
/** Runs graph code for one instance; an exception stops play and is logged, never thrown. */
private run(instance: Instance, body: (interpreter: GraphInterpreter, ctx: NodeContext) => void, dt: number): void {
const world = this.world;
if (!world) return;
@@ -146,6 +153,9 @@ export class BehaviorSystem implements System {
}
private context(entity: Entity, world: World, dt: number): NodeContext {
// Handles arriving from the graph are the entities themselves, or test doubles with the same id.
const resolve = (handle: EntityHandle | null): Entity | undefined =>
handle ? world.entities.find((e) => e === handle || e.id === handle.id) : undefined;
return {
self: entity,
owner: entity.parent,
@@ -156,16 +166,16 @@ export class BehaviorSystem implements System {
isKeyDown: (key) => this.input.isDown(key),
pointer: world.pointer,
hitTest: (target, x, y) => {
const found = world.entities.find((e) => e === target || e.id === target.id);
const found = resolve(target);
return Boolean(found && world.hitTest(found, x, y));
},
isTouching: (a, b) => {
const ea = world.entities.find((e) => e === a || e.id === a.id);
const eb = world.entities.find((e) => e === b || e.id === b.id);
const ea = resolve(a);
const eb = resolve(b);
return Boolean(this.collisions && ea && eb && this.collisions.touching(world, ea, eb));
},
moveAndSlide: (target, dx, dy) => {
const found = world.entities.find((e) => e === target || e.id === target.id);
const found = resolve(target);
if (!found) return { blockedX: false, blockedY: false };
if (this.collisions) return this.collisions.moveAndSlide(world, found, dx, dy);
found.x += dx;
@@ -176,12 +186,9 @@ export class BehaviorSystem implements System {
world.log.push(message);
console.log(`[behavior ${entity.name}]`, message);
},
spawn: (assetId, position, parent) => {
const parentEntity = parent ? world.entities.find((e) => e === parent || e.id === parent.id) ?? null : null;
return world.spawn(assetId, position, parentEntity);
},
destroy: (target: EntityHandle) => {
const found = world.entities.find((e) => e === target || e.id === target.id);
spawn: (assetId, position, parent) => world.spawn(assetId, position, resolve(parent) ?? null),
destroy: (target) => {
const found = resolve(target);
if (found) world.remove(found);
},
stop: () => {
@@ -192,11 +199,4 @@ export class BehaviorSystem implements System {
},
};
}
private traceListener(trace: Trace, world: World): TraceListener {
return {
onEdge: (id) => trace.edges.set(id, world.time),
onNode: (id) => trace.nodes.set(id, world.time),
};
}
}