Tooling: - Prettier as the single formatting authority (110 columns, single quotes, measured against the existing style); Markdown excluded because table-cell padding turns the README glossary into 1000-character lines. - Type-aware ESLint ending in eslint-config-prettier, so a rule always means "this is a bug", never "this looks different". - .editorconfig and an `npm run check` that runs typecheck, lint, format and tests in one go. Findings fixed on the first run: three more duplicate imports, four String(unknown) calls (parameter types narrowed instead of casts), two dead initialisers, two redundant casts, a console.log for a real error. Two warnings were false here and are disabled with a reason in place. Tests 132 -> 269: - node-catalog.test.ts covers every node with a run(), with a guard that fails when a new node has no row. - behavior-events.test.ts covers the ten event nodes through a running World, checking timing rather than existence. - Input's state machine (press/release/releaseAll) is reachable without DOM events now; the DOM handlers are thin adapters. Compare fell back to == without a configured operator although the schema default is <; aligned. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
104 lines
3.7 KiB
TypeScript
104 lines
3.7 KiB
TypeScript
import type { PropertyBag } from '../model/Properties';
|
||
import { sumAlongChain } from '../model/walk';
|
||
import { enabledAlongChain } from '../properties/enabled';
|
||
|
||
/**
|
||
* What a 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 } {
|
||
return sumAlongChain(
|
||
handle,
|
||
(h) => h.parent,
|
||
(h) => h,
|
||
);
|
||
}
|
||
|
||
/** 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. */
|
||
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;
|
||
}
|