Commit Graph
14 Commits
Author SHA1 Message Date
adminandClaude Opus 5.5 393caadc9d Head the groups of the inspector with bands that fold (#290)
game-maker / gate (push) Successful in 2m39s
game-maker / suite (push) Successful in 13m10s
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>
2026-10-01 22:08:28 +02:00
adminandClaude Opus 5.5 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>
2026-10-01 11:25:37 +02:00
adminandClaude Opus 5.5 1b3cb625a3 Let a text field select from right to left with the mouse (#236)
game-maker / gate (push) Successful in 3m8s
game-maker / suite (push) Successful in 9m36s
Dragging from right to left in a canvas text field only moved the
cursor. The hidden input behind the field got every selection sorted
and without a direction. Its selectionchange echo then handed back the
sorted range, and the anchor jumped to the mouse on the first move.

- KeyboardBridge sets the selection with 'backward' when the focus
  lies before the anchor, and reads selectionDirection when the input
  reports a change.
- TextField passes anchor and focus through unchanged. Its own
  sorting is gone.
- Shift+Left runs through the same path. The cursor should now sit at
  the moving end there too. This is not tested on its own.

Tests:
- ./check.sh: 167 files, 2435 tests, CHECK EXIT 0
- e2e 2: ALL PASSED, 25 checks. New check 2d-ii drags right to left
  in the name field and reads start 0, end 13, direction backward.
- Counter-check without the direction: 2d-ii fails with end 2, the
  reported symptom.
- e2e check total in README goes to 1401 (computed, not run in full).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 15:30:33 +02:00
adminandClaude Opus 5.5 cb32b4bc23 Draw scene symbols as white panel icons that make room for each other
game-maker / gate (push) Successful in 2m54s
game-maker / suite (push) Successful in 9m5s
The scene view drew its symbols from its own SVGs, each in a type
colour and some on a plate. They looked heavier than the panel icons
and were hard to read on busy ground. Symbols of different objects
also sat on top of each other whenever their positions met.

- Every symbol is now the panel icon of its kind, one file for both
  places. The scene-only SVGs in src/viewport/icons are gone.
- Symbols are white in a thin dark outline with no plate behind them.
  haloSvg widens each shape of the file by its own stroke and draws
  that silhouette dark under the icon, so no second file is needed.
- A butt stroke end would leave its end without outline. The halo caps
  such strokes square, and the rigidbody arrow shaft is now a filled
  rect so its top edge is outlined exactly.
- Symbols are 18 world units across, stay upright and are clickable.
  A click selects the object, a drag moves it.
- symbolLayout places every symbol on its object's position. Symbols
  that overlap stand on a ring round the middle of their positions,
  evenly spaced one icon apart. A ring that reaches into another
  symbol takes it in until nothing overlaps. Ownership plays no part.
- Names sit beside their symbol, on the outside of a ring. Selection
  frames and parent links follow the symbol.
- A collider icon no longer shows trigger or invalid state. The
  outline colour of the shape still does.
- A lone collider sits on its sprite's middle again. A press there on
  an unselected sprite drags the collider, so e2e 02 and 16 now grab
  the sprite beside its middle. Hook __gm.container.objectRect helps
  16 find that spot.

Tests:
- check.sh: 2245 tests, CHECK EXIT 0 (+8 for layout and halo)
- e2e 1-16,31,39-43: 670 PASS, 2 FAIL. Both were sampling too
  strictly for the thinner, smaller icons. 5c looked for the exact
  outline colour, which the thin outline only reaches blended at this
  zoom. 31n started the ring scan one pixel past the smaller ring.
  Both are fixed, and e2e 5,31 then passed with 48 checks.
- 5c checks white with an outline and none of the old colours, 41b
  checks the ring positions and that a click on a symbol selects its
  object, 31n checks the now white particle ring.
- Counter-checks: without the halo only 5c fails, also with the looser
  outline match. Without the layout the ring and click checks in 41b
  fail. Originals restored by cmp.
- The e2e check total stays 1351.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 20:07:47 +02:00
adminandClaude Opus 5 0d207769b9 Focused text fields show their outline on every edge (#202)
game-maker / suite (push) Failing after 1m15s
game-maker / gate (push) Successful in 2m15s
The focus outline of a text field was only visible at its corners.
The mask that clips the text covered the whole field, border included,
so the 1 px stroke was cut away along every straight edge. Only the
rounded corners bent inwards far enough to survive. With the larger
radius the new theme tried, this became obvious.

- TextField puts text, selection and caret into their own container.
  Only that container is masked. Background and outline are not.
- NumberField and the rename field in lists inherit the fix.
- The theme radius goes back to 2 px.
- New debug hook __gm.focusedRect() returns where the focused widget
  sits, so a check can read its border.

Tests:
- ./check.sh: 141 files, 2080 tests, exit 0
- GM_STEPS=2: ALL PASSED, 24 checks (one new)
- The new check in 2c reads the middle of all four edges of the
  focused name field. All four must be the focus blue.
- Counter-check with the old full-field mask: the check fails, all
  four edges read #24272e.
- Editor check total is now 1286 (1285 + 1), computed, not measured.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 18:11:42 +02:00
admin 7e5003b590 A test that waits for a deadline somebody guessed is waiting for the wrong thing, so it waits for the editor (#93)
game-maker / suite (push) Successful in 6m48s
The suite spent 88% of its step time asleep: 564 s of steps, 496 s of
them in sleep(n) - fixed spans estimated by hand and then copied from
step to step. That is why it was slow, and why it flickered.

The signal is afterFrames(count, timeout) in ui/render.ts, exposed as
__gm.afterFrames(). It wakes the loop itself, because an editor that
draws nothing would never redeem a promise of a frame, and it gives up
after a timeout instead of hanging the run. settle() in the harness
waits two drawn frames (~32 ms) instead of 150-300 ms and falls back to
sleep(150) when the hook is missing (an old build): silently not
waiting would make every later check a coin toss.

Every action goes through it now - click, doubleClick, wheel, drag,
dragAsset, press, typeText, select, mutate, undo, redo - and with that
about 550 sleep() calls behind those actions became redundant, helpers
that are nothing but a mutate in a coat included. What a frame cannot
answer got its own condition instead: waitUntil(expr), play()/stop()
waiting on __gm.state.mode rather than on 300 ms, saved() for the
autosave (the two hand-written polling loops sampled at 300 ms for
something that arrives after a second), and showTab() as a real tool -
47 places called evaluate("__gm.showTab(...)") raw, and 24 of them
waited for nothing at all and read the old panel.

564 s -> 243 s of step time, the waiting share 88% -> 76%. Three kinds
of wait were kept, and each one bit when it was taken away: loading
bytes (textures, fonts), game time that a repaint cannot stand in for
(time scale, shader and particle clocks, the live values a running
graph carries), and the inertia the scroll step measures on purpose.

Two things the new speed uncovered, both real: 25-animation read the
frame menu unfiltered and had frozen an incomplete list (the CI failure
on c990dbc), and 33-render-on-change measured idleness while the
autosave of the step before was still painting - it waits for
saveStatus to leave dirty/saving now, so it measures that nothing
happens any more instead of that it waited long enough.
2026-09-16 08:08:10 +02:00
rootandClaude Opus 5 c47fd5e4dc Console panel: capture console.*, timestamped lines, resizable (#50)
A log layer (src/editor/console/log.ts) keeps a 500-line ring buffer and
wraps console.log/info/debug/warn/error, still calling the original. Notices
and behaviour runtime errors go straight to the layer instead of through
console, so editor tests stay quiet and errors can render red. world.log is
untouched: it is per-run structured state that runtime tests and e2e read
directly, not a text stream.

The panel sits under the visible tab, resizable with Splitter('y'), with a
CONSOLE header and a clear button. Each line carries its wall-clock time in
its own dimmed column, so locate() still finds the message text.

The taller centre area drops the graph fit zoom to 0.60, which broke six e2e
checks that assumed one screen pixel is one graph unit and that a fixed
offset from the canvas centre is empty. They now ask __gm.emptySpot(),
__gm.graph.emptySpot() and __gm.graph.zoom() instead of guessing.

locate/locateAll added the unscaled Pixi text width to the global position,
so click points in the scaled graph canvas landed too far right; they use
getBounds() now.

check.sh: 831 tests. Full e2e: ALL PASSED - 671 checks. Smokes 24/19/20.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 19:35:40 +02:00
rootandClaude Fable 5.1 6471671f7d Refactor round: ship web/index.html, drop the remote browser, fix stale comments and README
- `.gitignore` said `index.html` unanchored, which also caught the Vite source page;
  a fresh clone could neither `dev` nor `build`. Anchored to `/index.html`, page added.
- Removed GM_REMOTE (tools/remote/gm-browser.ps1 and the ssh/scp branch in browser.mjs):
  the Windows test machine is gone and the only flaky check that lived on it is closed (#37).
- Removed `npm run preview`, undocumented and unused.
- Comments: wrong number in collider.test.ts (250 → 200), the "declarations used to…"
  history in listItems.ts and friends, the 24 px/28 contradiction, section banners in
  nodes/index.ts, node-catalog.test.ts and api/index.php, name paraphrases, and the stale
  step numbers in tools/e2e/11-…23- from the old global count.
- README: asset types include `animation`, kind order Number/Text/Boolean, tagged colour
  and texture items, 6/5 kinds, the `Texture` node, `./build.sh`, PROJECT_FORMAT_VERSION,
  and the file tree (kinds.ts, listItems.ts, propertyPorts.ts, Breadcrumb.ts, beads.ts,
  tools/assets/).
- tests/helpers.ts takes the duplicated `findObject` and `sceneWithCamera`.

Proof: ./check.sh 797 tests, full e2e ALL PASSED 633 checks against this bundle.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-12 15:44:05 +02:00
rootandClaude Opus 5 8f92a06c84 Consolidate duplicated editor code, trim comments, refresh README
Split GraphCanvas (1076 -> 831 lines) into EdgeLayer and graphMenus;
the port and marker menus were the same list and are now one function.
Value specs of the inspector and the list tab share valueSpecs.ts, and
the remaining copies (compound commands, type glyphs, typed asset
lookup, name rule, play/editor world setup, marquee) are merged.

Four copies had already drifted apart; merging them fixes:
- "Disconnect N links" in the port menu is one undo step, not N
- "+ Add collider" is hidden on a placed container instance, as in
  the hierarchy menu, instead of always failing
- a bool property coerced from the text "false" becomes false
- a port command on an unknown node type reports instead of crashing

Comment-only edits leave the bundle byte-identical. README corrected in
35 places (step limit, jump-run clock, suite duration, 409 body, ...).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 11:00:30 +02:00
rootandClaude Opus 5 5a440ba433 Hold back an inspector rebuild while a field in it has focus
A rebuild destroys the widget being typed in, and a typed value can change the
very structure that decides whether a rebuild is needed -- a field that says
how many ports a node has. That made such a field unusable: every keystroke
previewed, the preview emitted 'project', refreshValues() saw the signature
change and threw the row away. Dragging the value worked only because a drag
keeps receiving globalpointermove on the destroyed widget.

So refreshValues() now postpones a due rebuild while the focus sits inside the
inspector, and runs it as soon as the field is left. Values are safe in the
meantime: TextField.setValue ignores an edit in progress.

Two handles for that in the toolkit. FocusManager.isWithin(node) also replaces
the parent chain refreshLive() walked by hand, and changeHandlers lets a panel
notice that the focus has gone -- there was no signal for that before.

Nothing in the editor can trigger this today, since the Sequence setting that
did is gone. It is the ground for the List tab, where a row that adds or
deletes an item changes the structure while you type. The e2e check therefore
builds the case out of real parts: type into a field, set the object's parent
from outside -- the parent is part of the structure signature -- and confirm
the input survives and the rebuild follows on Escape.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 09:11:05 +02:00
root ed1fe31c77 Count clicks ourselves so double-clicks work at all
Pixi only fills `detail` on the `click` event it synthesises; on
`pointerdown` it is always 0, so every `e.detail === 2` branch was dead
code. Asset rows, hierarchy folding and select-all in text fields never
responded to a double-click.

A shared click counter in src/ui/clicks.ts replaces the three readings of
`e.detail`. A finished double-click ends the run, so two in a row count
2 and 2 rather than 3 and 4.

The harness could only send `clickCount: 1`, which is why no test caught
this: every "activate" check went through Enter. It can send real
double-clicks now, and five checks drive the three paths separately.
2026-09-09 14:13:05 +02:00
root b87fcdfbad Mark overrides in gold instead of bold, and up the hierarchy
An inspector label whose value deviates from its base was drawn bold, which is
easy to miss next to the regular labels and says nothing in the hierarchy, where
a folded subtree could hide a change entirely.

The colour now lives as a theme token (`override`, #f2c84b) used by both the
inspector labels and the hierarchy rows. Whether a row deviates is decided by
`src/core/properties/deviation.ts`: `objectDeviates` compares an object against
what it follows -- the container template for content, the asset otherwise --
and `deviationMarks` carries the mark up to every ancestor, so a folded subtree
still shows that something inside was changed.

`deviationMarks` takes the stored holder, not the resolved one: in the resolved
view container content looks like an ordinary object and `instanceTargetOf`
would compare against the asset, marking every instance.

Covered by 3 unit tests and 5 e2e checks, including one that strips an object's
own overrides to prove the row stays gold for a deviating child and restores
them byte for byte. Full suite remote on the RTX 4070: 275 checks, all passed.
2026-09-09 10:54:25 +02:00
rootandClaude Opus 5 d54a620ebd Add ESLint and Prettier, and close the node test gaps
Tooling:
- Prettier as the single formatting authority (110 columns, single quotes,
  measured against the existing style); Markdown excluded because table-cell
  padding turns the README glossary into 1000-character lines.
- Type-aware ESLint ending in eslint-config-prettier, so a rule always means
  "this is a bug", never "this looks different".
- .editorconfig and an `npm run check` that runs typecheck, lint, format and
  tests in one go.

Findings fixed on the first run: three more duplicate imports, four
String(unknown) calls (parameter types narrowed instead of casts), two dead
initialisers, two redundant casts, a console.log for a real error. Two
warnings were false here and are disabled with a reason in place.

Tests 132 -> 269:
- node-catalog.test.ts covers every node with a run(), with a guard that fails
  when a new node has no row.
- behavior-events.test.ts covers the ten event nodes through a running World,
  checking timing rather than existence.
- Input's state machine (press/release/releaseAll) is reachable without DOM
  events now; the DOM handlers are thin adapters.

Compare fell back to == without a configured operator although the schema
default is <; aligned.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 10:02:22 +02:00
rootandClaude Fable 5.1 1eb28314cf Refactoring: one file per node, consistent comments, shared helpers, split large modules
- Graph: nodes.ts split into nodes/<category>/<Type>.ts (62 files) with
  shared.ts factories and index.ts registry; NodeContext/values/NodeRegistry
  separated, writeTyped() replaces duplicated typed writes
- Comments: one style (JSDoc on non-obvious exports, // for decisions and
  traps), wrong/stale comments fixed or removed, ids.ts now real 10 base-36 chars
- Core: chainOf, shared worldPosition/editorPosition core, commands/require.ts,
  commands/naming.ts, util/list.ts, setBagValue/toPosition/propDef; dead code
  removed (isBehaviorAsset, SCENE_OBJECT_SCHEMA, CommandStack.subscribe,
  Input.axis, setMode, titleText, World.worldPosition, unused ctor params)
- Editor: Inspector split into inspector/ sections, App.ts into Editor.ts +
  debugHook.ts + shortcuts.ts, Viewport into items.ts + symbols.ts, shared
  "Add collider" action, unified type labels/swatches
- UI toolkit: Widget.trackHover/onPointerRelease, drawControlBox, math helpers,
  Popup.openBelow, Menu/Dialog/ColorPicker/ListRow in own files, named Theme sizes
- Tools: seed-demo/smoke-demo and demo-check project removed, tools/lib
  (harness, projects, builders), e2e.mjs as runner over tools/e2e/*.mjs sections
- Tests: tests/helpers.ts replaces copied fixtures and NodeContext doubles

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-08 08:37:22 +02:00