This website requires JavaScript.
Explore
Help
Sign In
meleki
/
feuerwerk
Watch
1
Star
0
Fork
0
Code
Issues
38
Actions
Projects
1
Wiki
Activity
Files
ffdf0a8ddb3d58d8301c02c39d6ae8ecb228b8a6
feuerwerk
/
tests
/
core
T
History
admin
and
Claude Opus 5
ffdf0a8ddb
game-maker / gate (push)
Successful in 2m4s
Details
game-maker / suite (push)
Successful in 7m26s
Details
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
>
2026-09-20 18:54:12 +02:00
..
animation-preview.test.ts
Physics stage 1a runs on Rapier: a new Rigidbody child object (body type, mass, gravity scale, damping, fixed rotation, CCD) makes its parent fall, bounce and come to rest in play, colliders gain friction, bounciness and density, the scene gains gravity (0, 980), the new PhysicsSystem steps at a fixed 60 Hz with interpolation in world coordinates and treats a tilemap as static ground, On collision enter gains normal and impulse, play waits for Rapier and does not start without it, and the editor run keeps the old CollisionSystem; the existing collision tests run against both systems unchanged (
#146
)
2026-09-18 18:11:43 +02:00
animation.test.ts
The scene view and the animation preview share one camera, the three form dialogs one base, and keys are found through one set of helpers (
#85
)
2026-09-17 14:19:53 +02:00
apply.test.ts
Measure a deviation against the base, not the innermost template
2026-09-09 13:45:27 +02:00
aspect.test.ts
Create a sprite asset from a texture's context menu
2026-09-11 09:57:19 +02:00
asset-ref.test.ts
Consolidate duplicated editor code, trim comments, refresh README
2026-09-11 11:00:30 +02:00
auth.test.ts
Changing the password: the Logout button at the far right of the toolbar became an Account menu that names the signed-in account in an entry of its own that cannot be picked, with Change password... above Logout, and the new dialog (src/editor/auth/PasswordDialog.ts) asks for the current password, the new one and its repetition, all three as bullets, and stays standing when the server refuses so that it can say why, unlike the other form dialogs; the rules for empty, too short, new equal to old and a differing repetition sit as the pure function passwordProblem() next to loginProblem(), the API gained POST ?r=auth/password which answers 204, insists on the current password although a session is there, enforces a minimum length of 8 in UserStore::MIN_PASSWORD_LENGTH and again in the client so the mask can answer without asking the server, and the new SessionStore::destroyAllFor($user, $keep) ends every other session of the account while the tab that changed the password keeps its own; the placeholders admin/admin and user/user stay as they are, which the length check does not touch because it only runs when a password is set through the API; e2e 45 grew by 8 checks for the menu, the dialog, both refusals and signing in with the old and the new password, and it sets the e2e account back at the end - a run dying in between leaves it on <password>-tmp, which a make-user.php call repairs (
#182
)
2026-09-20 13:46:40 +02:00
bag-commands.test.ts
Fix findUsages/undo/node-lookup bugs, split Viewport and Editor, share command and inspector helpers
2026-09-08 09:22:49 +02:00
behavior-events.test.ts
Physics stage 1a runs on Rapier: a new Rigidbody child object (body type, mass, gravity scale, damping, fixed rotation, CCD) makes its parent fall, bounce and come to rest in play, colliders gain friction, bounciness and density, the scene gains gravity (0, 980), the new PhysicsSystem steps at a fixed 60 Hz with interpolation in world coordinates and treats a tilemap as static ground, On collision enter gains normal and impulse, play waits for Rapier and does not start without it, and the editor run keeps the old CollisionSystem; the existing collision tests run against both systems unchanged (
#146
)
2026-09-18 18:11:43 +02:00
camera.test.ts
Play through the first camera that is switched on
2026-09-09 16:13:30 +02:00
collapsed-node.test.ts
Add the Variable node and show the variable name in node titles
2026-09-11 13:34:09 +02:00
collider.test.ts
Move with collision reacts along the contact normal: it travels to the surface, stays flush and slides the rest of the move along it with Coulomb friction (the mean of both colliders, a tile counts as 0.5), a circle now rolls over an edge instead of catching on it, pushed dynamic bodies move off along the normal, Move gains the outputs hit, normal x and normal y, an object without its own collider moves freely, and editor run and play share the same path through Solids; contact point and normal come from our own f64 geometry (contactBetween), because Rapier's castShape lands 3e-6 px inside the wall in f32 (
#150
)
2026-09-18 21:52:11 +02:00
collision-layers.test.ts
Collision layers and masks: a collider carries a layer and a mask of the layers it meets, and the pair only meets when both masks name the other's layer - the rule is one pure module (src/core/model/collisionLayers.ts) with layerBit, layersMeet and the names a project starts with, so the three places that have to agree read the same function: the editor run through CollisionSystem, the Solids sweep per pair and Rapier, where the mask becomes the collider's interaction groups; the project file gained collisionLayers: ["Default"] (default one layer, no formatVersion bump), the collider schema two new kinds - layer as a dropdown of the project's layers and layerMask as a menu of tickboxes that stays open while it is ticked (Menu.ts learned rows that do not close on a pick) - and the names are edited through "Collision layers..." in the toolbar's Project menu, not a Project tab, which is the decision I made myself because the question ran into the MCP timeout: the dialog (src/editor/CollisionLayersDialog.ts) holds up to 16 names, Add layer appends "Layer <n>" and Apply writes all of them as one undo step (SetCollisionLayersCommand); a mask read from a file goes through a check that refuses anything that is not a finite number, because Number(null) is 0 and a collidesWith: null would silently have meant "meets nothing"; 9 new e2e checks in module 8 cover the menu entry, the dialog, Add layer, Apply and its undo, both inspector rows, the tickbox menu, and the mover walking straight through the crate that 8c blocked on once their layers no longer meet, and 21 new unit tests cover the rule, the schema and the command (
#152
)
2026-09-20 15:31:22 +02:00
collision-stay.test.ts
One-way platforms, raycast and collision stay with contact data: a collider can be one-way with a side it blocks from (turning with its object, drawn thick in the scene view) and a tile can be one-way from above, one-way cells never merge with solid tiles, a contact blocks only when its normal is within 45 degrees of the blocking side, the new node Ignore one-way lets an object drop through for a while, and it all holds for Move and for dynamic bodies in Rapier, where the decision is made before each step because the hook may not touch the engine (
#155
); the new Raycast node answers hit, object, collider, point, normal and distance in our own f64 geometry, the same in editor run and play, and the editor run draws each ray for 0.25 s (
#153
); On collision stay gains an interval with its own clock per pair, and stay and enter gain the contact point next to normal and impulse (
#149
); fixes that every Rapier collider of a tilemap had the shape of its last cell
2026-09-19 11:36:11 +02:00
commands.test.ts
Transform: scale, rotation and mirror as properties, world transform as a matrix chain, oriented colliders (
#76
)
2026-09-13 17:25:06 +02:00
container.test.ts
Remove exports and methods nothing uses any more
2026-09-11 20:15:49 +02:00
deviation.test.ts
Shader assets: graph-built shaders, the Shader tab and sprite rows from the graph (
#62
)
2026-09-12 22:41:14 +02:00
dynamic-ports.test.ts
State machines: asset, tab, State branch and Set state nodes (
#51
)
2026-09-12 18:00:23 +02:00
enabled.test.ts
Shader assets: graph-built shaders, the Shader tab and sprite rows from the graph (
#62
)
2026-09-12 22:41:14 +02:00
folded-edge.test.ts
Give the three asset tabs one base, and move the viewports into the editor
2026-09-10 07:14:38 +02:00
fonts.test.ts
Fonts: a new asset type, picked from the server or dropped in, worn by text objects (
#74
)
2026-09-13 20:09:59 +02:00
frame-commands.test.ts
A frame with borders tiles unless told to stretch
2026-09-16 18:13:11 +02:00
ghost-ports.test.ts
An edge to a vanished port needs a handle, so the port stays as a ghost (
#60
)
2026-09-14 11:50:36 +02:00
graph-annotations.test.ts
A graph that grew past one screen needs somewhere to say what a part of it is for (
#38
)
2026-09-16 11:24:34 +02:00
graph.test.ts
A state change is something the graph can react to, so the machine announces it (
#53
,
#54
,
#55
,
#57
)
2026-09-15 06:18:20 +02:00
hierarchy.test.ts
Transform: scale, rotation and mirror as properties, world transform as a matrix chain, oriented colliders (
#76
)
2026-09-13 17:25:06 +02:00
holders.test.ts
Collision layers and masks: a collider carries a layer and a mask of the layers it meets, and the pair only meets when both masks name the other's layer - the rule is one pure module (src/core/model/collisionLayers.ts) with layerBit, layersMeet and the names a project starts with, so the three places that have to agree read the same function: the editor run through CollisionSystem, the Solids sweep per pair and Rapier, where the mask becomes the collider's interaction groups; the project file gained collisionLayers: ["Default"] (default one layer, no formatVersion bump), the collider schema two new kinds - layer as a dropdown of the project's layers and layerMask as a menu of tickboxes that stays open while it is ticked (Menu.ts learned rows that do not close on a pick) - and the names are edited through "Collision layers..." in the toolbar's Project menu, not a Project tab, which is the decision I made myself because the question ran into the MCP timeout: the dialog (src/editor/CollisionLayersDialog.ts) holds up to 16 names, Add layer appends "Layer <n>" and Apply writes all of them as one undo step (SetCollisionLayersCommand); a mask read from a file goes through a check that refuses anything that is not a finite number, because Number(null) is 0 and a collidesWith: null would silently have meant "meets nothing"; 9 new e2e checks in module 8 cover the menu entry, the dialog, Add layer, Apply and its undo, both inspector rows, the tickbox menu, and the mover walking straight through the crate that 8c blocked on once their layers no longer meet, and 21 new unit tests cover the rule, the schema and the command (
#152
)
2026-09-20 15:31:22 +02:00
http-store.test.ts
Plugins belong to the project, and they load before it: the project file gained the field plugins: ["demo-plugin.js"] (default empty, no formatVersion bump), the Plugins tab's switch writes into it as an ordinary undo step (SetPluginActiveCommand) instead of into a server-wide plugins.json, and the start order is turned around - startPlugins only fetches the list of files, then editor.boot() runs and ProjectStore.load(id, prepare) hands the raw JSON through before it is deserialized, where pluginsOf(raw) reads the list and PluginManager.syncTo() activates exactly those; that is the point of the issue, because what a plugin registers has to stand there before normalizeGraph would throw it away as unknown (
#126
,
#127
), a project switch first stops what the new project does not name and then starts what it does, a file the server does not have is passed over silently, and after signing out the manager is dead so a late event cannot start anything in a torn-down session; on the server plugins.json, PluginStore::setActive, isActive, the route PUT ?r=plugins/{file} and the 403 for inactive modules are gone and GET ?r=plugins only answers {file,bytes,updatedAt}, which makes every plugin file fetchable - the consequence accepted in
#117
, written down in the wiki for
#115
and
#116
; publishPlugins(root) only copies files now, the seeder switches the demo plugin on in the fixture project, and the Plugins tab carries a puzzle piece as its icon while only a plugin's own tab still goes without one (the knob and the socket are cubic curves, not arcs, which Pixi's parser draws as a spike after an L); 3 new e2e checks in module 38 and 5 new unit tests cover the project as the owner of the switch, its undo, the order, pluginsOf against crude input and an unknown file name (
#119
)
2026-09-20 14:24:27 +02:00
key-repeat.test.ts
Type a repeat delay as whole milliseconds
2026-09-10 05:59:39 +02:00
kinds.test.ts
State machines: asset, tab, State branch and Set state nodes (
#51
)
2026-09-12 18:00:23 +02:00
list-item-kind.test.ts
An animation is a timeline of rows over one asset, played on an object rather than worn as a picture (
#97
)
2026-09-16 20:02:25 +02:00
list-of-structs.test.ts
Describe every value kind in one table instead of spreading it over the code
2026-09-12 14:47:17 +02:00
list-value.test.ts
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
)
2026-09-20 18:54:12 +02:00
list.test.ts
The tab headers are one TabHeader, the node board and the tile board pan and zoom through one helper, reordering tile pictures and tilemap layers goes through one moveItem, and a tilemap cell is one named type (
#85
)
2026-09-18 16:01:12 +02:00
live-values.test.ts
State machines: asset, tab, State branch and Set state nodes (
#51
)
2026-09-12 18:00:23 +02:00
load-scene.test.ts
Physics stage 1a runs on Rapier: a new Rigidbody child object (body type, mass, gravity scale, damping, fixed rotation, CCD) makes its parent fall, bounce and come to rest in play, colliders gain friction, bounciness and density, the scene gains gravity (0, 980), the new PhysicsSystem steps at a fixed 60 Hz with interpolation in world coordinates and treats a tilemap as static ground, On collision enter gains normal and impulse, play waits for Rapier and does not start without it, and the editor run keeps the old CollisionSystem; the existing collision tests run against both systems unchanged (
#146
)
2026-09-18 18:11:43 +02:00
nested-behavior.test.ts
State machines: asset, tab, State branch and Set state nodes (
#51
)
2026-09-12 18:00:23 +02:00
node-catalog.test.ts
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
)
2026-09-20 18:54:12 +02:00
nodes.test.ts
Replace the five variable nodes with one Variables node
2026-09-11 16:56:24 +02:00
one-way-angle.test.ts
The Raycast node filters by collision layers: the node gained a "Layers" setting, a tickbox per collision layer of the project - the same field kind the collider uses for "Collides with" - and the ray only hits what sits on a ticked layer, tilemaps counting as colliders; the default is every layer, and a graph without the param answers exactly as before, which is its own unit test; the check is one-sided on purpose, unlike the symmetric rule of
#152
: a ray is not a collider, so it never reads the collidesWith of the thing it hits - a collider that meets nothing is still seen by a ray - because who a ray may look at is the ray's business, and a unit test holds that case; the filter sits in Solids.raycast in front of the intersection test, which is the one path the editor run and Play both take, so every unit test runs through describe.each against both systems; NodeView did not know the field kinds layer/layers, so the node would have read "Layers: 65533" - it now shows the name of the layer, or "All layers"; 5 new e2e checks in the new block 43d (a shield on its own layer between the caster and the floor stops the ray, the "Layers" row on the node, the tickbox menu with the project's layers, the ray reaching 280 instead of 145 units once the shield's layer is unticked, and the mask read as a name) and 6 new unit tests, three against each system; two counter-checks hold: disarming the filter line drops 6 unit tests and the unticking check in module 43, and switching the formatting off makes the node check fail with the raw bits; two findings from measuring came out of it and stand in the wiki and the notes - __gm.rays() only measures while the Scene tab is in front, so a node selection blinds it and the block measures through a Log node on distance instead, and a running behavior instance keeps the graph it started with, so the editor run casts with the old mask until the instance is built anew (
#158
)
2026-09-20 18:15:35 +02:00
one-way.test.ts
One-way platforms, raycast and collision stay with contact data: a collider can be one-way with a side it blocks from (turning with its object, drawn thick in the scene view) and a tile can be one-way from above, one-way cells never merge with solid tiles, a contact blocks only when its normal is within 45 degrees of the blocking side, the new node Ignore one-way lets an object drop through for a while, and it all holds for Move and for dynamic bodies in Rapier, where the decision is made before each step because the hook may not touch the engine (
#155
); the new Raycast node answers hit, object, collider, point, normal and distance in our own f64 geometry, the same in editor run and play, and the editor run draws each ray for 0.25 s (
#153
); On collision stay gains an interval with its own clock per pair, and stay and enter gain the contact point next to normal and impulse (
#149
); fixes that every Rapier collider of a tilemap had the shape of its last cell
2026-09-19 11:36:11 +02:00
parallel-transitions.test.ts
Several transitions between the same pair of states: make transition onto a state that is already connected in that direction adds another transition, which gets its own name right away (Transition 2, Transition 3, skipping names that already leave the state), so two unnamed transitions between one pair never exist; renaming is refused when another transition between the same pair in the same direction already carries the name, the empty name included, while equal names to other targets are only warned about as before; the behavior graph keeps Already connected; self transitions may repeat too, each further loop drawn as a ring around the previous one with the preview showing the next ring, and transition labels that would cover another one slide along their own edge until they are free (pure function freeLabelSpot); test hook transitionLabelAt also returns width and height, e2e 26e6 (
#162
)
2026-09-19 16:30:52 +02:00
particles.test.ts
Three stores, three importers and three pickers that differ only in their type are one of each (
#85
)
2026-09-16 17:38:54 +02:00
physics-engine.test.ts
Rapier 2D (@dimforge/rapier2d-compat 0.20) comes in as the physics engine: the editor loads it once in the background right after opening a project from its own chunk, so the main bundle grows by 0.19 KB gzip and the first frame does not wait; loadPhysics, physicsStatus and the 100 px = 1 m conversion live in src/runtime/physics/engine.ts, __gm.physics() reports the state, unit tests pin free fall and a box resting on a box, and e2e module 41 pins that Rapier is ready from its own chunk without console noise (
#145
)
2026-09-18 16:56:47 +02:00
physics-system.test.ts
One-way platforms, raycast and collision stay with contact data: a collider can be one-way with a side it blocks from (turning with its object, drawn thick in the scene view) and a tile can be one-way from above, one-way cells never merge with solid tiles, a contact blocks only when its normal is within 45 degrees of the blocking side, the new node Ignore one-way lets an object drop through for a while, and it all holds for Move and for dynamic bodies in Rapier, where the decision is made before each step because the hook may not touch the engine (
#155
); the new Raycast node answers hit, object, collider, point, normal and distance in our own f64 geometry, the same in editor run and play, and the editor run draws each ray for 0.25 s (
#153
); On collision stay gains an interval with its own clock per pair, and stay and enter gain the contact point next to normal and impulse (
#149
); fixes that every Rapier collider of a tilemap had the shape of its last cell
2026-09-19 11:36:11 +02:00
placement.test.ts
Place assets by dragging them from the panel into the view
2026-09-09 13:09:13 +02:00
polygon.test.ts
The scene view outlines a tilemap's collision only along its outer edges: where two pieces meet, their shared stretch runs once in each direction and is not drawn, so an autotile lake is one outline without cross strokes at the quarters, merged rectangles of plain tiles and one-way tiles follow the same rule, the bridges from splitting concave outlines disappear and a piece doubled on two layers is outlined once; fill and collision stay as they were, and the result is cached per tilemap and rebuilt only when its collision data changes (
#173
)
2026-09-19 12:54:53 +02:00
property-ports.test.ts
State machines: asset, tab, State branch and Set state nodes (
#51
)
2026-09-12 18:00:23 +02:00
raycast.test.ts
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
)
2026-09-20 18:54:12 +02:00
resolve.test.ts
Transform: scale, rotation and mirror as properties, world transform as a matrix chain, oriented colliders (
#76
)
2026-09-13 17:25:06 +02:00
scope.test.ts
A shared value belongs to no behavior, so it becomes an asset of its own (
#91
)
2026-09-14 18:05:36 +02:00
self-transition.test.ts
Several transitions between the same pair of states: make transition onto a state that is already connected in that direction adds another transition, which gets its own name right away (Transition 2, Transition 3, skipping names that already leave the state), so two unnamed transitions between one pair never exist; renaming is refused when another transition between the same pair in the same direction already carries the name, the empty name included, while equal names to other targets are only warned about as before; the behavior graph keeps Already connected; self transitions may repeat too, each further loop drawn as a ring around the previous one with the preview showing the next ring, and transition labels that would cover another one slide along their own edge until they are free (pure function freeLabelSpot); test hook transitionLabelAt also returns width and height, e2e 26e6 (
#162
)
2026-09-19 16:30:52 +02:00
serialize.test.ts
Plugins belong to the project, and they load before it: the project file gained the field plugins: ["demo-plugin.js"] (default empty, no formatVersion bump), the Plugins tab's switch writes into it as an ordinary undo step (SetPluginActiveCommand) instead of into a server-wide plugins.json, and the start order is turned around - startPlugins only fetches the list of files, then editor.boot() runs and ProjectStore.load(id, prepare) hands the raw JSON through before it is deserialized, where pluginsOf(raw) reads the list and PluginManager.syncTo() activates exactly those; that is the point of the issue, because what a plugin registers has to stand there before normalizeGraph would throw it away as unknown (
#126
,
#127
), a project switch first stops what the new project does not name and then starts what it does, a file the server does not have is passed over silently, and after signing out the manager is dead so a late event cannot start anything in a torn-down session; on the server plugins.json, PluginStore::setActive, isActive, the route PUT ?r=plugins/{file} and the 403 for inactive modules are gone and GET ?r=plugins only answers {file,bytes,updatedAt}, which makes every plugin file fetchable - the consequence accepted in
#117
, written down in the wiki for
#115
and
#116
; publishPlugins(root) only copies files now, the seeder switches the demo plugin on in the fixture project, and the Plugins tab carries a puzzle piece as its icon while only a plugin's own tab still goes without one (the knob and the socket are cubic curves, not arcs, which Pixi's parser draws as a spike after an L); 3 new e2e checks in module 38 and 5 new unit tests cover the project as the owner of the switch, its undo, the order, pluginsOf against crude input and an unknown file name (
#119
)
2026-09-20 14:24:27 +02:00
shaders.test.ts
Three stores, three importers and three pickers that differ only in their type are one of each (
#85
)
2026-09-16 17:38:54 +02:00
shapes.test.ts
The scene view outlines a tilemap's collision only along its outer edges: where two pieces meet, their shared stretch runs once in each direction and is not drawn, so an autotile lake is one outline without cross strokes at the quarters, merged rectangles of plain tiles and one-way tiles follow the same rule, the bridges from splitting concave outlines disappear and a piece doubled on two layers is outlined once; fill and collision stay as they were, and the result is cached per tilemap and rebuilt only when its collision data changes (
#173
)
2026-09-19 12:54:53 +02:00
sounds.test.ts
A game that is more than one scene needs a way from one to the next, so the graph can load one (
#39
)
2026-09-15 13:24:26 +02:00
state-machine.test.ts
A machine that arrives as a value can still be commanded, so a name may name its state (
#58
)
2026-09-16 09:08:57 +02:00
struct-shape-ports.test.ts
State machines: asset, tab, State branch and Set state nodes (
#51
)
2026-09-12 18:00:23 +02:00
struct-variable.test.ts
Drop the per-list item declaration; every item carries its own kind
2026-09-12 13:35:02 +02:00
text-and-pointer.test.ts
Replace the property nodes with one Properties node
2026-09-11 18:40:47 +02:00
texture-commands.test.ts
A border drawn in pixels is as wide as the texture says a pixel is, so a small sheet can frame a large sprite (
#96
)
2026-09-16 18:09:43 +02:00
texture-kind.test.ts
State machines: asset, tab, State branch and Set state nodes (
#51
)
2026-09-12 18:00:23 +02:00
texture.test.ts
The texture field only offers pictures: each texture with its frames, no animations any more; an animation id left in an older project shows as a missing reference (
#143
)
2026-09-18 12:14:01 +02:00
tile.test.ts
The loader keeps no migrations any more: an asset of the old type autotile (before
#131
) is an unknown type and dropped, and a tilemap layer with the cell map of the first version (before
#134
) loads empty; every project is saved in the current format, and the two tests now pin that the old shapes are not read (
#137
)
2026-09-18 15:12:07 +02:00
tilemap-cells.test.ts
The loader keeps no migrations any more: an asset of the old type autotile (before
#131
) is an unknown type and dropped, and a tilemap layer with the cell map of the first version (before
#134
) loads empty; every project is saved in the current format, and the two tests now pin that the old shapes are not read (
#137
)
2026-09-18 15:12:07 +02:00
tilemap-collision.test.ts
Move with collision reacts along the contact normal: it travels to the surface, stays flush and slides the rest of the move along it with Coulomb friction (the mean of both colliders, a tile counts as 0.5), a circle now rolls over an edge instead of catching on it, pushed dynamic bodies move off along the normal, Move gains the outputs hit, normal x and normal y, an object without its own collider moves freely, and editor run and play share the same path through Solids; contact point and normal come from our own f64 geometry (contactBetween), because Rapier's castShape lands 3e-6 px inside the wall in f32 (
#150
)
2026-09-18 21:52:11 +02:00
tilemap-fill.test.ts
The tilemap panel gains Fill and Pick: Fill floods the connected cells of the active layer that carry the same tile (edge neighbours only, bounded by the grid) as one undo step, Pick takes the tile of the clicked cell on the active layer, and holding Alt picks while any tool is active (
#133
)
2026-09-18 12:31:19 +02:00
tilemap-resize.test.ts
A placed tilemap grows and shrinks at every edge: the new GRID EDGES block in the inspector adds or removes one column or row at Left, Top, Right or Bottom as one undo step, shifting the cells of all layers and moving the placement through its own matrix so the content stays in place on screen; removed painted cells are kept outside the grid and return when the edge grows again, and the Columns and Rows fields of a placement keep the content in place too (
#136
)
2026-09-18 13:58:03 +02:00
tilemap.test.ts
The active tilemap layer can be renamed from the painting section, and a Show setting fades or hides the other layers in the scene view while the tilemap is selected (
#138
)
2026-09-18 10:56:32 +02:00
time.test.ts
One clock under everything that moves by itself (
#90
)
2026-09-14 09:31:53 +02:00
transform.test.ts
A source wears an icon without becoming decoration (
#89
)
2026-09-14 08:25:49 +02:00
variable-node.test.ts
State machines: asset, tab, State branch and Set state nodes (
#51
)
2026-09-12 18:00:23 +02:00
variable-ports.test.ts
A shared value belongs to no behavior, so it becomes an asset of its own (
#91
)
2026-09-14 18:05:36 +02:00
vec.test.ts
Refactoring round 5 covers the code since round 4 without changing behaviour: four identical copies of dot() and three of "nearest point on a segment" stood in the collision math, in the hit area of the collider outlines and in the polygon handles, so they moved into the new module src/core/util/vec.ts; the warning line, the busy state, info() and the caret reset were written out line by line in both the sign-in screen and the password dialog and are now FormStatus; the closing row "Cancel plus a confirming button, optionally one on the left" was built by hand in FormDialog, FilePicker, OpenProjectDialog and PasswordDialog and is now dialogButtons(); the header bar was drawn by the toolbar, the TabHeader and the Breadcrumb each on its own and is now drawHeaderBar(); AddColliderCommand and AddRigidbodyCommand share a base, each subclass only builds its own asset; Editor.ts derives the panes from assetEditors instead of repeating eight entries and clamps its splitters with clamp(); jscpd over src falls from 0.19 % to 0.14 %; dead code is gone - listScenes() has been orphaned since the Scene picker fell away (
#183
), DEFAULT_FILTER (
#152
) was never used, the two test hooks projectPickerRowAt/rowPosition and particleCompiles are read by no tool, and four functions lost an export they did not need; seven comments that told history ("used to", "no longer", "since #...") describe the state of things now, and that this changed nothing was proved by a rebuild - the bundle is md5 identical, b4ad0dfe... before and after; the README said Logout sits at the far right of the toolbar, which has been the Account menu since
#182
, and wiki/Architecture.md was missing src/core/util/ altogether; 4 new unit tests cover vec.ts, and switching the clamp on the segment off makes exactly one of them fail while none of the 76 neighbouring tests notice, so that clamping was covered nowhere before (
#85
)
2026-09-20 17:24:42 +02:00
walk.test.ts
Move with collision reacts along the contact normal: it travels to the surface, stays flush and slides the rest of the move along it with Coulomb friction (the mean of both colliders, a tile counts as 0.5), a circle now rolls over an edge instead of catching on it, pushed dynamic bodies move off along the normal, Move gains the outputs hit, normal x and normal y, an object without its own collider moves freely, and editor run and play share the same path through Solids; contact point and normal come from our own f64 geometry (contactBetween), because Rapier's castShape lands 3e-6 px inside the wall in f32 (
#150
)
2026-09-18 21:52:11 +02:00
world-sync.test.ts
Remove exports and methods nothing uses any more
2026-09-11 20:15:49 +02:00