Add ESLint and Prettier, and close the node test gaps
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>
This commit is contained in:
1 parent
f140c2fd77
commit
d54a620ebd
113 files changed
+5049
-666
No files matched your search
@@ -1,6 +1,13 @@
|
||||
import { isHandleEnabled, worldPositionOf } from '../../core/graph';
|
||||
import { parentChain } from '../../core/model/walk';
|
||||
import { axisCorrection, colliderShape, colliderSize, isTrigger, type PlaneAxis, shapesOverlap } from '../../core/properties/collider';
|
||||
import {
|
||||
axisCorrection,
|
||||
colliderShape,
|
||||
colliderSize,
|
||||
isTrigger,
|
||||
type PlaneAxis,
|
||||
shapesOverlap,
|
||||
} from '../../core/properties/collider';
|
||||
import type { Entity } from '../Entity';
|
||||
import type { World } from '../World';
|
||||
import type { System } from './System';
|
||||
@@ -79,22 +86,52 @@ export class CollisionSystem implements System {
|
||||
* are split into sub-steps so nothing jumps across a thin wall. Triggers, the entity's own
|
||||
* colliders and colliders that travel with it (descendants) never block.
|
||||
*/
|
||||
moveAndSlide(world: World, entity: Entity, dx: number, dy: number): { blockedX: boolean; blockedY: boolean } {
|
||||
moveAndSlide(
|
||||
world: World,
|
||||
entity: Entity,
|
||||
dx: number,
|
||||
dy: number,
|
||||
): { blockedX: boolean; blockedY: boolean } {
|
||||
const own = collidersOf(world, entity).filter((c) => !isTrigger(c.props) && isHandleEnabled(c));
|
||||
const obstacles = own.length === 0 ? [] : world.entities.filter((c) => c.type === 'collider' && !isTrigger(c.props) && isHandleEnabled(c) && !travelsWith(c, entity));
|
||||
return { blockedX: this.slideAxis(entity, own, obstacles, 'x', dx), blockedY: this.slideAxis(entity, own, obstacles, 'y', dy) };
|
||||
const obstacles =
|
||||
own.length === 0
|
||||
? []
|
||||
: world.entities.filter(
|
||||
(c) =>
|
||||
c.type === 'collider' && !isTrigger(c.props) && isHandleEnabled(c) && !travelsWith(c, entity),
|
||||
);
|
||||
return {
|
||||
blockedX: this.slideAxis(entity, own, obstacles, 'x', dx),
|
||||
blockedY: this.slideAxis(entity, own, obstacles, 'y', dy),
|
||||
};
|
||||
}
|
||||
|
||||
private slideAxis(entity: Entity, own: Entity[], obstacles: Entity[], axis: PlaneAxis, delta: number): boolean {
|
||||
private slideAxis(
|
||||
entity: Entity,
|
||||
own: Entity[],
|
||||
obstacles: Entity[],
|
||||
axis: PlaneAxis,
|
||||
delta: number,
|
||||
): boolean {
|
||||
if (delta === 0 || own.length === 0) return false;
|
||||
// Long steps are split so a fast mover cannot jump across a thin obstacle in one frame.
|
||||
const steps = Math.min(MAX_STEPS, Math.max(1, Math.ceil(Math.abs(delta) / stepLimit(own, obstacles, axis))));
|
||||
for (let i = 0; i < steps; i++) if (this.stepAxis(entity, own, obstacles, axis, delta / steps)) return true;
|
||||
const steps = Math.min(
|
||||
MAX_STEPS,
|
||||
Math.max(1, Math.ceil(Math.abs(delta) / stepLimit(own, obstacles, axis))),
|
||||
);
|
||||
for (let i = 0; i < steps; i++)
|
||||
if (this.stepAxis(entity, own, obstacles, axis, delta / steps)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/** One indivisible move along `axis`; returns whether an obstacle stopped it. */
|
||||
private stepAxis(entity: Entity, own: Entity[], obstacles: Entity[], axis: PlaneAxis, delta: number): boolean {
|
||||
private stepAxis(
|
||||
entity: Entity,
|
||||
own: Entity[],
|
||||
obstacles: Entity[],
|
||||
axis: PlaneAxis,
|
||||
delta: number,
|
||||
): boolean {
|
||||
const dir: 1 | -1 = delta > 0 ? 1 : -1;
|
||||
// How deep each pair already sits in the other before the step. A step may never deepen
|
||||
// that, but resolving it is not our job (no jolt, and leaving a wall is always possible).
|
||||
|
||||
Reference in new issue
Block a user