393caadc9d4cc3b07581a0037fa7f1e9d7b98c0a
368
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
393caadc9d |
Head the groups of the inspector with bands that fold (#290)
The groups of the inspector had a small grey label as their title, in the same grid as a property row, so with many rows one group hardly stood apart from the next. Now every group has a band as its head, from edge to edge of the panel, and a click on it folds the group. - A band runs across the padding of its column in the header colour, with the title and a mark that says whether the group is open. The titles stay as they are, as answered in the issue. - A click on a band folds its group: the rows go, the band stays and the next band moves up against it. A control in the band (Open, + Variable, + Property) does its own job and folds nothing. - A placed object has a group SWITCHES under its name, asset and parent: Enabled, Keep on scene load and the switches of a behavior. An asset has SWITCHES and PROPERTIES, and a group without rows is left out. - The same bands head the tilemap panel, the Variables tab, the side of the Pixel art tab and the shape list of vector art. A list with a title (Layers, Frames, Shapes) folds with a click on its header, clear of its plus. - ui/sections.ts holds the rules as pure functions, SectionStack is the column and SectionHeader the band. Every title of the inspector goes through addTitle or sectionHeader in inspector/host.ts. Decided here: - A fold is kept for good, in localStorage of this browser. The issue asked whether for the session or for good and the answer left it open. - A fold is remembered by title, per panel. PROPERTIES folded once is folded on every object, asset and node. A number in a title does not count. - A behavior asset has all its rows under SWITCHES. A Variables asset and a state machine get a group SCOPE. - A greyed out control in a band takes the click too. Without that the click fell through to the band and folded the group. - The Curve tab has no column of this kind and is unchanged. Found on the way: - The inspector of a sprite is taller than the 800 pixel window of the checks now. The hooks that hand out a row scroll it into view first, and module 28 scrolls before it looks for a row by its text. Tests: - Gate: 202 files, 3008 tests (2982 + 26), exit 0 - e2e: no complete run for this state. Modules 28 to 61 passed alone (749 checks). A full run was cut off in the accounts module at 1492 passes without a failure, the modules after it and the smokes are missing. The sum of 1639 (1635 + 4) is reckoned, CI measures it. - 26 unit tests in tests/editor/sections.test.ts - Four e2e checks with a real mouse, three in module 02 and one in module 48 - 25 unit counter-probes all fail tests, run on stein2 - Screenshots of the bands looked at before the last build, none after it Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
c5ba21533d |
Pick a link on its line and delete it from there (#289)
A link could only be removed through the menu of its port, and the line itself took no input. A transition of a state machine could only be deleted in the list of the inspector. Now the line is the handle, in a behavior's graph and between states alike. - A click on a line selects that link and draws a halo behind it. A press counts within 6 screen pixels of the line, and where two cross the one drawn last wins. - Ctrl-click or Shift-click adds a link or takes it away. The rubber band takes every line it crosses, into the same selection as the nodes and boxes it caught. - Delete or Backspace removes what is picked as one undo step. A right-click on a line offers Hide link and Delete link. - The inspector shows a picked link: the node and port at each end and the type it carries, with Hide link and Delete link below. For several it says how many. - The arrow of a transition is picked the same way, on its name too. Delete or a right-click with Delete transition removes it. - The menu of a port is unchanged, Disconnect included, as answered in the issue. - editor/graph/edgeHit.ts holds the hit test as pure functions, and editor/inspector/edgeSection.ts builds the section. The selection has a new kind edge, and node and annotation carry edgeIds. Decided here: - A hidden link has no line and is never picked this way, also while it is shown on loan. Its mark at the port stays its handle. - The menu of a line speaks about the whole selection, like the menu of a box. With nodes or boxes held alongside it reads Delete N items. - Hiding picked links lets go of them, since no line is left. - A link wins over the box it crosses, and a mark wins over a link. - A transition has no Hide entry. - With nodes or boxes in the selection the inspector keeps showing the node or box, not the link. Tests: - Gate: 201 files, 2982 tests (2967 + 15), exit 0 - e2e, full run: 1635 checks passed (1629 + 6), exit 0 - Smokes: 24 / 19 / 21, exit 0 - The full run was done for exactly this state. - 15 unit tests in tests/editor/edge-selection.test.ts - Six e2e checks with a real mouse, block 6z-xi in module 06 and 26e7 in module 26 - 12 unit counter-probes all fail tests, run on stein2 - Three browser counter-probes fail, the build went through each time: no hit on the line, no halo, a band that skips lines - Screenshots of a picked link and a picked transition looked at before the last build, none after it Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
0f85476b93 |
Add points by double-click and delete the picked one (#288)
Four editors edit points, and all of them put a point down on a single press on the line and took one away on a double-click. A stray click added a point, and there was no way to see which point a key would hit. All four now work the same way: the Curve tab, the Vector art tab, a polygon collider in the scene view and a frame's collision outline in the Texture tab. - A double-click on a line, edge or segment puts a point there, in the Curve tab on free space too. The point is picked and stays in place. It can be dragged with the next press, as answered in the issue. - A click on a point picks it, and it is drawn filled. A double-click on a point takes nothing away any more. - Delete or Backspace takes the picked point away. Without a picked point the key removes the shape, the object or the outline as before. - A right-click on a point picks it and opens a menu with Delete point. In the Curve and Vector art tab the four point types stand below it. Alt-click stays. - A polygon keeps three corners and a curve keeps its ends. The entry is greyed out then. - The pick ends with a click beside the points, with another object, shape or frame, and after the point is gone. - editor/pointGestures.ts holds the rule as a pure function, and editor/pointMenu.ts builds the menu for all four. Decided here: - A single click on a line no longer adds anything. In Vector art it drags the shape, on a polygon it drops the pick, and in the Texture tab it makes that outline the active one and drops the pick. - In the Texture tab a click inside the frame still appends a corner, and that corner is picked. Delete takes the last corner first. To remove the whole outline, click one of its edges before. - The point the Pen put down last is picked, so Delete takes it and not the shape. - Delete hits the picked point also when the tab has lost the keyboard focus, after a click in the shape list or after the menu. - A vector shape that loses its last point is removed whole. Before, an empty shape stayed in the list. Findings: - Fixed: the new hint line in the Texture tab was cut off at the edge. The shorter text was not looked at in a picture again. - Not fixed: in the scene view the collider's name label covers a corner that sits near the centre. That was so before. Tests: - Gate: 200 files, 2967 tests (2944 + 23), exit 0 - e2e, full run: 1629 checks passed (1625 + 4), exit 0 - Smokes: 24 / 19 / 21, exit 0 - After the full run an unused parameter and a comment were tidied. For that final state the gate and e2e 42,47,53 (105 checks) ran again, the full run did not. - 23 unit tests in tests/editor/point-gestures.test.ts, for the rule and for each of the four editors - Four e2e checks with a real mouse, two in module 42 and one each in 47 and 53. The existing checks for adding and removing a point use the new gestures. - 20 unit counter-probes all fail tests, run on stein2 - Two browser counter-probes fail, the build went through both times. Without the Delete wiring the key removes the collider. Without the right-click hook the menu is missing, and that run stops in module 42, so 47 and 53 were not reached with it. - Three screenshots looked at, the picked point is filled. None of the Vector art tab. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
1aa824ecb4 |
Keep objects through a Load scene with a switch (#165)
A Load scene destroyed every object of the old scene. A player, a HUD or a music object had to be built again in each scene, with its state pushed through shared variables. A top-level object with the new switch Keep on scene load now travels into the next scene as the same entity, with everything below it. - The switch is a common property right under Enabled, off by default, on the asset and on a placed object. The inspector shows the row on top-level objects of a scene only, as answered in the issue. - runtime/keptObjects.ts holds the rules as pure functions. keptEntities picks what travels, objectsToBring copies the stored objects as the run parents them, and bringObjects puts them into the next scene. - World.instantiate takes a handed entity as it is instead of making one. The next syncToModel then writes the run's values into the scene. - BehaviorSystem.release takes the running instances out before the old world is destroyed, and the next system adopts them. They hear no On destroy and no second On start, and their sounds play on. - PhysicsSystem.resume gives a dynamic body the velocity and spin it had. - Duplicates: the object brought along wins. The next scene does not build its own object with the same id, which is the way back into the scene the object came from. - Decided here: the next scene does not build a top-level object of the same asset that is kept itself either. The issue only names the id. With the id alone a level that places its own player would hold two after a swap. - Decided here: kept objects land behind the scene's own, the switch is no animation row, and collisions are found anew. A finding, fixed along with it: Load scene did not build a scene as it is stored, but as the run had left it. A moved object stayed moved and a destroyed one stayed gone. The way back into a scene needs a clean build. - GameRun copies a scene's objects and groups before it first builds it and puts them back before it builds it again. - GameRun.end puts every such scene back. The player calls it on stop, so spawned objects no longer stay for "Click to play again". - The consequence: an edit made in a scene during Play is gone the next time that scene is loaded. Tests: - Gate: 199 files, 2944 tests (2929 + 15), exit 0 - e2e, full run: 1625 checks passed (1619 + 6), exit 0 - Smokes: 24 / 19 / 21, exit 0 - 15 unit tests in tests/core/keep-on-scene-load.test.ts - Six e2e checks in module 35: the switch is set with a real mouse, the child has no row, the object is in the new scene, its counter still reads 1, its sound plays on, and the stored scenes are unchanged after Stop - Eleven tests with fixed row lists only count the new schema row - 16 unit counter-probes all fail tests, run on stein2. One did not fail at first (same id not left out), it has its own test now. - The browser counter-probe fails 4 of the 6 new checks - Not looked at in the browser: the switch as a port of the Properties node, and a released game with a kept object Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
3e8c88c66d |
Add a node that names the collision layer of an object (#211)
The issue asked for three nodes: Set layer, Set collides with and Layer of. A probe showed that the finding behind the first two is outdated. The Properties node of type Collider already writes both settings while the game runs, and both collision systems see the change at once. As agreed, only Layer of is built, and writing stays with Properties. - Layer of is a data node with the input object and the outputs name and number. The name is what the project calls the layer. - NodeContext.collisionLayerOf answers the number, and the new field collisionLayers on GraphLookup brings the names to the node. - layerHolderOf in solids.ts says whose layer counts. An object stands for its first collider child, a collider or tilemap for itself. - Decided here: a collider that is switched off counts too, because its layer is set either way. - Decided here: without a collider the name is empty and the number is -1. - The node is a read, not a run, so it answers at the moment it is asked. Right after a Set in the same flow it names the new layer. - The output number was not asked for. It fits the layer port of Properties as it is. Changing the layer during a run had no test at all so far. Five tests now hold it in both systems. Tests: - Gate: 198 files, 2929 tests (2914 + 15), exit 0 - e2e 1-6: 285 checks passed (06: +2 for block 6z-x) - Five unit tests for the change during a run: a mover is blocked, passes and is blocked again, a ray with a layer filter follows the change, and a dynamic body falls through the ground in Play - Six unit tests for Layer of, three against each system, and four cases in the node catalog - 6z-x finds the node in the palette, checks its ports and logs "Ghost" and "1" for the owner's collider in a Play run - Two counter-probes for the change during a run fail 1 and 4 tests, and only new ones, so those places were unguarded before - Six counter-probes for Layer of fail 2 to 3 tests each. All eight ran on stein2, not in the container. - The browser counter-probe fails 6z-x with "Layer 1" and "1" - The e2e sum of 1619 is calculated (1617 measured in CI run 276, plus 2). Neither the full suite nor the smokes ran for this state. - Not looked at in the browser: that the layer port of Properties shows the same picker as the inspector. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
d39bf83ea9 |
Pick the frame's outline when adding a collider to a sprite (#218)
"Add collider" on a sprite always made a box of the sprite's size, even when the sprite's picture is a frame that has a collision outline. The outline then had to be picked by hand as "Frame collision". - newColliderProps in colliderCommands.ts is the rule as a pure function. A sprite whose picture is a frame with an outline gets a collider with shape "frame" and that frame. Anything else keeps the box of the sprite's size. - The frame is the one the sprite shows when the collider is added, also when only this placement overrides it. - Decided here, the issue says nothing about size: a frame collider is as big as its frame, so a sprite drawn bigger or smaller with the same proportions gets the collider's scale set to match. - Decided here as well: a sprite stretched out of the frame's proportions keeps the box, because an outline cannot be stretched in one direction. Tests: - Gate: 198 files, 2914 tests (2911 + 3), exit 0 - e2e 8,42: 46 checks passed (42: +1 for 42e-ii) - Three new unit tests in tests/core/collider-parts.test.ts: the frame with undo, the scale with an overridden frame, and the cases that keep the box - 42e-ii goes through the context menu: "Frame collision" with scale 2 on the sprite wearing the wedge frame, a box on a sprite without a picture - Five unit counter-probes fail, 1 to 2 tests each. They ran on stein2, not in the container. - The browser counter-probe fails 42e-ii with shape "box" - The e2e sum of 1617 is calculated (1616 + 1). Neither the full suite nor the smokes ran for this state. - Vector art has no test of its own, it takes the same branch as a whole texture. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
d6171fd386 |
Preview pixel art and curve fields once per frame too (#274)
The previous commit left two findings open: in the pixel art tab the palette colour and the opacity of a layer still told the editor about every input, and so did the place fields of the curve tab. They now follow the same rule as the inspector rows. - PixelArtSide writes colour and opacity into the model on every input and requests the preview through a FramePreview. Both commits cancel a waiting preview first. - CurveCanvas.movePicked does the same for the t and v fields of the picked point. Showing another curve cancels a waiting preview. - Also fixed: when the colour picker ended on the colour it started from, the model was put back but nobody was told, so the previewed colour stayed on screen. commitColor now says so, as the opacity already did. The gain was not measured for either tab, only the behaviour is tested. Tests: - Gate: 198 files, 2911 tests (2907 + 4), exit 0 - e2e 47,48,53: 117 checks passed - Four new unit tests in tests/editor/frame-preview.test.ts: colour, opacity, curve fields and the return to the starting colour - Seven counter-probes fail, one test each. They ran on stein2, not in the container. - No new e2e check, the sum stays at 1616. Neither the full suite nor the smokes ran for this state, and there was no counter-probe in the browser. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
c4a9280140 |
Preview inspector edits once per frame, not per input (#274)
Scrubbing a number in the inspector and dragging a colour stop of a gradient told the editor about every input. Pixi repeats each pointer move shortly after, so both gestures paid for two previews per move. The issue asked to measure first and to build only from 10 % per move. The measurement showed about 50 %. - previewingHandlers still writes the model on every input but requests the preview through a FramePreview of its own. The commit cancels a waiting preview first. - The preview handler of the GradientBar in shapeSection.ts does the same. - Wider than the issue describes: the row handlers also serve typing, the arrow keys and the colour picker. All of them preview once per frame now. Measured on stein2, fixture project with 120 extra objects, 120 moves 8 ms apart, three runs each: - Scrub: 236 project events before, 51 to 61 after. 45 to 50 ms per move before, 22 to 27 after. - Colour stop: 238 project events before, 50 to 51 after. 44 to 49 ms per move before, 22 after. - One preview still costs 13 to 17 ms. That price per frame is what remains. Finding, not fixed here: in the pixel art tab the palette colour and the opacity of a layer still preview on every input (PixelArtSide.ts), and so do the head fields of the curve tab. Neither was measured. Tests: - Gate: 198 files, 2907 tests (2903 + 4), exit 0 - e2e, full run: 1616 checks passed (1615 + 1), exit 0 - Smokes: 24 / 19 / 21, exit 0 - Four new unit tests in tests/editor/frame-preview.test.ts, two for a row and two for a colour stop - One new e2e check in module 02: a held scrub gave 10 project events in 12 frames, and the value is there before the release - Four unit counter-probes fail, 1 to 2 tests each. They ran on stein2, not in the container. - The browser counter-probe fails the new check with 50 events in 16 frames Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
c1cc80bb45 |
Add nodes that ask whether an animation still runs (#233)
A graph could start, pause and stop an animation and hear it finish, but it could not ask in between whether one runs or how far it is. Two data nodes answer that now. - Animation playing takes an object and an animation and gives playing, progress and time left (s). It answers at the moment it is read: right after Play animation in the same pass it says on, and in the frame On animation finished fires it says off. - With Animation at None it answers for the running animation started last on the object. - Animation length gives the seconds of one pass, from the timeline and the frame rate alone. - The rule is a pure function in src/core/model/animation.ts (playerStatus, leadingPlayer). The nodes reach an animation's length through a new field animation on GraphLookup. - animationPort is exported so both nodes show the same picker as Play animation. Decided in the issue: a Once (reset) counts as running while it blends out, and progress and time left also hold for Loop and Ping-pong, at the place of the playhead. On Ping-pong progress falls again on the way back and time left counts to the next turn. Decided here, the issue says nothing on it: - A paused animation does not count as running. Progress and time left stand still. - One that Stop animation fades out does not count as running, and its time left is 0. - Time left of a Once (reset) includes its blend out. While it blends out progress is 1 and time left is the rest of the fade. Finding, not fixed: on the node the name of the chosen animation overlaps the output label progress in the same row. That comes from the fixed node width in NodeView and was seen in the Fit view only. Tests: - Gate: 198 files, 2903 tests (2891 + 12), exit 0 - e2e 1-6: 282 checks passed, 06 has +2 for block 6z-ix - Nine unit counter-probes fail, 1 to 4 tests each - The browser counter-probe fails 6z-ix with ["false","false"] - e2e total 1615 is computed, not measured in a full run. The smokes were not run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
26cc6324af |
Stop resolving every object in every frame of a run (#287)
World.syncToModel runs after every frame of a run and resolved each object of the scene anew to compare it with its entity. With 222 entities that was most of the Editor line in the performance panel, also for entities that are never drawn. - The world keeps per entity what the model said when it last read the object (syncFromModel, spawn) or wrote it. A frame compares the entity with that and resolves nothing while both agree. - A place that differs is written as three numbers. - A value that differs is written as an override, and that one object is resolved again. An override can bring rows of its own, such as a sprite's shader. - The objects of entities that left the run are dropped only in a frame in which one left. World.remove notes that. - Editing during a run works as before: the editor has the model read again on every project event, which renews what the world keeps. One difference: a write to the model that goes past the editor and does not have it read again now stays. The next frame used to overwrite it with the entity's state. No code does that, during a run only the world writes. Measured on stein2 with a copy of the reference project of #284, ms per frame: syncToModel alone 0.92 to 0.12 (Node, 600 frames), the Editor line in the browser 1.14 / 1.20 / 1.29 to 0.25 / 0.25 / 0.27. Physics and Drawing are unchanged. Tests: - Gate: 198 files, 2891 tests (2887 + 4), exit 0 - e2e, full run: 1613 checks passed, exit 0. That measures the total that was only computed since #273. - Smokes: 24 / 19 / 21, exit 0 - Four new unit tests, no new e2e check: 60 unchanged frames resolve nothing, an edit during the run arrives and stays, an override with rows of its own is seen with them, container content in an editor run is written into the instance edits once - Seven counter-probes fail, 1 to 4 tests each Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
cfd288c4b7 |
Halve the frame time of a playing scene with a tilemap (#284)
The performance panel showed Physics and Editor as the large parts of a frame in a scene of 222 entities. Nearly all of it came from a tilemap that only stands there. - Whoever writes cells in place now says so with cellsChanged. Only then does cellsVersion compare the content. The comparison ran about 60 times per frame and was the largest single item at 14 percent. - The four writers call it: the painter during and at the end of a stroke, PaintTilesCommand on execute and on undo. A new writer that forgets it stays invisible. - The game view is drawn once per frame in play. With an animated tile in view it was drawn twice, once by the run and once by animate(). animate() now gets the redraw count from the start of the tick. - TilemapField builds the geometry of a mesh only when its source changes: the cells, the tile size, the origin or the image. For that groupByTile and layerRows return the same objects for the same list. - PhysicsSystem skips a tilemap whose source and switch are unchanged. The switch is kept because a map that starts switched off would otherwise have dead colliders once it is switched on. - World.syncToModel looks entities and assets up in a map per pass instead of a linear search per object. Measured on stein2 with a copy of the reference project, ms per frame: Physics 2.64 to 0.97, Drawing 6.89 to 2.56, Editor 1.95 to 1.31. Left open: syncToModel still mirrors every entity every frame. That is its own issue. Tests: - Gate: 198 files, 2887 tests (2883 + 4), exit 0 - e2e 1-17,28,33,39-43,59: 810 checks passed, 40 has +3 - Smokes: 24 / 19 / 21, exit 0 - Three unit tests wrote cells directly and now call cellsChanged - Eleven counter-probes fail. Two did not at first: the stroke needed a check of its own, the physics test has to start with the map switched off. That last one was repeated on stein2 only. - e2e total 1613 is computed, not measured in a full run Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
6491888f56 |
Stop offering private variables as new animation rows (#270)
A behavior variable switched private is hidden from the placed object and from the Properties node, but the row dialog of an animation kept offering it. Rows that exist already stay as they are, as decided in the issue. - offeredDefs in src/core/model/animation.ts is what the dialog offers now: what the target can animate, less private variables and less what another row animates already. The filter moved there from RowDialog.ts, so it can be tested without the editor. - animatableDefsOf is unchanged. The label and the runtime read it, so a row on a variable that went private keeps playing and keeps its label. - Deviation from the proposal: under Change target the row's own variable stays on offer, private or not. The dialog would open with no property and a disabled button otherwise. Other private variables are not offered there either. - The command does not refuse a private variable. Only the dialog does not offer it. Finding, not fixed: e2e 56 fails when it runs alone, in 56a-ii from #257 (typing "9" did not arrive: focus is BODY). With 55 before it the module passes, so a full run does not show it. It was not measured on HEAD without this change. Tests: - Gate: 198 files, 2883 tests (2882 + 1), exit 0 - e2e 55,56: 27 checks passed, 56 has +56e - e2e 25: 31 checks passed, it uses the same dialog - Three unit counter-probes each fail the new test - The browser counter-probe fails 56e, mood shows up in the dialog - e2e total 1610 is computed, not measured in a full run Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
5e21d473a4 |
Fix references into nested placements and on asset delete (#285, #286)
Two findings from #269. A reference from a container into a placement nested in it was dead from the start, and deleting an asset left references to the objects it took along showing "(missing)". - A reference is stored the way its holder shows the object. In a container tab that is q~x for content of a nested placement q. - referenceCandidates translates it when the container is placed: in placement r, q~x becomes r~q/x. pathOfId and idOfPath in instance.ts do the mapping, objectChoices offers the same spelling the other way. The rule holds at any depth. - Storing q/x as proposed in #285 was dropped. The container tab would then have had to translate its own ids. - No migration. All projects and snapshots hold three references, none of them with ~ or /. - Create container lets a reference to content of a member placement follow it. It was left dead before. - Paste lets such a reference point into the copy. It kept pointing at the original before. Both go through the new renamedReference. - Delete asset records what every holder shows before the cascade and clears every reference to an object that is gone afterwards, with dropReferencesToGone from #269. The row shows None, undo restores the references (#286). Tests: - Gate: 198 files, 2882 tests (2873 + 9), exit 0 - Unit object-references: 26 passed (17 + 9) - e2e 1-17,57: 566 checks passed, 57 has +57i and +57j - Six unit counter-probes fail between 1 and 3 tests each - The browser counter-probe fails 57i and 57j as described in the issues - e2e total 1609 is computed, not measured in a full run Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
214218d1b2 |
Clear references to a template object in every scene on delete (#269)
Deleting an object in a container tab only cleared references inside the template. Placements in scenes and other containers kept their instance edit and showed "(missing)" in the inspector. - shownObjectIds records which objects every holder shows before the delete. dropReferencesToGone compares that with the state after it and clears each reference that no longer resolves. - A reference is resolved from its own place with referenceCandidates, the same way the runtime does. One rule covers instance edits, nested placements and full ids like p~crate. - A cleared instance edit is dropped when it now says the same as the template's own value. Without such a value a marked None stays, as if picked by hand, and Reset hands the row back to the template. - All cleared references come back with a single undo. - References that were already dead in saved projects stay untouched. Findings, not fixed here: - A reference from container D into a nested placement q is stored as q~<id>, but the copy is named r~q/<id> in every placement of D. The runtime finds nothing, so that reference is dead from the start. - Delete asset removes placed objects by cascade but does not clear references to them. Tests: - Unit object-references: 17 passed (13 + 4) - Gate: 198 files, 2873 tests, exit 0 - e2e 1-17,57: 564 checks passed, 57 has 13 (+1 for 57h) - Five unit counter-probes and one browser counter-probe fail as expected - e2e total 1607 is computed, not measured in a full run Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
0f6e628616 |
Check the declared type when a graph writes a typed variable (#271)
A list variable with an item type only kept its shape in the editor. A graph could write any list into it at runtime, and the List node had no type at all. Now the declaration decides what a write may store. - Writing a list into a typed list variable leaves out every item that does not have exactly the fields of the type. The rest is written and the console names each item that was left out, like Add item does. - The same rule holds for typed list fields inside a struct variable and for lists inside typed items, at any depth. - A typed struct variable refuses a struct with a field the type does not have, and a value that is no struct. A struct with missing fields still merges in as before, so a partial Pack keeps working. - The check runs in the Variables node (own variables and Variables assets) and in the Properties node when it sets a behavior variable of another object. - Ports learn their declaration through GraphLookup: what a variable declares about its type, and which behavior owns a graph. The rule itself lives in the new src/core/graph/typedValues.ts, fitsType moved there from SetItem. - The List node gets a Type setting. With a type its items are shaped and marked, follow the struct asset, count when the type is deleted and offer Split into fields in For each. The mark never reaches the running game. - A type on a List node holding something other than structs is refused with a console line. - Setting the List node back to None removes the marks, unlike a list variable. A node list has no per-item type row, so marked items could never be freed there. Undo brings everything back. - tests/core/kinds.test.ts looked for the value as the first parameter of a literal. Type now comes first on the List node, so the test finds the value by its key. Tests: - Gate: 198 files, 2869 tests (2856 + 13), CHECK EXIT 0 - e2e 55: 19 checks (+55j write at runtime, +2 for 55k List node) - e2e 54: 16 checks, smokes 24 / 19 / 21 - Five mutations, each fails between 1 and 4 tests - e2e total 1606 is computed (1603 + 3), no full run Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
f2282a81fb |
Stop loading the bold face the editor never draws in (#273)
The editor waits for every face in FACES before it starts. Source Sans 3 in 700 was one of them, but nothing in src/ sets a weight above 600, so it cost a download and a wait for nothing. - fonts.ts loads Source Sans 3 in 400, 500 and 600 plus Source Code Pro 400. The 700 file (15 596 bytes) is gone from src/ui/fonts. - A search for bold, font-weight and HTML headings in src/, web/*.html and the player found nothing that would fall back to 700. - fonts.test.ts now checks both ways. A loaded Sans face must be set somewhere in src/, and every .woff2 in the font folder must be in FACES, so a stray file the glyph test would still read shows up too. - The weight scan moved into usedWeights(), since two tests need it. Tests: - Gate on melektaus-atx: 197 files, 2856 tests (2855 + 1), exit 0. - e2e 1-14: all passed, 492 checks, loaded faces ["400","500","600"]. - e2e 14 alone fails its two tab width checks, as known: it needs the documents 7-13 open. The font check was green there as well. - dist/assets holds no 700 file after the build. - Counter check: loading 700 again with its file fails the new "loads no face the editor never sets". - Counter check: the file back without a FACES entry fails "... and no other" and names the file. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
f14c4ecb77 |
Show the frame rate the measured times allow in the overlay (#275)
The fps line of the performance overlay stops at the display's rate, 60 on most screens. It does not tell how much room a game has left. The new top line answers that from the times the overlay measures. - possibleFps in src/editor/frameMeter.ts is 1000 ms over the sum of the five areas, rounded down. It has no number while nothing took any time, and the overlay shows a dash then. - The editor's share is in the sum, because it costs time while playing in the editor. A released game can do a little better. - It is an upper bound. The GPU's time is not measured and not in it. - e2e 59e reads the lines one further down and checks the new line against the times it measured itself. Tests: - Unit frame-meter: 11 passed (+2 for possibleFps) - Gate: 197 files, 2855 tests, CHECK EXIT 0 (2853 + 2) - e2e 59: ALL PASSED, 19 checks (+1), on melektaus-atx - Counter-check: possibleFps without the editor's share fails the unit test - The e2e total of 1602 is counted (1601 + 1), not measured Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
77f4c2c1a3 |
Show entities and time per area while playing in the Game View (#275)
A game that stutters gave no hint where the frame goes. The Game tab now has a Performance switch. It shows a small overlay in the top left corner of the Game View while a game runs: fps, the number of entities and the mean milliseconds per frame for each area, averaged over half a second like the fps counter. - src/editor/frameMeter.ts holds the pure rule. FrameMeter sums the areas per frame, closes a window every 0.5 s and formats the lines. - World.timeSystem times every system the world ticks. PlaySession maps the systems to Behaviors, Physics and Animation and sets the hook again after Load scene. - Animation has its own line. Counting the AnimationSystem under Behaviors would have skewed that number. - Drawing is the build of the game picture plus app.render(). It is main thread time only. The GPU work after it is not in the number, and app.render() draws the whole canvas including the panels. - Editor is the rest of the editor tick after the systems and the game picture: mirroring into the model, panels and console. - The overlay lets clicks through to the game. It shows dashes until the first window closes. The switch is off by default and is not saved. The released game (#266) runs without a meter. Tests: - Unit frame-meter: 9 passed. - Gate on melektaus-atx: 197 files, 2853 tests, CHECK EXIT 0. - e2e 59 on melektaus-atx: ALL PASSED, 18 checks (13 before, +5 in 59e with a real mouse on the switch). Run twice. - Three mutations were caught: the editor share not subtracting the systems (2 unit tests), systems running in pause not reported (1 unit test), the overlay getting no times (59e). - The e2e total of 1601 is computed (1596 + 5), not measured. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
ded71a92c9 |
Pick the target of an object variable in the Scene View (#276)
Choosing the target of an object variable meant finding its name in a dropdown. With many objects, or several with the same sprite, the name alone does not say which one it is. The eyedropper lets you click the object where you see it instead. - Every object dropdown gets an eyedropper button next to it. This covers variables, struct fields and list entries, in the Inspector and in the Variables tab. - The button opens the Scene View or the container tab the object lives in. A strip above the view names the field and the current candidate and offers Take and Cancel. The cursor is a crosshair. - A click picks a candidate and writes nothing yet. The candidate gets its own cyan frame, the selection stays on the object with the variable. - Clicking follows the selection rule. `pickOn` in `src/viewport/pick.ts` uses `pressOn` and only counts objects the dropdown offers. A click on empty space keeps the candidate. - Enter or Take writes through the same handlers as the dropdown. That is one undo step, or none if nothing changes. - Esc, Cancel, a right click in the view or a second click on the button cancel without writing. So do a tab switch, another selection, undo, redo, play and loading a project. - After Take or Cancel the editor goes back to the tab the pick came from. - While the eyedropper runs, the view does not drag, marquee, paint, show handles or open its context menu. Panning and zooming stay. Found on the way: - Started from the Variables tab, the click missed the object. The Scene View is hidden while that tab is open, and the layout skips hidden children. The strip appeared then, the view moved down by 32 px but kept its old height. `Editor.display()` now lays the row out again after showing it. Tests (on melektaus-atx): - unit pick and object-pick: 27 passed, 13 new - gate: 196 files, 2844 tests, CHECK EXIT 0. Two more come from svg-icons, which checks every icon twice. - e2e 57: ALL PASSED, 12 checks, 5 new (57e to 57g) - Counter-checks work. Without the layout fix 57g fails. Picking objects the dropdown does not offer fails 2 tests. Writing on cancel fails 2 more. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
6d4247ebf8 |
Paste where the view shows it, editor offsets included (#263)
Paste mixed two frames. The pointer comes from the Scene View, which draws every object moved by the editor offsets of it and its parents. Paste turned that point into a local position through the parents' plain world transform, so the copy landed beside the pointer by the offset of the parent it went into. Without a pointer, an original with an offset of its own had its copy placed beside its game position, which the view does not show. - A copied root now remembers where the view showed it (`ClipRoot.shown`, from `editorTransform`) instead of its world position. - New `editorLocalPositionFor` in `properties/transform.ts` turns a shown point into a local position through the parent chain with the editor offsets, as the view draws it. Paste uses it. - The copy itself still gets no editor offset of its own, as since #240. In the game it stands where it was drawn, minus the offsets of its parents. - Re-parenting under a parent with an offset still keeps the game position, so the object jumps in the view by that offset. That is a different rule from paste and is left as it is. Tests: - Unit (clipboard, groups, references, transform): 50 passed, 2 new. - e2e 51 on melektaus-atx: ALL PASSED, 12 checks, 1 new (51f). - Gate on melektaus-atx: 195 files, 2829 tests, CHECK EXIT 0. - Counter-checks: without the paste fix both new unit tests fail (copy at 540/470 instead of 500/500) and 51f fails by exactly the 60/-40 offset. With the root remembering its world position again, the own-offset unit test fails. - No full run. The e2e total of 1591 is 1590 plus 1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
ffedd81803 |
Read many pixels from one render in the e2e checks (#283)
On the WSL runner every __gm.pixelAt costs about 7 ms, three times as much as on stein2. Each call renders the whole stage into a fresh render texture and stalls until the GPU hands one pixel back. pixelRow and several checks read hundreds of pixels that way. Module 05 spent 36.8 of its 40 s on these reads, module 04 11.3 of 20 s. - stagePixels in src/ui/render.ts renders once around all points and reads them in one go. Only whole pixels at resolution 1 take that path. Anything else stays point by point. - The hook __gm.pixels(points) exposes it, and pixelRow uses it. - The loops in e2e 04, 05, 14 and 44 read their grids in one call. - The old comment in pixelRow said a wider frame comes back resampled. That is not true. The colours match exactly, so the comment is gone. Measured on the RTX (d3d12) and on stein2 (Intel, Vulkan): diff 0 at 10 680 points on each GPU. One screen row takes 4 to 18 ms instead of 2.4 to 9.9 s. The six affected modules need 32 s instead of 139 s under WSL, faster than on stein2. The rest of the gap is in Chromium's GPU process. perf puts three quarters of its time in the drivers (NVIDIA D3D12, Mesa d3d12, dxg). Four browser and Mesa settings were measured, none helped. Tests: - ./check.sh on melektaus-atx: 195 files, 2827 tests, CHECK EXIT 0 (+4) - e2e 1-16,30,37,44,58 under WSL: ALL PASSED, 587 checks - counter-check: a wrong index in stagePixels fails the unit test Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
9b5eeaab57 |
Empty a deleted tile's cells on every tilemap (#268)
Deleting a tile left the cells painted with it in the tilemaps. They drew nothing, blocked nothing and did not show in the delete dialog, but still read as a tile for "Tile at" and stopped a fill. - DeleteAssetCommand empties the cells of a deleted tile on every layer of every tilemap in the same step. Undo puts the tile and its cells back together. - The delete dialog counts them, for example "3 cell(s) on 1 tilemap(s) are painted with it and become empty." (cellsPainted). - presentTile treats a cell naming a missing tile as empty. "Tile at" (World.tileAt) and floodFill use it, so cells already in existing files read and fill as empty. There is no migration. - The brush, the rectangle and the fill check the tile in hand. A tile deleted after it was picked no longer paints new invisible cells, the editor says "Choose a tile to paint with." instead. - Unchanged on purpose: the picker still reports such a cell as naming a tile that no longer exists, and erasing removes it. Tests: - ./check.sh: 195 files, 2823 tests, CHECK EXIT 0 (+6). - GM_STEPS=39,40: ALL PASSED, 100 checks (4 new in 40t). - Full e2e sum is now 1590 (1586 confirmed by CI on round 8, plus 4). - Counter-checks, all fail as expected: 5 in unit tests (delete without emptying, undo without cells, fill and "Tile at" without presentTile, wrong map count) and 3 in the browser (dialog without count, delete without emptying, brush without the check). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
14972d057f |
Refactoring round 8: hide data files, safe saves, Esc as Cancel (#85)
Recurring cleanup round over the code since round 7 (
|
||
|
|
bc688f3efc |
Refactoring round 7: split BehaviorSystem, fewer copies (#85)
Recurring cleanup round over the code since round 6 (
|
||
|
|
dded62b9f9 |
Let the example-game smokes read texts in the game again
Since
|
||
|
|
cab1c0177f |
Show only the game view in fullscreen, in the editor and the link (#267)
A game is easier to judge and to play without the editor around it. The Game tab and the game link now both have a button that puts the game view itself into fullscreen, not just the browser window. - The Game tab header has a fullscreen button on the right. It hides toolbar, panels, tab headers and console and keeps the zoom from the picker. A running game keeps running. - A corner button at the top right, or Esc, leaves fullscreen and puts the view back into the tab where it was. - The game link shows the same corner button. Over a running game it hides after 2 s without mouse movement and returns on the next move. - A click on the corner button never reaches the game. - Stop ends fullscreen. Stop switches back to the Scene tab, and any switch away from the Game tab ends fullscreen, so no empty screen is left behind. - src/viewport/fullscreen.ts wraps the Fullscreen API and holds the rule for when the button shows. FullscreenCorner.ts is the button used by both pages. Two SVG icons are new. - Editor.showGameFullscreen moves the GameViewport into the root while fullscreen is on. Tests: - ./check.sh: 195 files, 2815 tests, exit 0 (3 new unit tests). - e2e 60: 2 new checks for the corner button on the game link. - e2e 61 (new): 4 checks for fullscreen in the editor. - Full e2e run (local): ALL PASSED, 1580 checks. - Counter-checks fail as expected: rule without "no game running", no stopPropagation (60h), no end on tab switch (61c), no reparenting of the view (61a). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
24d360aeec |
Add zoom steps and a larger start caption to the game link (#266)
The released game only ever zoomed in whole steps up to 400%, so a small camera on a large screen stayed far smaller than it could be. The "Click to play" caption was drawn in the 11 px panel title size and was easy to miss. - GAME_SCALE_STEPS gains 300%, 500% and 600%. The game tab picker, Auto in the editor and the game link all read the same list. - The start caption takes its size from the new Theme.captionSize (16) instead of a constant in GameScreen. - The player page keeps the default arrow cursor at all times. It no longer switches to the hand over the caption. Tests: - view.test: Auto for 320x180 in 2000x2000 is now 6, with cases for 3 and 5. play-settings.test takes 2.5 and 8 as invalid steps. - e2e 60 checks the caption against the panel title size and the arrow cursor before and after the start. Module 03 follows the new step list. - ./check.sh 2808 tests, CHECK EXIT 0 (before the last Theme tweak, which changes no test). Full e2e run ALL PASSED, 1574 checks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
4194f3e150 |
Share a link that plays the game without the editor (#266)
A project could only be played inside the editor, behind the login.
To show a game to someone else there was no way short of an account.
A release freezes the saved project under a random token and serves
it on a page that holds nothing but the game view.
- Menu -> Release... opens a dialog with the state of the release,
the link with Copy link and Open, Publish or Update release, and
Take down behind a confirmation.
- Publish saves first and refuses while anything is unsaved, like
snapshots. Updating keeps the link. Take down kills it, and a new
Publish hands out a new token.
- The link is play.html?game=<32 hex>. The page needs no session,
paints Theme.colors.background, zooms with Auto and waits for
"Click to play" so that sound and keyboard focus are allowed.
- The game always starts in the start scene. Persistent variables
are stored per link in the browser. Plugins are not loaded, and the
dialog warns about them and about a project without a scene.
- GET ?r=releases/{token} is the only route besides auth/* that works
without a login. The public answer carries the token as project.id,
so the project id stays hidden. Release files are written with 0640
and nginx does not serve them directly (measured 403).
- Starting, the frame and Load scene moved into runtime/GameRun.ts.
Play in the editor and the play page run through the same code.
- WorldViewport moved to src/viewport/ and no longer needs the
EditorState. Pointer forwarding (gamePointer.ts) and setting up
textures, fonts and sounds (projectFiles.ts) exist only once.
- Vite builds two pages. The play page loads 4 kB of its own code
plus the shared chunk (Pixi, runtime, drawing, 653 kB) and Rapier,
without the editor.
Found on the way, not fixed here: data/projects/<id>.json answers 200
without a login because the files are 0644.
Tests:
- ./check.sh: 194 files, 2808 tests, exit 0.
- tests/php/releases.test.php: 20 checks, also added to CI.
- New e2e module 60 with 10 checks, played in a browser without a
session. Module 46 counts the menu entries and was updated.
- Full e2e run: 1572 checks passed.
- Counter-checks: 3 unit, 2 PHP and 3 browser mutations all fail.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
94c1055895 |
Throttle what the editor shows while a game runs (#265)
With the behaviors fixed, the scene Performance test still ran at about 23 fps with 500 entities. The rest of the frame went to the editor: every spawn or destroy rebuilt the hierarchy with all its rows, redrew the scene view behind the game tab and refreshed every panel. The game itself is cheap. What the panels show of it now follows a preset in the project settings. - New section While playing in the project settings with three presets: Live, Balanced and Minimal. Every project reads as Balanced, including those saved before the setting existed. - Live keeps today's behavior. Balanced brings hierarchy and panel values up to date 4 times a second and stops redrawing the hidden scene view. Minimal updates hierarchy and values only on pause and throttles the graph tabs as well. - The graph and state machine tabs stay live under Balanced. At 4 updates a second the beads on exec edges stutter, and the tabs draw only while on screen. This differs from the issue proposal. - The game itself is never throttled. Pause, step and stop bring every panel up to date at once, and a change of structure that falls between two updates is kept for the next one. - The rule lives in editor/playThrottle.ts as panelPolicy and PanelClock. PlaySession asks it after every frame. - Objects spawned at the top level go into a hierarchy group named after their asset, so hundreds of gold pieces fold into one row. Groups start folded. Measured on a throwaway copy of Performance test, 3 s each, fps: - About 500 entities: Live 38.6, Balanced 56, Minimal 60.6. - About 315 entities: Live 56.9, Balanced 60.5, Minimal 60.6. - p95 frame time at 500 entities: 60 ms, 41 ms and 23 ms. Tests: - ./check.sh: 193 files, 2799 tests, CHECK EXIT 0. - GM_STEPS=1,18,59: ALL PASSED, 54 checks. - Full e2e run (local): ALL PASSED, 1562 checks. - tests/editor/play-throttle.test.ts: presets, update rates, kept structure changes, flush, spawn group and loading old projects. - New e2e module 59: preset chosen with the mouse, measured update rates, scene view behind the game tab, Minimal with pause. - Module 03 now expects spawned objects in their group row after the next update. - Counter-checks: 4 in unit tests and 2 in the browser, each failing as expected. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
a7e793f6de |
Keep many spawned behaviors from eating the frame
A scene with 200 spawned gold pickups, each with a Pickup behavior of 97 nodes, dropped the game to about 14 fps. The graphs themselves ran only a dozen nodes per pickup and frame. The cost was the lookup: for every data input the interpreter searched all edges and all nodes of the graph linearly and computed the port shape of the node again. - New module core/graph/graphIndex.ts builds tables once per graph: nodes by id, edges by node and port, node types. Port shapes are cached as well. - BehaviorSystem shares one cache between all its instances and clears it in syncFromModel. A graph changed during a run applies from the next frame on, as before. - An interpreter without a cache, as in most tests, searches as before. Measured on the scene Performance test (headless per frame, browser fps on a throwaway copy of the project): - Headless: about 21 ms down to about 3.6 ms per frame. - Browser, about 505 entities: 13.6 fps up to 23 fps. - Browser, about 320 entities: 32.3 fps up to 48 fps. - After the fix behaviors take about 15 % of the browser profile. The rest is the editor mirroring the game into the model and rebuilding its panels, which is a separate change. Tests: - ./check.sh: 192 files, 2786 tests, CHECK EXIT 0. - Full e2e run (local): ALL PASSED, 1551 checks. - tests/core/graph-index.test.ts: 2 tests. The tables return the same as the search, in the same edge order. A graph changed during a run applies from the next frame. - Counter-check: without clearing the cache in syncFromModel the second test fails with ['before']. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
5fbc6eb088 |
Set the order in which event nodes run across behaviors (#264)
Which of two events ran first depended on hidden lists: the order of the behavior instances and the order in which nodes were created. A graph had no way to say that one On update must run before another, and an On update of one behavior could run before a key event of another. - A frame now runs event kind by event kind over all behaviors: On disable and On enable, key down, key up, key held, timer, collision enter, stay and exit, animation finished, click, mouse wheel and at last On update. - All 17 event nodes have a field Order, a whole number, default 0, negative allowed. Within one kind a lower Order runs earlier, no matter which behavior holds the node. - On a tie the current hierarchy decides from top to bottom, then the order in which the nodes were created. The old rule that a behavior enabled later goes to the end is gone. - The node shows Order: 5 only when the value is not 0, so existing graphs look the same. - The rule lives as pure functions in core/graph/eventOrder.ts. The interpreter can now fire a single event node. Changes in existing projects: - An object destroyed in a frame gets no further event in that frame. On disable and On destroy still come at the end of the frame. - New instances get all their On start first, then all On enable. Teardown runs all On disable first, then all On destroy. - State events follow right after the event kind that caused them. - Four unit tests had fixed the old order and are adjusted: two in behavior-events, one in physics-system, one in key-repeat. Tests: - ./check.sh: 191 files, 2784 tests, CHECK EXIT 0. - GM_STEPS=1-6: ALL PASSED, 280 checks. - Full e2e run (local): ALL PASSED, 1551 checks, measured. - tests/core/event-order.test.ts: 11 tests for the rule, the field on all 17 nodes, order across behaviors, key before update, two nodes in one graph, On start, hierarchy after late enable and reorder at runtime, state events, save and load. - e2e 6z-viii, 2 checks with real mouse and keyboard: Order -1 set in the inspector shows on the node, and a run logs B, A although B is lower in the hierarchy. - Counter-checks: 5 in unit tests and 1 in the browser, all fail as expected. The originals were restored and checked with cmp. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
0c41cb97b1 |
Clip the children of an object to a clip area (#260)
An inventory grid could not scroll, because nothing could cut a child off at an edge. A game also had no way to hear the mouse wheel. Both are needed for a scrolling list, a dialog text or a window with more content than room. - A new object type Clip area has width and height and turns, scales and mirrors with its parent. The size gizmo drags width and height. - Everything below a clip area, on every level, is drawn only inside its rectangle, in the Scene view as in the game. Nested areas cut to their intersection. The rule works on a convex polygon, so a turned area cuts right as well. - On click, Is hovered and On mouse wheel do not hit a cut-off part. The Scene view does not pick it either. - In the Scene view the area is a dashed frame that is grabbed only at its edge, so a click inside lands on the content. The eye under Shape hides the frame. In the game the area is invisible. - While an object below an area or one of its parents is selected, the Scene view shows the cut-off part at 30 % opacity and it can be grabbed there. The rule lives in core/properties/clip.ts. - The Properties node knows the type Clip area. - The new event node On mouse wheel fires by the rule of On click. Its delta counts in notches, positive is down, and a frame's turns arrive as one value. It also gives x and y. Limits: - Only sprites and texts show the faded rest. Tilemaps and particles are cut and can be grabbed when open, but are not shown faded. - Masks by texture shape are out, as noted in the issue. Tests: - ./check.sh: 2773 tests, CHECK EXIT 0. - Full e2e run (local): ALL PASSED, 1549 checks, measured. - tests/core/clip-areas.test.ts: 12 tests for the cut rule, scene and game, save and load, hits, the wheel with and without owner, the notch conversion and the open rule. - e2e 58, 5 checks with real mouse and wheel: the scene cuts (74 of 74 red inside, 0 outside), a click on the cut part picks nothing, the selected rest is faded and grabbable, the game cuts, one notch logs 1. - e2e 04 and 46 count the object types and the eye rows. They now include Clip area. - Counter-checks: 6 in unit tests and 3 in the browser, all fail as expected. The originals were restored and checked with cmp. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
89aa92d2e3 |
Put a shader on a text and draw pixel fonts sharp (#169)
A text could not wear a shader, and a pixel font was always smoothed. Pixi's Text takes no custom shader. It also shares one texture between texts of the same content and style, and the filter is not part of that key, so two equal texts with different filters would overwrite each other. - TextPicture rasters a text into a texture of its own and draws it like a sprite. Shader, time animation and edge mode work without a special path. A new text, font or size rasters again and keeps the shader. - The old texture is dropped only after the shader holds the new one. Before, Pixi warned about a destroyed texture that was still bound. - A text has a Shader row with the shader's property rows below, as a sprite has. Overrides, saving, animation and the Properties node (type Text) know them. A shader parameter named like an own row of the text loses to that row, as on the particle source. - The font asset has a Filter row, Linear or Nearest. The row existed three times already (texture, vector art, pixel art) and is now one helper filterRow. - Nearest rasters the text 8 times finer and cuts every 8x8 block to one texel: at least half covered is full, else empty. Chrome on Windows smooths canvas text sideways through DirectWrite whatever filter we set later. The cut removes those half pixels on every system, and the texture stays one texel per world unit. Found on the way: - On a font that is no pixel font thin strokes break apart under Nearest. For a pixel font at its own size this does not matter. Not done: - The preview in the Shader tab does not take a selected text as its picture, it shows the test picture then. Tests: - ./check.sh: 189 files, 2759 tests, CHECK EXIT 0. - text-shader.test.ts with 10 tests: rows and name precedence, a clamped override, save and load, animation, the font filter, and sharpCoverage on a smoothed edge and at the texture border. - e2e 30 has 7 new checks: red glyphs through the shader picked with the mouse, the shader survives a new text, red in the game, soft edges under Linear, clean 4x4 blocks under Nearest, and half covered texels (Linear 238, Nearest 0). - Full local run before the Nearest cut: all passed, 1543 checks. GM_STEPS=1,30 on the final bundle: all passed, 32 checks. The sum 1544 in the README is counted from these two runs, not measured. - Counter-checks fail as expected: in the browser no shader on a text fails 30h-30j, an always linear sampler fails the Nearest check, the old 1:1 raster leaves 107 half covered texels and fails 30k-ii. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
ef2600493f |
Read the pixel size of a texture in a graph (#262)
A graph had no way to know how big a picture is. An icon sprite with a fixed size therefore stretched every icon of another size, an 8x8 pixel-art icon in a 16x16 slot was drawn twice as big. - New data node Texture size. It takes a texture, a frame or a pixel art and hands out width and height in pixels, 0 x 0 for none. Wired into a sprite's width and height, the picture shows pixel for pixel. - GraphLookup gets textureSize. The project answers it with the same pictureSize rule a sprite uses to size itself. Tests: - ./check.sh: 2749 tests, CHECK EXIT 0. - 2 catalog cases (a picture, no picture) and one test against a real project: pixel-art sheet, single frame, missing texture. - No e2e check and no counter-check for this node. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
d7d0a6e7d4 |
Let Tab reach the game in the Game tab (#261)
The game did get Tab, but the editor moved the keyboard focus to a button at the same time. The next Space then pressed that button instead of reaching the game. The key field of On key down could not capture Tab at all, so an inventory on Tab could not be built. - FocusManager asks tabBelongsElsewhere before it moves the focus. The editor answers yes while the Game tab is in front, so Tab stays with the game as long as no field has the focus. - A widget can claim Tab through takesTab. The key capture button does so while it is capturing, so On key down accepts Tab. - The harness knows the key Tab. Found on the way: - The first version of check 20g left the Behavior tab in front, and module 21 then failed in the chained run. 20g now goes back. Tests: - ./check.sh: 2746 tests, CHECK EXIT 0. - e2e 20 has two new checks: Tab reaches the game and leaves the keyboard with it, the key field captures Tab. Both failed against the old bundle. - GM_STEPS=19,20: all passed, 44 checks. The full local run with all three topics of this push passed with 1543 checks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
d2701b3ec5 |
Scroll the image tile into view before clicking it in e2e 12
The image picker scrolls once the texture store holds more images than fit into the dialog. The hook imagePickerTile still reports the tile position then, so the click in module 12 landed outside the visible list and picked nothing. Locally spare.png sat at place 27 of 32, and the full run broke off in module 12 with "Texture not found". - FilePicker exposes its scroll area as listView. - The new test hook __gm.imagePickerRect() returns that area. - Both tile clicks in module 12 wheel the tile into view first with the existing inView helper, as modules 41 and 43 do. The editor behaves the same. Only the hook is new. The texture store is shared with real projects, so the seed does not clean it up. Tests: - ./check.sh: 188 files, 2746 tests, CHECK EXIT 0 - GM_STEPS=10,12: ALL PASSED, 58 checks - Full local run: ALL PASSED, 1535 checks - Counter-check: the old module 12 against the same bundle gives 6 FAILED (selected: null, then "Texture not found") Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
4e6aeb5f72 |
Query objects in bulk and filter, count or test a list (#259)
The object queries only ever returned one object: Object by name, Child by name and Find all stop at the first hit. Getting every coin, every child or every item that passes a test needed a For each with its own bookkeeping. These nodes return the whole set as a list of Object, so For each and Find can use item straight away. - Objects by name returns every scene object with that name in scene order. - Children by name returns every match below object, nearer levels first, the same order as the singular node. - Children returns the direct children, with recursive all of them level by level. - Objects of asset returns every instance of an object asset or container by the same rule as Is asset, spawned ones included. - All four take a snapshot. Destroyed objects are never in it, disabled ones are, as with the singular nodes. - Find has a Mode setting: First, All, Any and Count. First is the default and hides the row, so existing graphs look the same. - Match follows the mode of its paired Find: done with items and indices, done with any, or done with count. An unpaired Match shows First, since the pairing only comes from the wiring. - First and Any stop at the first hit, All and Count walk the list. - Changing the mode drops edges at outputs the Match no longer has, in one undo step. SetNodeParamCommand now cleans the whole graph, but only edges that were valid before the change. - The palette entry "filter" adds a Find set to All with its Match. Found on the way: - After adding the filter entry, "find" + Enter picked Filter. The entry has node type Find, counted as an exact hit, and the tie went to the alphabet. The search now ranks by the entry id. e2e 6z-v caught it. Tests: - ./check.sh: 188 files, 2746 tests, CHECK EXIT 0. - find-modes.test.ts with 11 tests, 11 catalog cases for the four nodes, the asset input guard now expects 3. - e2e 6z-vii with 3 checks: filter from the palette, Count picked in the inspector drops the items edge, a run logs 2, 2, 2, 3, 4, "0, 2". - GM_STEPS=1-6: all passed, 278 checks. - Counter-checks fail as expected: every mode stopping early (2), Match ignoring the mode (6), cleanup on the own node only (1), no recursion (2), no inOrder (2), old ranking (1). In the browser a Match ignoring the mode fails both editor checks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
f84defb9b6 |
Pick an object of the same scene for an Object variable (#258)
An Object variable could only be set by a graph while the game runs.
A placed object always showed "(null)", although the other objects of
its scene are there to choose from and the runtime can already find
any object by its id. Now the placed object stores a reference to one
of them, and the running game turns it into the real object.
- Stored as {"$object": "<id>"}. The empty placeholder stays as it
was, so old projects load unchanged and empty still means "(null)".
- The row of an Object variable on a placed object is a dropdown with
None and every object of the scene, indented like the Hierarchy.
The same goes for Object fields in structs and Object items in
lists, also in the Variables and List tabs.
- The behavior asset keeps "(null)". An asset has no scene, so "Apply
to asset" is gone from Object rows.
- The runtime resolves the reference when the Variables or Properties
node reads it. A destroyed target reads as null, a missing one shows
"(missing)" in the row.
- Inside a container only objects of the same container are offered.
The reference keeps the template id, so every placement finds its
own copy.
- Deleting the target clears the reference in the same undo step.
Paste points references inside the pasted group at the copies and
leaves outside references alone. Create container and Unpack carry
references along with the new ids.
- A value a graph writes while running stays in the run. The scene
keeps the picked object, also in "Run in editor".
Found on the way:
- Setting an override coerced every object slot to empty, in plain
values, struct fields and list items. The shared objectPlace() now
keeps the reference.
Not done:
- No eyedropper to pick in the scene, the dropdown is enough for now.
- Deleting a template object in the container editor does not clean up
references in other scenes. They read as null there.
Tests:
- ./check.sh: 187 files, 2724 tests, CHECK EXIT 0
- e2e 1-9, 51, 57: ALL PASSED, 389 checks
- e2e 1, 57 on the final bundle: ALL PASSED, 17 checks
- 13 unit tests in object-references.test.ts, 5 checks in e2e 57
- Counter-checks: 8 unit breaks fail their tests, the browser break
without a pick list fails 57b-57d
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
e8b3ac0a3b |
Keep behavior variables private to their own graph (#257)
A behavior often needs working state such as a timer or a counter. Until now every variable showed up on each placed object and in the Properties menu, so the inspector filled up with values nobody should touch. A private variable works like any other inside its graph but is not offered anywhere outside. - Each behavior variable gets a lock switch next to Keep. It starts off, so existing projects stay as they are (`privateVariables`). - A placed object shows no private variable, neither in the inspector nor while the game runs. If all are private, it says so. - Switching a variable to private drops its overrides on every placed object in one undo step. Switching back does not restore them. - A running game does not write a private value back as an override, and a project change during the run does not reset it. - `+ Property` no longer offers private variables. A port that names one by hand still reads and writes it without a warning. - The default value of a private variable is read-only in the asset inspector. Its shape (name, fields, type, delete) stays editable. - Keep is now a pin icon instead of a word. With the lock added, the header of a list variable had no room left and the lock covered `+ Item`. Not done: the animation track picker still offers private variables. Tests: ./check.sh -> 186 files, 2711 tests, CHECK EXIT 0. e2e 1,19,20,21,56 -> ALL PASSED, 119 checks. New module 56 has 7 checks with a real mouse. Counter-checks fail as expected: unit without the write-back filter, the sync guard, dropping overrides or loading the flag. Browser with all variables on the object, an unfiltered menu, or no value lock. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
cf6565d108 |
Start new behavior variables with Keep switched off
Keep is a choice the author makes per variable. Switching it on for every new variable meant most behaviors carried state across runs that nobody asked for, and the toggle had to be turned off by hand. - AddVariableCommand no longer adds a new variable to keptVariables. Keep is only switched on by hand. - Existing projects are unchanged. Their keptVariables stay as saved. - The comment in Asset.ts describes the new default. - The counter fixture in keep-state.test.ts now keeps count explicitly instead of relying on the old default. Tests: - ./check.sh: 2706 tests, CHECK EXIT 0 - e2e 1, 6, 19: ALL PASSED, 172 checks - 19c-ii expects [] after adding, then ["var1"], [] and ["var1"] after undo, all clicked with the real mouse - Before the fixtures were adjusted, 8 unit tests failed on the new default Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
38e0f656be |
Split a struct output into one pin per field (#256)
Reading one field of a struct needed its own Expand node, even where the graph already knows the struct type. A split output shows the fields directly on the node, so a Find … Match test becomes a single Equals and the wires keep running left to right. - Right-clicking an output pin offers "Split into <Type> fields" when the type is known and "Split as …" for every other struct asset. The output is replaced by one pin per field with the field's kind. - The type is known for typed struct variables, typed Pack/Expand, fields of another split and item of For each, Find, Match and Item at over a typed list (portSplits.ts). - Right-clicking a field pin offers "Recombine". Splitting drops the edges of the whole output, recombining drops the field edges. One undo step brings them back. - The split is stored as GraphNode.splits and saved with the graph. Copy and paste carry it along. - Field pins follow the struct asset like a typed Expand. A new field gets a pin, a removed field loses pin and edge, a renamed field keeps its edge. Deleting the asset keeps the last fields. The delete dialog counts splits, so it now says "node(s)" instead of "Pack/Expand node(s)". - Renaming a variable carries its split and the field edges along. - At run time the node still produces the whole value. A field pin reads its field like Expand, a missing field reads as empty. - Limits: outputs only, one level, behavior graphs only. Tests: - ./check.sh: 185 files, 2706 tests, CHECK EXIT 0 - e2e GM_STEPS=1-6,54,55: ALL PASSED, 303 checks - New port-splits.test.ts with 12 tests. e2e 6z-vi with 3 checks using a real mouse: split Find.item, wire item.Name into the test and Match.item.Amount into the log (logs 7), recombine. - Counter-checks: without field reads in the interpreter, without rename in the split, without moving edges on rename and without cleanup on field removal one unit test fails each. In the browser, without field reads nothing is logged and without type inference the split entry is missing. - Check total in README: 1520. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
2aaca01929 |
Find the first matching item with a Find … Match block (#255)
Searching a list with For each needs the test result wired back into break on the left of the loop node. The wires then run across the node and the graph reads badly. Find … Match keeps the whole search flowing left to right, while the test in between stays a free graph. - Find takes exec and list and offers item and index. It sets them to each element in turn and has no exec output of its own. - Match reads its bool input match once per element. The first true value continues at found with item and index of the hit. No hit or an empty list continues at not found with an empty item and index -1. - The pair is derived from the wiring and stored nowhere. A Match belongs to the Find whose item or index its test is built from. Copy, paste and undo therefore need no extra code (findBlock.ts). - An unpaired Find or Match, two Matches on one Find and a Match fed by two Finds show a warning on the node. At run time the flow ends at an unpaired Find and the console says so. - Adding Find from the palette places its Match 480 px to the right in the same undo step. - The graph draws a quiet frame behind Find, Match and the nodes of the test. Literals feeding the test from outside stay outside the frame. - The test consists of data nodes only, like the break of For each. Tests: - ./check.sh: 184 files, 2694 tests, CHECK EXIT 0 - e2e GM_STEPS=1-6: ALL PASSED, 272 checks - New find-match.test.ts with 12 tests, 2 catalog cases, e2e 6z-v with 3 checks using a real mouse. - Counter-checks: ignoring the test fails 4 unit tests, no ambiguity check fails 2, framing everything fails 1. In the browser, no frame and no Match from the palette each fail their check. - Check total in README: 1517. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
e1d3c0cadb |
Keep items a running game puts into a typed list
A typed global list lost the items the game had added to it. The item showed up in the inspector for a moment and then was gone again. - Since #254 the project hands out a typed list as a fresh copy each time, with the `$struct` marks stripped. - While a game runs, every change to the project runs `adoptDeclarations`. It took a declared value over when it differed from the last one it had seen, compared with `!==`. - The fresh copy never equals the last one by identity. So the empty list from the project overwrote the live list on every project change, for example when the game writes something back to the scene. - The comparison now uses `sameValue`. A real edit to the variable during a run still reaches the game. Tests: - ./check.sh: CHECK EXIT 0 - New unit test in scope.test.ts, red without the fix. - e2e 55i: a graph appends to a typed global list, then another asset changes during the run. With the fix {"filled":2,"afterEdit":2}, without it afterEdit is 1 and the check fails. - GM_STEPS=1,55: ALL PASSED, 26 checks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
b0d0868f55 |
Give a list one struct type so every item keeps its shape (#254)
A list of structs used to let every item carry its own fields. Adding an item meant typing the same fields again, and nothing kept the items in step with the struct asset from #253. A list can now name a struct type for its items, and the asset keeps them in shape. - A list variable and a list field get the row Items with Mixed, every struct asset and Make struct type. Make struct type collects every field any item has, so no item loses a field. - A typed item carries its type under the key `$struct` (ITEM_TYPE_KEY). listItems does not count it as a field. - A list that holds anything but structs refuses a type, also when the offending item sits in an override. The console names the list and the count, and the list stays as it was. - In a typed list `+ Item` adds an item with the defaults, without the kind menu. Field names are locked, values stay editable. - The rows of a typed item take their kind from the struct asset (declaredKind in InlineList.ts). An asset field shows the asset picker instead of a text field, as reported in the issue. - A struct item in a mixed list gets its own Type row. Switching a list back to Mixed keeps the type on each item. - syncStructTypes brings marked items into shape and carries renames into them, also inside overrides and nested lists. One undo step. - The delete dialog of a struct asset also counts typed lists. - The game never sees the key. World and sharedState strip it with withoutItemTypes and bagWithoutItemTypes, so a struct stays a plain object at runtime. - Add item and Set item get the setting Type. With a type they only accept a struct with exactly its field names (fitsType in SetItem.ts). Anything else is left out and logged. - The item indent from #252 is back to its old width, the narrow one squeezed the value fields. Not done: writing a variable does not check the type, and the List node offers no type. Found on the way: a test helper in list-of-structs.test.ts called nodes without params. It now passes the same arguments as the interpreter. Tests: - ./check.sh: 183 files, 2679 tests, CHECK EXIT 0 - e2e GM_STEPS=1,21,22,32,54,55: ALL PASSED, 150 checks - e2e GM_STEPS=1-6,21,22,32,54,55 before the follow-up: 404 checks - New: tests/core/typed-lists.test.ts, e2e module 55 with 13 checks - Counter-checks bite in unit and browser, originals restored by cmp Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
61bbb96a96 |
Define a struct once as an asset and reuse its shape (#253)
A struct used to declare its fields wherever it stood. The same shape in two variables meant typing it twice, and Pack/Expand copied it once without staying in sync. A Struct asset now holds the shape in one place, and every struct that picks it as its type follows it. - New asset type "Struct" (not placeable, group Structs, own icon). The inspector shows its fields with name, kind and default value, and "Open" shows them in the variables tab. - Struct variables and struct fields get a Type row: own fields, any Struct asset, or "Make struct type". With a type the fields come from the asset and cannot be added, renamed or removed there. The values stay per variable, overrides on placed objects still work. - Pack and Expand get the setting Type. With a type their ports are the fields of the asset and read-only in the inspector. - A change to the asset reaches every typed place in one undo step (syncStructTypes). A new field arrives with its default. A removed field goes along with its port, edge and typed value. A renamed field keeps its value in variables, overrides and on ports with edges, also through nested types. - Undo for these edits records plain assignments (Recorder), because the reach of a change is not known in advance. - Cycles such as Loot in Loot, also through another type, are refused and not offered in the picker. - Deleting the asset names how many places use it. Afterwards each place keeps its fields as its own. - Loading reshapes a stale copy in a project file to its type. - "Make struct type" creates an asset from an existing struct and links the place to it. Differs from the plan: - A changed default value applies to new fields and newly typed places only. Existing values stay. - Removing a single field shows no warning. Undo brings it back. Typed lists are left for #254. Lists stay dynamic for now. Found by e2e: after deleting or renaming the type the inspector did not rebuild and kept showing the old name. Fixed, the check was red before. Tests: - ./check.sh: 182 files, 2661 tests, CHECK EXIT 0 - GM_STEPS=1,4,21,22,54: ALL PASSED, 176 checks - 14 unit tests in tests/core/struct-types.test.ts - New e2e module 54 with 14 checks, real mouse. Module 04 counts 23 asset types. - Counter-probes: without the rename pass 4 unit tests fail, without reshaping values 8, without the cycle rule 1. In the browser, without spreading changes and with + Field despite a type, 3 checks fail. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
4857bb75e6 |
Show lists in the inspector and variables in their own tab (#252)
The List tab could show one list at a time and was the only place to see its items. A list now shows its items right where it lives, and a new Variables tab shows all variables of an asset or placed object. - InlineList draws the items of a list under its row in the inspector, in struct fields and in the Variables tab. A list inside a list or a struct field is drawn the same way, one step indented. - The index column is the indent. Items sit under their list without an own margin, and the index column is 16 px wide instead of 28 px. - Above 20 items (INLINE_LIMIT) the rest collapses into "... N more - open in tab". During a run the rows show the values from the game. - ListHead puts "N items" and "+ Item" in one row. "N items" opens the list in a tab. A separate Open button squeezed the counter to zero width in the narrow inspector column. - VariablesEditor shows every variable of a behavior asset, a variables asset or a placed behavior. On an asset it edits the declarations. On a placed object an edit only overrides that object, with the usual reset and push buttons. - Opening a list inside a tab adds a new tab and keeps the open one. valuesKey gives each list its own document, so a list that is already open is shown instead of opened twice. - Titles read "Pickup · Variables" or "Listy · nums · [3]". The tab closes when its asset or object is deleted. - Lists on node inputs stay inline only, a node owns no variables. - The List tab, ListEditor and the __gm.list hook are gone. Tests: - ./check.sh: 181 files, 2645 tests, CHECK EXIT 0 - GM_STEPS=1,21,22,32: ALL PASSED, 123 checks - e2e 21 is rewritten for inline lists and the new tab, 22 and 32 are adapted. Sum in README: 1486 (+6 in 21, -1 in 22), counted by hand. - Counter-probes fail as expected: sub-lists not told apart (1 unit test), sub-list replacing the open tab, all items drawn inline (60 rows instead of 20), no live values (0 rows at 24 items in a run). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
92fad6b9e3 |
Show the list a running game fills in the list tab (#251)
While playing, the inspector said "2 items" for a list a graph had filled, but clicking it opened an empty list tab. The inspector reads a Variables asset from the running game, the list tab only read the list stored in the project. It did not follow the run's ticks either. - resolveList takes the live values. For a Variables asset it uses the running game's value while one runs, the project's otherwise. This is the inspector's rule. - ListEditor refreshes on runtime ticks about 10 times a second, and not while a field in the tab is being typed in, like the inspector. - Editor hands the list editor the inspector's liveVariables. - __gm.list.info() also returns rows, the rows the last build drew. - Editing in the tab while playing goes into the run's copy of the project, as in the inspector. A very long, growing list rebuilds all rows up to 10 times a second. Tests: - ./check.sh: 180 files, 2634 tests, CHECK EXIT 0 - e2e GM_STEPS=1,32: ALL PASSED, 30 checks - e2e 32b-ii, 2 checks: a graph adds an item every frame, the test pauses and clicks "N items" with a real mouse. The tab draws 26 rows for 26 items, grows to 50 after resuming and is empty after Stop. - Counter-checks: without live values the tab draws 0 rows for 23 items, and without the refresh it stays at 23 - README: 1481 editor checks, counted by hand Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
b90b62886b |
Put one item into a list, a struct staying whole, with Add item (#250)
A list could only grow through Join lists, and that takes its b apart: a struct becomes one item per field and a list is joined. A list of structs such as loot could not grow at all. - Add item returns a new list with value as exactly one more item. A struct or a list goes in whole. The list that came in is left alone, like with Set item. - index -1 or unset appends. Otherwise the item goes in before that index. An index outside the list appends, so the value is not lost. - The rule that an item takes the kind of its slot moved out of Set item into fitToSlot. Both nodes use it, Set item behaves as before. - No e2e check and no browser look. The node is covered by unit tests. Tests: - ./check.sh: 180 files, 2634 tests, CHECK EXIT 0 - 7 catalog cases, 4 cases in list-of-structs.test.ts, and index in the whole-number inputs of texture-kind.test.ts - Counter-check: taking the value apart like Join lists fails 3 tests - This commit alone: tsc and the three touched unit files pass (337 tests) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
cb8a2d029a |
Loop over a list and stop at the first match with For each (#249)
Finding the first item that matches took Count, Repeat, Item at and a Branch, and the loop could not stop once it had found something. A graph has no lambdas, so the predicate is an input that is read every round. - For each runs body once per element, with item and index set, and goes on at done. - Its break input is read anew before every round, after item is set. Once it is true the loop ends there and that round's body does not run. - done sees the matching item and index with stopped true. Without a match it sees no item, index -1 and stopped false, the OrDefault. - RepeatResult has optional items, stopWhen, stopped and exhausted. GraphInterpreter.loop reads them, Repeat runs as before. Tests: - ./check.sh: 180 files, 2623 tests, CHECK EXIT 0 - e2e GM_STEPS=1-6: ALL PASSED, 269 checks - 5 interpreter tests in graph.test.ts, 2 catalog cases - e2e 6z-iv: the palette creates the node, and a search for Name == "Gold" in Axe, Gold, Bow, Gold logs "1true" - Counter-checks: without the break check 2 unit tests fail, and with break not read over its edge 6z-iv fails - This commit alone: tsc and the three touched unit files pass (349 tests) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
a5ae21481b |
Type point values, snap and scrub the preview in the curve tab (#248)
Building a curve by mouse alone was fiddly: there was no way to put a point on an exact value, no grid, and the running preview could not be stopped at a chosen moment. The hand cursor over points also looked like a link instead of a drawing tool. - Number fields t and v in the header show the selected point. Typing or dragging on them goes through the same rules as a mouse drag, so points never overtake and handles stay clamped. One undo step each. t is greyed out on the two end points. - The magnet button from Vector art snaps points and handles to a 0.05 grid in t and v, when placing on free space and when dragging. - Points and handles keep the default arrow in the curve tab and in Vector art. The pixel art palette dropped pointer as well, which is beyond the issue. Collider corners never had it. - The preview now starts off. A vertical time line with a knob under the t axis sets t when dragged sideways. Dragging the dot on the rail jumps to the first t where the curve reaches that value, or to the closest one if it never does. - The rules are pure functions in curveView.ts (withCurvePointSet, snappedToGrid, shareAtValue, previewSecondsAt), the canvas only calls them. Tests: - ./check.sh: 180 files, 2616 tests, CHECK EXIT 0 - GM_STEPS=1,47,53: ALL PASSED, 87 checks - 4 new unit tests in curve-view.test.ts, 4 new e2e checks in 53 (53h-53k) and 1 in 47, all with real mouse and keyboard - Counter-probes bite: without "first t" 1 unit test fails, with pointer, no snap, no field commit, no scrub or preview on 5 browser checks fail, pointer in Vector art fails the new 47 check Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |