The new node "Raycast all" answers with every collider along the ray instead of only the nearest one: it carries the same inputs and settings as Raycast (Ignore owner, Hit triggers, Layers from #158) plus "max hits" where 0 means all, and it hands back parallel lists sorted by distance - objects, colliders, points x/y, normals x/y, distances - next to count, so index i of every list speaks about the same hit; a collider counts once no matter how many convex parts its shape is built from, while a tilemap counts per cell it is hit in, and the filter sits in Solids.raycastAll like the one of #158, the one path the editor run and Play both take, so all 12 new unit tests run through describe.each against both systems; the node needed the value system to learn something it could not do - a graph list carried numbers, text, booleans and bundles but no object handles, asList turned an object into its name, so objects/colliders would have been lists of names, which loses the object when two carry the same name, and collider children usually do; Value now knows a travelling list that may hold handles and "Item at" gives such an item out as an object, ready for Destroy, Apply force or Name of, while the single door into a stored file - writeTyped/asTypeOf - runs the list through the new storedList() so every object becomes its name again and nothing living reaches a project file, which is its own unit test over JSON.stringify; three list nodes follow the handles: "Index of" compares objects by identity because a name is not the object of that name, "Set item" and "Fill" write an object as an object, and "Join text" prints the names instead of the innards; this decision was mine to make because the question ran into the MCP timeout - the alternative was a list of names plus "Object by name", which is the smaller change but lossy; 4 new e2e checks in the new block 43e (shield and floor in one go reading 2 and "145, 280", the second object answering to its name, which only a real object in a list can do, "max hits" 1 stopping at the nearest, and the node showing the row) and 18 new unit tests, 12 in raycast.test.ts, 4 on the value system and 2 in the node catalog; two counter-checks hold: not counting tilemap cells one by one drops 2 unit tests, and answering with the nearest hit only drops module 43 with 2 FAILs; one finding on the way - the node read "maxHits" while every other input carries a spoken label, because int() took the key as its label, so it has a third parameter now, and the e2e check caught that, no unit test did (#157)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0fdde6cb1e
commit
ffdf0a8ddb
20 files changed
+607
-33
No files matched your search
@@ -1,14 +1,26 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { AddVariableCommand } from '../../src/core/commands/variableCommands';
|
||||
import { SetOverrideCommand } from '../../src/core/commands/sceneObjectCommands';
|
||||
import { sameValue, tagged, textOf } from '../../src/core/model/Properties';
|
||||
import { type PropertyBag, sameValue, tagged, textOf } from '../../src/core/model/Properties';
|
||||
import { findAsset, objectHolders, type Project } from '../../src/core/model/Project';
|
||||
import { deserializeProject } from '../../src/core/store/serialize';
|
||||
import { coerceValue, variableDef } from '../../src/core/properties/PropertySchema';
|
||||
import { coerceField } from '../../src/core/properties/kinds';
|
||||
import { deviatesFromAsset } from '../../src/core/properties/resolve';
|
||||
import { asBoolean, asEntity, asStruct, writeTyped } from '../../src/core/graph';
|
||||
import { fakeEntity, makeBehavior, makeSpriteScene, place, roundTrip } from '../helpers';
|
||||
import {
|
||||
asBoolean,
|
||||
asEntity,
|
||||
asList,
|
||||
asNumber,
|
||||
asString,
|
||||
asStruct,
|
||||
storedList,
|
||||
type Value,
|
||||
writeTyped,
|
||||
} from '../../src/core/graph';
|
||||
import { IndexOf } from '../../src/core/graph/nodes/data/IndexOf';
|
||||
import { NO_LOOKUP } from '../../src/core/graph/lookup';
|
||||
import { fakeContext, fakeEntity, makeBehavior, makeSpriteScene, place, roundTrip } from '../helpers';
|
||||
|
||||
const findObject = (project: Project, id: string) =>
|
||||
objectHolders(project)
|
||||
@@ -93,6 +105,39 @@ describe('a list as a value', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('a list carrying objects (#157)', () => {
|
||||
const hero = fakeEntity('Hero', 10);
|
||||
const crate = fakeEntity('Crate', 20);
|
||||
|
||||
it('hands the objects on as they are, and a lone object reads as a list of one', () => {
|
||||
expect(asList([hero, crate])).toEqual([hero, crate]);
|
||||
expect(asEntity(asList(hero)[0])).toBe(hero);
|
||||
expect(storedList([hero, 3, crate])).toEqual(['Hero', 3, 'Crate']);
|
||||
});
|
||||
|
||||
it('never lets a live object into a stored value', () => {
|
||||
// The one door into a PropertyBag: whatever arrives is narrowed to what a stored list may hold.
|
||||
const bag: PropertyBag = { seen: ['x'] };
|
||||
writeTyped(bag, 'seen', [hero, crate]);
|
||||
expect(bag.seen).toEqual(['Hero', 'Crate']);
|
||||
expect(JSON.parse(JSON.stringify(bag))).toEqual({ seen: ['Hero', 'Crate'] });
|
||||
});
|
||||
|
||||
it('reads as text by the names of its objects, not by what they are made of', () => {
|
||||
expect(asString([hero, 3, crate])).toBe('Hero, 3, Crate');
|
||||
});
|
||||
|
||||
it('is searched by identity, since two objects have no content to compare', () => {
|
||||
const indexOf = (list: Value, value: Value) =>
|
||||
asNumber(IndexOf.run!(fakeContext(), { list, value }, {}, IndexOf, 'exec', NO_LOOKUP).outputs?.index);
|
||||
expect(indexOf([hero, crate], crate)).toBe(1);
|
||||
expect(indexOf([hero, crate], fakeEntity('Crate', 20))).toBe(-1);
|
||||
// A name is not the object of that name, and neither stands in for the other.
|
||||
expect(indexOf([hero, crate], 'Crate')).toBe(-1);
|
||||
expect(indexOf(['Hero', 'Crate'], crate)).toBe(-1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('a struct as a value', () => {
|
||||
it('compares by content, one level deep, and tells a different shape apart', () => {
|
||||
expect(sameValue({ x: 1, tags: ['a'] }, { x: 1, tags: ['a'] })).toBe(true);
|
||||
|
||||
Reference in new issue
Block a user