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:
1 parent
3234eb480f
commit
1eb28314cf
189 files changed
+6231
-5543
No files matched your search
@@ -1,106 +1,8 @@
|
||||
import type { PropertyBag, PropertyValue } from '../model/Properties';
|
||||
import { enabledAlongChain } from '../properties/enabled';
|
||||
import type { PropertyDef } from '../properties/PropertySchema';
|
||||
import type { PortType } from './Graph';
|
||||
|
||||
/** A value travelling along a data edge. Objects are live entity handles (play mode only). */
|
||||
export type Value = PropertyValue | EntityHandle | null;
|
||||
|
||||
/**
|
||||
* What the graph sees of a running entity. The runtime's Entity satisfies this shape; unit
|
||||
* tests use plain objects. Mutating `x`/`y`/`props` is how actions take effect.
|
||||
*/
|
||||
export interface EntityHandle {
|
||||
id: string;
|
||||
/** Asset this entity was instantiated from (what `Is asset` compares against). */
|
||||
assetId: string;
|
||||
name: string;
|
||||
/** Position local to `parent` (world position for top-level entities). */
|
||||
x: number;
|
||||
y: number;
|
||||
z: number;
|
||||
props: PropertyBag;
|
||||
/** The entity this one is parented to; children move with their parent. */
|
||||
parent: EntityHandle | null;
|
||||
}
|
||||
|
||||
/** World position of a handle: its local position plus those of all ancestors. */
|
||||
export function worldPositionOf(handle: EntityHandle): { x: number; y: number; z: number } {
|
||||
const result = { x: handle.x, y: handle.y, z: handle.z };
|
||||
const seen = new Set<EntityHandle>([handle]);
|
||||
for (let p = handle.parent; p && !seen.has(p); p = p.parent) {
|
||||
result.x += p.x;
|
||||
result.y += p.y;
|
||||
result.z += p.z;
|
||||
seen.add(p);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Whether a handle and every ancestor of it are switched on (see `properties/enabled.ts`). */
|
||||
export function isHandleEnabled(handle: EntityHandle): boolean {
|
||||
return enabledAlongChain(handle, (h) => h.props, (h) => h.parent);
|
||||
}
|
||||
|
||||
/** Where the mouse is (world units) and what it did this frame. */
|
||||
export interface PointerState {
|
||||
x: number;
|
||||
y: number;
|
||||
/** Whether the pointer is over the game view at all. */
|
||||
inside: boolean;
|
||||
down: boolean;
|
||||
/** Went down / up during this frame. */
|
||||
pressed: boolean;
|
||||
released: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Services a graph may use while running. Implemented by the runtime (BehaviorSystem) and
|
||||
* by test doubles. Everything the graph does to the world goes through here, so the core
|
||||
* stays free of runtime imports.
|
||||
*/
|
||||
export interface NodeContext {
|
||||
/** The behavior entity itself. */
|
||||
self: EntityHandle;
|
||||
/** The behavior's parent object (the one the graph acts on), if any. */
|
||||
owner: EntityHandle | null;
|
||||
/** Mutable variable state of this behavior instance. */
|
||||
variables: PropertyBag;
|
||||
/** Seconds since the world started and since the last tick. */
|
||||
time: number;
|
||||
dt: number;
|
||||
findByName(name: string): EntityHandle | null;
|
||||
isKeyDown(key: string): boolean;
|
||||
/** Mouse state of this frame, in world units (see `PointerState`). */
|
||||
pointer: PointerState;
|
||||
/**
|
||||
* Whether a world point lies on `target` – on its colliders when it has any, otherwise on
|
||||
* its own footprint or that of anything parented below it (so a button made of a container
|
||||
* with a sprite and a label is hit as a whole).
|
||||
*/
|
||||
hitTest(target: EntityHandle, x: number, y: number): boolean;
|
||||
/**
|
||||
* Whether any collider of `a` overlaps any collider of `b` right now. A collider entity
|
||||
* stands for itself; any other entity stands for its direct collider children.
|
||||
*/
|
||||
isTouching(a: EntityHandle, b: EntityHandle): boolean;
|
||||
/**
|
||||
* Moves `target` by (dx, dy) in local units, stopping its solid colliders at other solid
|
||||
* colliders axis by axis (x first, then y) so it slides along walls. Reports which axes hit.
|
||||
*/
|
||||
moveAndSlide(target: EntityHandle, dx: number, dy: number): { blockedX: boolean; blockedY: boolean };
|
||||
log(message: string): void;
|
||||
/**
|
||||
* Places a new instance of an asset into the running scene and returns it. The position is
|
||||
* local to `parent` (world when none). Returns null when the asset does not exist or cannot
|
||||
* be placed (a scene). Spawned behaviors run their `On start` on the next tick.
|
||||
*/
|
||||
spawn(assetId: string, position: { x: number; y: number; z: number }, parent: EntityHandle | null): EntityHandle | null;
|
||||
destroy(target: EntityHandle): void;
|
||||
stop(): void;
|
||||
/** Ends the run and starts it again from the saved scene (everything spawned is lost). */
|
||||
restart(): void;
|
||||
}
|
||||
import type { NodeContext } from './NodeContext';
|
||||
import { asBoolean, asNumber, asString, type Value } from './values';
|
||||
|
||||
export interface PortDef {
|
||||
key: string;
|
||||
@@ -128,7 +30,7 @@ export interface NodeDef {
|
||||
type: string;
|
||||
label: string;
|
||||
category: NodeCategory;
|
||||
/** Shown in the palette. */
|
||||
/** Shown in the palette and the inspector. */
|
||||
description: string;
|
||||
inputs: PortDef[];
|
||||
outputs: PortDef[];
|
||||
@@ -177,7 +79,7 @@ export function inlineInputs(def: NodeDef): PortDef[] {
|
||||
return def.inputs.filter((p) => p.type !== 'exec' && p.type !== 'object');
|
||||
}
|
||||
|
||||
/** A PropertyDef for editing an inline input value of the given port. */
|
||||
/** A PropertyDef for editing the inline value of a port. */
|
||||
export function inlineInputDef(port: PortDef): PropertyDef {
|
||||
switch (port.type) {
|
||||
case 'boolean':
|
||||
@@ -194,30 +96,3 @@ export function inlineInputDef(port: PortDef): PropertyDef {
|
||||
export function inlineValue(node: { params: PropertyBag }, port: PortDef): Value {
|
||||
return node.params[port.key] ?? port.default ?? null;
|
||||
}
|
||||
|
||||
// --- value helpers for node implementations ---------------------------------------------
|
||||
|
||||
export function asNumber(value: Value | undefined): number {
|
||||
if (typeof value === 'number') return value;
|
||||
if (typeof value === 'boolean') return value ? 1 : 0;
|
||||
if (typeof value === 'string') {
|
||||
const n = Number(value);
|
||||
return Number.isFinite(n) ? n : 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
export function asBoolean(value: Value | undefined): boolean {
|
||||
if (typeof value === 'string') return value !== '' && value !== 'false' && value !== '0';
|
||||
return Boolean(value);
|
||||
}
|
||||
|
||||
export function asString(value: Value | undefined): string {
|
||||
if (value === null || value === undefined) return '';
|
||||
if (typeof value === 'object') return value.name;
|
||||
return String(value);
|
||||
}
|
||||
|
||||
export function asEntity(value: Value | undefined): EntityHandle | null {
|
||||
return value && typeof value === 'object' ? value : null;
|
||||
}
|
||||
Reference in new issue
Block a user