Let a variable hold a list

A variable could only be a number, a text or a yes/no, so every set of values had
to be smuggled through a string - which is where Tetris got its unreadable
"03066326048763714" colours from. A list is now a value of its own, with an item
kind (number, text, yes/no, colour) that decides how the inspector reads the one
comma-separated line it is typed as, and eight nodes to work with: List, Fill,
Count, Item at, Set item, Sub list, Join lists, Index of.

Two traps came with it. An array is typeof 'object', so every place that told a
value from an entity had to learn the difference, or a list would have been
passed around as a game object. And a value was compared by identity in three
places - with lists that marks everything as an overridden property after each
reload, so those comparisons now compare contents.

There is no list of objects: a list is written to the project file and an object
is a live handle that no reload survives. A set of objects stays a list of names.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
rootandClaude Opus 5 committed 2026-09-09 21:26:31 +02:00
1 parent c6f907b4e0
commit a9c3111377
25 files changed
+469 -25

No files matched your search

+18 -1
View File
@@ -1,8 +1,25 @@
/**
* A list is deliberately flat – a list of lists would need a nested editor and a deep compare on
* every override, and nothing so far wants one: a board is addressed as `row · width + column`.
*/
export type ListValue = readonly (number | string | boolean)[];
/** Colors are stored as 0xRRGGBB numbers. */
export type PropertyValue = number | string | boolean;
export type PropertyValue = number | string | boolean | ListValue;
export type PropertyBag = Record<string, PropertyValue>;
export const isList = (value: unknown): value is ListValue => Array.isArray(value);
/** Values are compared by content, because two equal lists are never the same array. */
export function sameValue(a: PropertyValue | undefined, b: PropertyValue | undefined): boolean {
if (isList(a) || isList(b)) {
if (!isList(a) || !isList(b) || a.length !== b.length) return false;
return a.every((item, i) => item === b[i]);
}
return a === b;
}
/** `undefined` removes the key, which is how an undo of "no value" works. */
export function setBagValue(bag: PropertyBag, key: string, value: PropertyValue | undefined): void {
if (value === undefined) delete bag[key];
+2 -1
View File
@@ -15,6 +15,7 @@ import {
type Position,
type PropertyBag,
type PropertyValue,
sameValue,
setBagValue,
type Vec2,
} from './Properties';
@@ -312,7 +313,7 @@ function instanceRef(target: InstanceTarget, object: SceneObject): ObjectRef {
}),
setOverride: (key, value) =>
write((edit) => {
if (value === target.base.overrides[key]) delete edit.overrides[key];
if (sameValue(value, target.base.overrides[key])) delete edit.overrides[key];
else setBagValue(edit.overrides, key, value);
}),
};