8a4e4a676263b5e6004018bfe581977c9c3c7cc4
355
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
8a4e4a6762 |
Play sounds as notes of an instrument asset #331
A sound could only be played as it was, one at a time. An instrument
is a node graph that makes a note a sound: Notes nodes bring the note
in, Samples play sound assets for it, the effects of a sound FX shape
it and the Output sums the layers. Its tab plays it from a keyboard,
a behavior with Play note. It is what patterns and songs (#330) will
play.
- New asset type instrument, under the heading Music, not placeable.
A new one starts as Notes into a Sample into the Output.
- Notes are counted as MIDI does: 60 is C4, names like C#4, sharps
only, the lowest C-1. Notes has From and To, outputs any, note,
frequency and velocity, and with Single notes a trigger per note,
each with an optional name for a drum kit.
- New port type trigger, only into the trigger of a source. Inputs
can take any number of edges now (PortDef.many): a trigger, and
the Output, which plays everything into it side by side.
- Sample plays a sound asset. Follow note shifts it by note - root
through the pitch worklet of #317, so it keeps its length. Gate
fades it out at the end of the note, One-shot plays through. Loop
runs between Loop start and Loop end, also in the worklet.
- Parameters declares the instrument's own parameters with a
default. Sound FX sends the sound through a sound FX asset, its
parameters as inputs, the sound set in where its Input was.
- compileInstrument walks the graph once per note. A trigger is on
only for the notes it stands for, a silent source silences what it
alone feeds, a Mix with a silent side is the other side. velocity
and the parameters stay open for the voice. Builds are cached per
note until the graph or a sound or FX it names changes.
- The player takes a note as one voice of several layers, each way
with its own file and volume. release fades the gated ways out,
one-shots play on. playNote can release after a duration.
- Play note plays one note with velocity, volume, duration and
detached, and hands out voice for Set sound FX parameters and
Stop sound. Velocity is always loudness. Instrument parameters
picks values for the instrument's parameters like Sound FX
parameters does for an FX, renames follow.
- The tab has the graph, a header field per parameter, Stop, and
128 keys below. Keys without sound are grey. A key plays while it
is held. Focused, the computer keyboard plays as in a tracker by
KeyboardEvent.code, the arrows move the octave.
Findings:
- A label of the keys set fill on the shared small text style and
darkened every small label of the editor. Caught in a screenshot
before the commit, the keys have a style of their own now.
- Suite task 523 on
|
||
|
|
cf6799a784 |
Let echoes ring on with a Delay node in sound FX #324 #327
A voice used to end the moment its sources did, and Stop sound cut it dead. An echo has to outlive its sound, and a stop must not click. - Delay node: a delay line with a damped feedback loop, added at mix next to the untouched sound. Time 0.01 to 2 s, feedback up to 0.95, damping 20 kHz to 500 Hz evenly in octaves, all fed by parameters and moved by Set sound FX parameters like any other number. - Ping-pong is a setting of the node. It takes the sound into the left of the line and swaps the sides every round. - A voice with an echo rings on for its tail: until the last echo is 60 dB below the sound, from time, feedback and mix (fxTailOf). It stays reachable meanwhile, and a change counts the tail again. - The end of a tail is timed on the audio clock with a silent ConstantSourceNode, so a paused run holds it. Then the voice is disconnected, since an echo is a loop of nodes. - Stop sound fades a voice out in 10 ms instead of cutting it. Let ring fades only its sources and keeps the voice for its echoes. Where the context is suspended it cuts at once. - Stop sound takes a voice. Connected, it stops only that play, and a 0 from a Play sound that played nothing stops nothing. - Play sound hands out tail: length plus the echoes at its start values. - Destroying a behavior and ending a run still cut, echoes included. - No limit on voices. The emit of a sound FX node now also gets the node's settings. Finding: in Chrome every round of a feedback loop comes one render quantum late (128 samples, 2.7 ms at 48 kHz), measured as 0, 128, 256, 384 for the first four echoes. Left so, since a browser without it would then hear the echoes early. Written in the wiki. Tests: - sound-library, soundfx, node-catalog, sounds, keep-on-scene-load: 489 tests green. New: fade on stop, cut while suspended, echo and ping-pong wiring, ringing until the tail, tail counted again, let ring, stop by voice, echo knobs, delay compile, tail sums. - Counter-checks: no tail, a stop without fade, a connected voice 0 read as unconnected. Each fails its tests, files back by cmp. - e2e 34 (melektaus-atx): ALL PASSED, 37 checks. A blip through a ping-pong Delay (0.3 s, 0.5, 0.5) rang 2.97 s, 2.95 s worked out. Let ring kept the voice, a stop after it cut it. Rendered offline with the player's own stageNodes the echoes are 0.25 left, 0.125 right, 0.063 left. 64 stereo voices of it took about 0.3 s for 2 s, so about 400 play at once. - Gate (melektaus-atx): CHECK EXIT 0, 242 test files, 3713 tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
6e70421b92 |
Keep the length of a sound whatever its pitch does #317
A Pitch used to keep the length only for the pitch a voice started at. Set sound FX parameters changed it by speed, so a sweep made the sound longer or shorter, and length into time no longer spanned the sound. Now a way with a Pitch is played by an AudioWorklet, a phase vocoder whose pitch is an AudioParam in semitones. The length never changes, and a pitch glides, ramps and eases like a Gain. - PitchPlayer (core/soundfx) is the vocoder as a plain class, so it runs in a unit test. Frames of about 43 ms, four overlapping, a real FFT done as a complex one of half the size. - Peaks are moved with the bins around them (Laroche and Dolson's phase locking), and frames are turned by half their size so a phase is the one at the middle. Without both, a tone beat against itself and dipped to 0.54 of its loudness while the pitch moved. - It reads the sound itself rather than being fed it. It looks ahead as far as a frame reaches, so a voice starts with its first sample and every way of a Mix stays in step. A change of pitch is heard about half a frame (20 ms) late. - At exactly 0 a frame is the sound itself and costs next to nothing. Coming back to 0, the phases are pulled round to the sound's for about half a second before it switches, so the switch does not dip. - pitchWorklet (viewport) is the processor, bundled by Vite through ?worker&url. It is loaded with the first play that has a Pitch, which waits for it like for its bytes. A file's samples are sent with its first voice only and kept in the worklet's scope. - A way has the worklet when its plan has a Pitch, a typed-in 0 excepted (FxChain.shifted). A way without one stays a plain buffer source. Without AudioWorklet a Pitch is not heard, and the console says so. - The pitched copies (WSOLA, pitchShift.ts) and their cache are gone. - Decisions of the user: the player at the source rather than an effect in the chain, a phase vocoder rather than WSOLA, no limit on voices, no range tighter than the node's -64 to +64. - stats() says whether the worklet is there, levels() gives a pitch in semitones, and __gm.sound.pitchWorkletUrl() lets a check run it. Tests: - pitch-player.test.ts (new, replaces pitch-shift.test.ts): 11 tests. Pitches from -30.5 to +12 within 2 %, the length to the sample, 0 exact to 1e-5, a sweep half a frame behind, 8 kHz sounds, loops, ramps there and back without a dip. - sound-library.test.ts: 23 tests (+4), soundfx.test.ts: 31 (+1). - Counter-checks: no worklet in the player, shifted taken from the value, no pull back to 0. 15 tests fail, files restored by cmp. - e2e 34 on melektaus-atx: ALL PASSED, 34 checks (+1). In real Chrome the worklet is there and the pitch param glides to 3.0007. Offline: 880.0 Hz for 440 an octave up, 48000 of 48000 samples, a sweep 0 to -12 at 392 then 248 Hz, still 48000 samples. - Gate on melektaus-atx: 242 test files, 3690 tests, CHECK EXIT 0. - Cost: 53 ms per second of a stereo voice at +5 in Node on stein2, 5 ms at 0. In headless Chrome 16 voices for 2 s took 1.3 s, about 24 fill the audio thread. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
297fca3e94 |
Show Direction and Curve of a node only where they matter
An animation key shows Direction only for one of the ten curves and the Curve row only for Curve. Lerp and Set sound FX parameters hid them on the node body, but the inspector still offered both rows for every easing, so a linear Lerp had a Direction to pick that did nothing. - A node definition can name `idleParams`: settings that do nothing at the current values. The node body and the inspector both leave them out. - `hiddenParams` stays as it was. It only trims the node body, the inspector keeps those rows (the Order of an event, the mode of Find), so it could not take this job. - Lerp and Set sound FX parameters use `idleEasing` (formerly `hiddenEasing`) for it. - The idle settings are part of the node signature of the inspector. Picking an easing that needs Direction or Curve rebuilds the section, and the row appears. Tests: - easing-curves: 14 tests (+1), node-catalog: 381, green - e2e 06 (149 checks, green) checks that a new linear Lerp has no Direction and no Curve row in the inspector. Picking Cubic brings the Direction row, and Out is picked there as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
e7a360db73 |
Offer the easings of an animation key on Lerp and sound FX ramps
A timed Set sound FX parameters ran only linearly, and a Lerp had four quadratic eases of its own. An animation key already had the full set. Now all three offer the same choice: Linear, Step, the ten curves from Sine to Bounce with In, Out or In-out, and a drawn Curve asset. - easingParams in src/core/graph/nodes/easing.ts gives a node the fields Easing, Direction and Curve from the key's own lists, and easingOfParams reads them back as a key interpolation. - There is no migration. A Lerp saved with the old in, out or inOut runs linear until its easing is picked again. - Set sound FX parameters hands the easing to the player. Linear and Step stay ramps of the audio clock, every other easing goes to setValueCurveAtTime as 200 samples a second, in semitones and dB. - Step now reaches its end at t 1. A key never asked for that, it reads the next key itself, so animations play as before. - The curve-in-use count and the warning for a gone curve cover the sound node too. Tests: - node-catalog: Lerp with cubic in, quad out and in-out, a curve without a direction, step, an unknown easing as linear. The sound node hands on a curve, step and a gone curve as linear. - sound-library: an eased change becomes a curve in semitones and decibels, a step a jump at the end. - node-catalog and curve after dropping the old values: 399, green. - e2e 6, 34, 53 on melektaus-atx: ALL PASSED, 198 checks. In Chrome a 2 s Quad in from depth 7 to -5 read speed 0.864 early and 0.5 at the end, a 1 s Step held 1 and then jumped to 0.5. - Counter-check: with curves sent as linear ramps, the curve test of the sound library fails. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
501a1524ed |
Ramp sound FX parameters over a time on the audio clock
A pitch sweep counted down in On update or On fixed update runs in game time, while the sound runs on the clock of the audio device. A short sound magnifies any offset between the two, so the sweep came out different on every play and most of all on the first. - Set sound FX parameters has a new input time in seconds. The values run evenly to their target in that time, scheduled on the audio clock with linearRampToValueAtTime or exponentialRampToValueAtTime. - Evenly in their own unit: a pitch in semitones and a gain in decibels, an EQ band and a Mix share linearly in their value. - A time of 0 or below keeps the quick glide of about 0.1 s. - Each change starts from where the voice is now. Whatever was still planned is dropped, so a change never queues behind an older one. Firefox lacks cancelAndHoldAtTime, there the value is held by hand. - A voice still waiting on its bytes starts a timed change with the sound, so the whole sweep is heard. Tests: - node-catalog: time is handed on, a time below 0 is a quick glide. - sound-library: semitones and decibels ramp in their own steps, a timed change on a waiting voice starts with the sound. - sound-library + node-catalog + soundfx: 419 tests, green. - e2e 34 on melektaus-atx: ALL PASSED, 31 checks. A 2 s ramp from depth 7 to -5 read speed 0.979, 0.926 and 0.778 half a second on. - Counter-check: folding a timed change on a waiting voice into its start values fails the new test. - The gate did not run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
c7e9769f90 |
Change the sound FX parameters of a sound while it plays #315
Until now a value handed to a sound FX counted from the start of the voice only. An engine whose pitch follows the speed, or a loop that gets quieter, could not be built. Every play of Play sound now gives out the number of its voice, and a new node lets the knobs of that one voice glide to new values while it plays. - Play sound has a new output voice: the number of this one play, or 0 where nothing was played. Kept in a Number variable it reaches the voice from another event. Two plays of one sound are two voices. - New action node Set sound FX parameters (exec, voice, fx params). It reaches exactly the voice it is given, a detached one too. A voice that has ended, or voice 0, is not reached. - AudioSink.play returns the voice number and takes the plan and the values (FxVoice) instead of a finished chain. AudioSink.tune takes a voice number and new values. A name the voice's FX does not have is left out, one not given keeps its value. - SoundLibrary hands out the number at once, before the bytes are there, and never twice. A voice still waiting for its bytes starts with the values handed in meanwhile. - Every knob of a voice moves with setTargetAtTime (30 ms time constant). A knob whose value did not change is not sent again, so a tune every frame costs next to nothing. - A pitch on a playing voice glides by playbackRate. A new WSOLA copy in the middle of a sound is not worked out, so the voice gets shorter or longer while it is moved. On a loop that is not heard. - fxChainOf now gives a chain whose shape depends on the plan alone. A stage fed by a parameter stays at 0 dB, and a Mix fed by a parameter keeps both ways. A typed-in 0 or Mix at 0/1 is still left out by the compiler. - Every way of a voice has a source of its own, also at the same pitch, so each can glide on its own. The pitched copy is shared. - The tab keeps the numbers of what it plays, so moving a parameter field is heard at once. - Test hook __gm.sound.levels(file) reads the knobs of every voice of a file, oldest first, and stats() counts the voices tuned. Tests: - soundfx.test.ts: 29 tests (+2 for the node in a running world: the voice of the first of two plays of one sound, and a play that played nothing) - sound-library.test.ts: 15 tests (+3: glide, reach by number, pending voice) - node-catalog.test.ts: 370 tests (+2, Play sound checks its voice) - sounds + keep-on-scene-load: 31 tests, unchanged - Counter-test: a tune that reaches every voice of the same file makes the reach test fail. - e2e 34 (melektaus-atx): ALL PASSED - 30 checks (+1). In Chrome two looping voices of one sound start at depth 7. The one whose number sits in a variable glides to 0.7938 (2^(-4/12) = 0.7937), the other stays at 1. Both gains stay at 0.5012. - The gate ran before voices got a number (CHECK EXIT 0, 241 files, 3658 tests), not after. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
072537b2e8 |
Blend two ways of a sound FX with a Mix node #316
A sound FX was a single line from Input to Output, so two versions of one sound could not be heard together. The new Mix node joins two ways again, and each way can carry its own effects, its own Pitch included. - Mix has the audio inputs a and b and a number input mix (0 to 1, default 0.5). At 0 only a is heard, at 1 only b. Like every number it can come from a parameter of the Input and is held to its range. - The compiled plan and the chain a voice gets are now a tree. A source carries a pitch and stages, a mix carries a, b and the stages after it. A Mix that ends at exactly 0 or 1 collapses to the one way heard, so the silent way is never worked out. - A Pitch is still worked out on the sound as it starts (#312). Behind a Mix it goes to both of its sounds. An effect before a Pitch therefore acts as if it came after it, the node now says so. - The player starts one source per pitch, all at once and all of the same length, and two ways at the same pitch share one. A mix becomes two gain nodes with 1 - mix and mix into a sum. A voice leaves the count when all of its sources have ended. - stats().last reports pitches (one per source) instead of pitch. Tests: - soundfx.test.ts: 27 tests (+5), sound-library.test.ts: 12 (+2), node-catalog.test.ts: 368, all green. Typecheck, ESLint, Prettier clean. - Counter-check: a Pitch behind the Mix given to a only, and b mixed at 1 - mix, each fails its new test. - e2e 34 on melektaus-atx: ALL PASSED, 29 checks (+1). The new check plays the pitched way mixed with the dry Input in the browser: two sources at 5 and 0 semitones, as long as the sound itself. - The full gate did not run before this commit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
790e483198 |
Pitch a sound FX without changing its length #312 #313 #314
The Pitch node changed the playback speed, so a sound pitched up was also shorter and one pitched down longer. A pitch now keeps the length, takes fractions and reaches further. Gain and the EQ bands reach further down, and the EQ is renamed so a parametric one can come later without a mix-up. - Pitch shifting is the pure function pitchShift in core/soundfx/pitchShift.ts. It reads the sound faster or slower and stretches it back by WSOLA: 40 ms frames under a Hann window, each moved within 12.5 ms to where it continues the frame before it. A looping sound reads on across its end. - SoundLibrary plays a pitched copy of the decoded buffer instead of setting playbackRate. Copies are kept per file, pitch and loop, the last 32 of them. - stats().last tells the duration of what was played, so a check can see that the length is kept. - Pitch: -64 to +64 semitones, scrubbed in steps of 0.1 (#312). - Gain: -120 to +24 dB (#313). - EQ is now labelled "5-Band-EQ", its bands go -60 to +24 dB (#314). The node type stays SoundFxEq, so saved graphs load unchanged. Findings: - A plain granular shifter was tried first and missed the pitch: at +0.5 semitones it measured 438 Hz instead of 452.9 Hz, because every grain restarts at the phase of the original. WSOLA measures 452 Hz. - Far ends: +64 semitones from 440 Hz lands above half the sample rate and folds back, -64 is below what a 40 ms frame can follow. Nobody has listened to either. - Speed: 3 s of a 48 kHz channel take 47 ms at +3 and +64, 171 ms at -64 (measured in Node). Tests: - pitch-shift (new): 7 tests. A 440 Hz sine at +12, -12, +7, +0.5 and -30.5 semitones is within 2 % of the target and keeps its length. Level stays at most 1, a loop does not fade at its seam. - sound-library: 10 tests (+1), soundfx 22, node-catalog 368. - Counter-check: with the pitched copy switched off the new sound-library test fails. - e2e 34 (melektaus-atx): ALL PASSED - 28 checks. Its octave-down voice is now also checked for the length of the sound. - Typecheck, ESLint and Prettier clean. The gate did not run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
45e45fb1df |
Count the Sound FX type in the + menu check
The suite run on |
||
|
|
183f356c99 |
Let a game hand values to the parameters of a sound FX #311
A sound FX played the same every time: its numbers were settings of the nodes. Now the Input declares parameters with a default, every number of Gain, Pitch and EQ is an input they can feed, and a Play sound hands in values for them. - Input declares number parameters in the inspector, each with a Default (DeclaredPort.default, SetPortDefaultCommand). They are outputs of the Input. Input and Output count as terminals now, one of each per graph. - Gain, Pitch and EQ take their numbers at inputs under the keys the settings had, so saved graphs sound as before without a migration. Each value is held to its range however it arrives. - The compiler gives an FxPlan with the parameters still open. fxChainOf fills them in when a voice starts. The player still only sees an FxChain. - New behavior node Sound FX parameters: names an FX and picks its parameters like a Variables node picks variables. Each picked one is a number input that starts at the FX's default. Its output has the new port type fxparams, which only fits Play sound's fx params. - Play sound warns when the parameters name another FX than the one it plays. Names the FX does not know are left out. - Renaming a parameter at the Input renames it in every Sound FX parameters node naming that FX, edges and values included. - The Sound FX tab has a number field per parameter in its header. Play uses those values, they are never written into the asset. - A value counts from the start of a voice. Changing a playing voice is #315, a Mix node is #316. Tests: - soundfx.test.ts 22 (+11): parameters in the plan, held values, a 0-by-default stage, EQ band, save with defaults, wrong port types, default command with undo, Sound FX parameters into Play sound, defaults of picked ones, node problems, mismatch, rename with undo - node-catalog 368 (+3): Play sound fx params, Sound FX parameters - e2e 34: ALL PASSED - 28 checks (+2: header field plays depth 5, a Sound FX parameters node gives the game's voice depth 7) - Gate on melektaus-atx: typecheck, lint, prettier clean, 240 test files, 3636 tests green - Counter-check: with fxChainOf ignoring the values and the rename not followed, 5 of the new tests fail. - Browser probe: header fields, inspector Default and the picked parameter looked at in two screenshots. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
de14f0f9e2 |
Play a sound through a graph of gain, pitch and EQ effects #311
A sound could only be played as it was uploaded. A Sound FX asset is
a node graph like a shader: Input, effects, Output. Play sound has a
new input fx that sends the voice through it.
- New asset type soundfx ("Sound FX"), not placeable, with a graph
and its own tab. The heading in the asset panel is "Sound effects",
so it does not take the click meant for the + menu entry.
- Nodes in core/soundfx/nodes.ts, category sound, port type audio
that only fits audio: Input, Output, Gain (dB), Pitch (semitones),
EQ (80 Hz and 12 kHz shelves, peaks at 250 Hz, 1 kHz and 4 kHz).
- compileSoundFx walks back from the Output with the shared graph
emitter. An input takes one edge, so the result is one line: a
pitch and a list of gain and filter stages. A node without input
or a graph without Output reports a problem on the node.
- Pitch is the playback rate of the source. It applies to the whole
sound and also changes its length, the node description says so.
- The BehaviorSystem resolves fx and hands the line to the AudioSink.
An fx that is missing or does not compile plays the sound dry.
- SoundLibrary wires source, stages, volume, destination in that
order. stats().last now also names pitch and stage count.
- The tab header picks one of the project's sounds and plays it
through the graph as it stands. Stop there ends only its own voice.
Tests:
- soundfx.test.ts (new, 11), sound-library (9, +2 for the wiring),
node-catalog (+1 for fx), graph and asset-groups adjusted
- e2e 34: ALL PASSED - 26 checks (+5: + menu opens the tab, palette,
2 effects, tab Play and game Play through the FX at -12 st)
- Gate on melektaus-atx: typecheck, lint, prettier clean, 239 of 240
test files green. The red one was sound-library (its fake
AudioContext had no playbackRate), fixed afterwards, gate not
re-run in full.
- Counter-check: without the audio rule in portsCompatible the port
test fails.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
a5184b3368 |
Let the rows of a Variables node be dragged into a new order
The Variables and Properties nodes listed their picked rows in the inspector, but unlike Start, End and struct rows they could not be reordered. The drag dots also sat right on the edge of the row and touched the kind mark. - buildPickedPortSection gathers each row with its warning line as one block and hands the blocks to sortBlocks. A move reorders the ports on the node and is one undo step. Properties uses the same section and gains the same reordering. - Grip draws the dots 3 px in from the left edge and moves the kind mark 3 px clear of them (DOTS_PAD). The column keeps its width, so the name fields do not shift. Rows without dots are unchanged. Tests: - block-drag, reorder, variable-choices: 3 files, 13 tests, all green. block-drag now expects the mark 11 px in with dots, 0 without. - e2e 23 on melektaus-atx: ALL PASSED, 32 checks (+3 for dragging vy before vx and undoing it). It ran before the padding change, which touches no checks there. - The gate did not run before this commit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
2da07599d8 |
Let Get position give only the local x and y
Get world position now covers the world coordinates. Two nodes that both offer world x and y made it unclear which one to take. No project uses the world outputs of Get position any more. - Get position drops its world x and world y outputs and its description points to Get world position. - Old snapshots with edges on those outputs still load. The edges hang on ghost ports and the node warns about them. - The unit tests in hierarchy and raycast and the graphs in e2e 43 use Get world position where they read the world coordinates. Tests: - node-catalog, hierarchy, raycast: 3 files, 417 tests, all green - e2e 43 on melektaus-atx: ALL PASSED, 17 checks Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
3f12a77aea |
Preview a sprite at the end of its inspector, placed or as an asset
A sprite's inspector listed its rows but never showed what they add up to. The picture, its frame, nine-slice, tint and shader could only be judged in the scene, often far from the object being edited. - SpritePreview draws the sprite through syncSpriteMesh, the function the scene draws it with. Slices, padding, tint and shader come out the same without a second path. syncSpriteMesh now takes only the three fields of a DrawItem it reads. - The subject is resolved by previewPicture from the Shader tab, so a placed sprite shows its own overrides. - previewFit is the rule for size and place: whole device pixels per unit from 1 on, at most 8, and the corner on a whole device pixel. Pixel art stays sharp. A sprite larger than the group shrinks freely. - A shader with a Time node keeps the preview redrawing once a frame while it is on screen. A folded group or a hidden panel stops it. - New test hook __gm.inspectorSpritePreview(). Finding: the preview redrew on a model change in a microtask at first. A drawn picture whose revision moved on is retried once, in a microtask queued by the scene's synchronous redraw. That retry ran before the preview had let go, and the old pixel art sheet stayed waiting (e2e 48l-iii read waiting [1,1,1]). The preview now redraws at once on a model change, as the scene does. Tests: - unit: 238 files, 3592 tests (+5 in preview-fit.test.ts), and the group list of a placed sprite in sections.test.ts ends in PREVIEW - e2e 48: ALL PASSED, 47 checks (+2: 48l-i asset preview with the frame, 48l-iii placed preview with its shader values) - counter-checks: without whole-pixel zoom 2 unit tests fail; with the microtask redraw 48l-iii fails with waiting [1,1,1] Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
1665576fb9 |
Let a pixel perfect camera draw on the game view's pixel grid
A camera that follows smoothly stands between pixels most of the time, and pixel art then wanders between them. The new camera switch draws what the camera shows on the game view's own grid, while positions in the model keep their fractions. - `Pixel perfect` is a camera row, off by default. It works at every fixed game scale and at Auto, but not at Fit and not in the Scene tab, where one works with the fractions. - The grid has one step per device pixel of a world unit. At 100% that is whole units, at 500% fifths. The view carries it (`View.grid`), so no view draws with the grid of another. - The view looks from a point that puts the world's origin on a whole device pixel (`lookFrom`). This also covers odd camera sizes and views that cut the picture to an odd size. - Sprites, texts and tilemaps snap their top left corner when they are unturned, so an odd size lands on whole pixels too. A turned object snaps its pivot (`snapPlacement` in `pixelGrid.ts`). - Under a camera shader the picture is taken the same way, and the objects in it use the picture's grid. With Camera pixels that is one step per camera pixel. - An overlay camera has its own switch and snaps only while the main picture is at a fixed scale. - Particles are drawn where they are. - New test hook `__gm.gameDrawnRect(id)` reads where a sprite's picture is drawn, which `gameRect` (the model's) cannot tell. Found: the e2e fixture at 300% drew the crate at y 205.5 device pixels without moving the camera, because the view cut to an odd height puts its centre on half a pixel. With the switch on it is at 206. Tests: - Gate: 237 files, 3587 tests (+10 in pixel-grid.test.ts) - e2e 2,3: ALL PASSED, 75 checks (+1, 3j) - Counter-checks: without the origin snap 3 unit tests fail, with the centre snapped instead of the corner 2 fail Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
92395f6773 |
Find the render layer log pair in e2e 63c3 instead of the tail (#305)
The suite went red three times in a row (tasks 488, 491, 492) on one check only: 63c3 read `named: ["",""]` while the picture half passed. Alone the module was green. In the full run objects left by earlier modules are still in the scene, and their behaviors log two empty lines after the OnStart of the check. `log.slice(-2)` took those. - The check now searches the log for the pair "UI", "1". Both lines come from one exec chain, so they stand next to each other. - Without the pair it shows the last 6 lines for diagnosis. - Not found out: which earlier module writes the empty lines. Tests: - e2e 63 on melektaus-atx with a temporary behavior that logs two empty lines after the switch: ALL PASSED, 13 checks. - The same with the old `slice(-2)`: 1 FAILED, `named: ["",""]`, the picture from CI. The disturbing behavior is removed again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
3f57cf731d |
Keep a pixel frame worn through a shader alive while it is drawn
Changing a palette colour of pixel art flooded the console with Pixi warnings: "[BindGroup] a 'textureSource' was destroyed while still bound to a shader", and the same for its sampler. #293 had stopped this for a drawing, but a frame of pixel art is worn as a cut of the sheet. The cut has a source of its own, so the sheet looked unbound, was freed at once and took the cut with it under the shader. - The check whether a replaced picture is still in use gets its key too. TextureLibrary then asks the picture and every cut taken from it. - A sheet with a bound cut waits like a bound picture did since #293, and both go together once the shader has taken the new frame. Found on the way: - The project the report came from has a sprite wearing a frame of an eleven-frame pixel art through a shader asset. - A nine-sliced sprite wearing the frame showed no warning, even without the change. The first try of the e2e check used it. Tests: - Gate on melektaus-atx: 236 files, 3577 tests (+1) - e2e 48: 45 checks (+1, sum 1741). 48l-iii puts a shader on the object wearing the second frame, changes a palette colour three times and counts the warnings. - Counter-checks bite: without the cuts in the check the unit test fails, and 48l-iii counts 6 warnings for 3 changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
c74b6b1cab |
Show where an inspector row can be dragged and answer the pointer
After #310 rows could be dragged by their mark, but nothing said which marks take a drag, and the pointer on one changed nothing but the cursor. - A mark that drags is now a Grip: the same widget wrapped, the same size, so the value column stays where it was. Once sortBlocks takes it, it carries six faint dots at its left edge. - Marks that cannot drag stay plain: the variables of a placed object and the fields of a typed struct show no dots. - With the pointer on a grip its dots light up and the whole block that would move is washed in the accent colour, a struct with its fields. While the block is carried the wash gives way to the dimming, and the grip stays lit. - While a block is carried the other grips let the pointer pass: no lit dots, no wash. Every grip of the column shows grabbing instead of grab until the drop. - The wash first used controlHover like list rows. In the inspector it is darker than the panel and vanished next to the dark fields, so it became the accent at 15 percent, the colour of the drop line. - __gm.inspectorMark also reports the dots: none, idle or lit. Tests: - Gate on melektaus-atx: 236 files, 3576 tests (+4 in block-drag.test.ts, one of them a drag passing over another grip). - e2e 22: 40 checks (+1, sum 1740). 22f-iii reads idle dots, moves the mouse onto the mark and reads them lit. - Two counter-checks bite: no wash on hover fails 1 test, grips never made grabbable fail 3. A third: hover ignoring the drag fails 1. - Screenshots of a lit single row and a lit struct block looked at. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
2c19c89878 |
Let variables, fields, ports and list items be dragged into order (#310)
Variables of a behavior, the fields of a struct, the ports of a start or end node and the items of a list could only be added and removed. Changing their order meant removing and adding them again. Now each of them is dragged by the mark in front of its row. - sortBlocks (ui/blockDrag.ts) makes the rows of one column sortable. A block is every row it covers, so a struct moves with its fields and a list item with its nested list. The block is dimmed while it is carried, an accent line shows where it lands, and a scrolled panel follows the pointer at its edge. Less than 4 px stays a click. - The arithmetic is pure and lives in ui/sortable.ts: blockGap, gapLineY, dropSlot and edgeScroll. dropIndex of the tile strip moved there too, so the reorder maths sits in one file. - MoveVariableCommand, MoveStructFieldCommand and MovePortCommand build on the existing MoveCommand. Undo moves back. - Lists need no command. InlineList replaces the list as a whole, as it does for every other edit of its items. - The grip is the kind mark, for a list item its number. Moving only happens among siblings: a field stays in its struct. - On a Struct asset the move runs through WithStructTypes. Typed variables, Pack/Expand ports and split outputs follow the new order. - Values and edges hang on names, so only the order changes. Nodes running a behavior read their ports from the start node, the new order reaches them without a copy. - Not sortable by design: the variables of a placed object, the fields of a typed struct (moved on the Struct asset) and the picked ports of Variables/Properties nodes. - Test hook __gm.inspectorMark(label) finds the mark of a row. Tests: - Gate on melektaus-atx: 235 files, 3572 tests, CHECK EXIT 0 (+9). - tests/core/reorder.test.ts: a variable, a field two levels deep, a Struct asset reaching a typed variable and Pack, and start ports reaching the calling node with edges and result kept. - e2e 22: ALL PASSED, 39 checks (+4, sum 1739). 22f-iii drags a variable past a struct of several rows, then a field inside it. - Counter-probes: dropSlot by top edge instead of middle and a field move that ignores its path fail 3 unit tests. With sortBlocks switched off, the three order checks of 22f-iii fail. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
cf6043a066 |
Show a wheel step or Fit as a short zoom transition (#309)
The wheel and the Fit button used to jump to the new view. Now the view takes a short way there, 200 ms by default, everywhere the editor zooms. The zoom steps stay the same, only the change is shown. - viewTween: tweenView is the way between two views as a pure function. The scale moves by equal ratios, and the world point that sits at the same place on screen in both views stays there. After a wheel step that is the point under the pointer. - ViewTween drives it on the ticker. A surface hands it reading, writing and drawing its own view and wraps its wheel and Fit code in change(). A step during a transition starts from where that one was heading, so quick steps add up to exactly as many steps. - Anything else that moves the view ends the transition. A pan jumps to the target first, and saving a view saves the target. - Wired into FreeViewport (Scene, Container, animation preview), SheetCanvas (Texture, Curve, Pixel art, Vector art), GraphCanvas, panAndZoom (Tile board, plugin node board) and the timeline. - Menu > General settings... sets the time in ms, 0 jumps at once. It is kept in this browser like the panel sizes, not in the project, and it is not an undo step. - A document that opens still comes up framed at once. The animation preview frames a new subject itself instead of calling fit(). - The pixel art readout names the step the view is heading for. Measured in the scene view at 200 ms on melektaus-atx, where every frame of the transition is a full redraw: - 500 sprites: redraw 6.7 ms, 16 frames, mean 18 ms - 1000 sprites: redraw 13.0 ms, 9 frames, mean 27 ms - 2000 sprites: redraw 32.2 ms, 5 frames, mean 58 ms Tests: - The harness sets 0 ms on every load, so the existing modules read the step and not the way. - e2e 46i samples one wheel step at 1000 ms a frame later: zoom 1.033 between 1 and 2, the point under the pointer kept at (134, -40). It lands on the view a 0 ms jump reaches. - e2e 46 and 49 expect the new menu entry. - Gate (atx): 234 files, 3563 tests, CHECK EXIT 0 (+15) - e2e 46,49 (atx): ALL PASSED, 43 checks (+3, sum 1735) - Three counter-checks fail view-tween.test.ts as expected. e2e 46i fails when the scene view does not carry its centre along. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
3e05cbbed5 |
Let pixel art and vector art be nine-sliced and sorted (#308)
Nine-slice and the sort line hung on the texture frame alone. Drawing and sorting already read both off the TextureRef every picture hands out, but the generated pictures always said "no slice, no line". So the feature moves from the frame to the picture, without a second copy of any rule. - PictureCut (border, fill, sortLine) is one type. TextureFrame extends it, PixelArtAsset and VectorArtAsset carry it as a whole. The texture file format does not change, and no migration is needed. - The rules in texture.ts take a cut and a size: cutSortLine, setCutSlice, rawCutSlice and the rest replace the frame* versions. serialize.ts reads all three through one readCut. - cutTarget.ts resolves a target to cut and size: a texture frame, a pixel frame's size or the vector canvas. It lives apart from texture.ts because vectorart.ts reaches texture.ts itself. - SetSliceCommand and SetSortLineCommand take that target instead of (assetId, frameId). They refuse an asset that draws no picture. - generatedRef fills slice and sortLine from the drawing. The whole strip of a pixel art asset with several frames gets none, it is no picture of that size. With one frame asset and frame are the same. - Nine-slice scale is one shared schema row, now also on pixel art and vector art. - cutSection.ts holds the NINE-SLICE and SORTING rows once. The frame inspector and the drawing inspector both build them. - SheetCanvas draws a drawing's lines that are set, dashed as in the Texture tab. They are not dragged there, the painting tools own the canvas (as proposed in the issue). - drawOrder falls back to the picture's height when a ref has no rect, or a sort line on vector art would have been ignored. Tests: - Gate: 232 files, 3542 tests (+8: drawing-cut.test.ts, and vector art in sort-by-y.test.ts). Three counter-proofs bite: no cut on a drawing, a cut on the whole strip, no height fallback. - e2e 48: 44 checks (+1, sum 1732). 48l-ii reads red and blue where 12-pixel borders at scale 2 put them. Without borders both spots read green. - Looked at: the Pixel art tab with the lines, and the sliced sprite. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
d9c6bbfa4d |
Add a node that names the render layer of an object (#305)
The issue asked for Set render layer, Set sees and Render layer of. As with #211, a probe showed that writing already works. A Properties node of the object's type writes Render layer during a run, Inherit included, and one of type Camera writes Sees. The next picture follows both. As agreed, only Render layer of is built, and writing stays with Properties. - Render layer of is a data node with the input object and the outputs name and number. It answers the layer that holds, so an object without a choice of its own names its parent's. - NodeContext.renderLayerOf answers the number with the same rule the drawing uses (renderLayerAlong). The new field renderLayers on GraphLookup brings the names to the node. - hasRenderLayer in PropertySchema says which types are drawn. - Decided here: for an object that is not drawn (a camera, a behavior, a collider) the name is empty and the number -1, as Layer of does without a collider. Changing the render layer or Sees during a run had no test so far. Three unit tests now hold the proof from the issue. Tests: - Gate: 231 files, 3534 tests (3526 + 8), exit 0 - e2e 6: 149 checks passed, e2e 63: 13 checks passed (+1) - Unit: the coin moves into the overlay with Set and back with Inherit, a HUD container takes its sprite along, the main camera sees a second layer once its Sees is set, and Render layer of names the inherited layer, and none for a behavior - Node catalog: four cases for Render layer of - 63c3 moves a sprite onto UI in a Play run: the main camera no longer draws it, and the log reads "UI" and "1" - Three counter-probes fail 1, 1 and 2 tests: without inheriting in the node, without the check for a drawn type, and without inheriting in the drawing - "layer of" in the palette still finds Layer of first (6z-x) - The e2e sum is 1731 (1730 plus 1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
70f3608733 |
Let Mouse position and Is hovered see through any camera (#304)
An overlay camera from #302 can stand elsewhere and zoom differently.
A HUD button only it shows lay somewhere else in the world than the
mouse of the main camera, so Is hovered never found it. Both nodes now
take a camera and see the mouse through that camera's picture.
- New input `camera` on Mouse position and Is hovered. Left open, both
read the main camera exactly as before.
- `pointerThrough` in core/properties/camera is the rule as a pure
function. It fits the camera's picture into the main one and centres
it, the same way overlayView draws it.
- The game view reports where the mouse lies on the main picture, as a
part of its width and height. The world turns that into a camera's
view, so a main camera on the move does not drag the mouse across
the HUD while the mouse stands still.
- `in view` is true only on the camera's own picture. A camera that is
not drawn (switched off, a second plain one, not a camera) never has
the mouse on it.
- On click and Mouse button stay with the main camera, as agreed in
the issue.
Also fixes a unit test that
|
||
|
|
1d0fa7b947 |
Let the game view zoom up to 20x
A small camera on a large screen stopped at 6x. The steps now go on in whole numbers up to 20, both in the zoom menu of the Game tab and for Auto, which takes the largest step that fits whole. - GAME_SCALE_STEPS in playSettings.ts runs 0.25, 0.5, 1 to 20. Prettier wraps the longer line. - e2e 60 keeps its own copy of the steps to work out what Auto should pick. The copy follows the new list. Tests: - view.test.ts: 23 passed (every step is used as its own factor) - e2e 60: ALL PASSED - 19 checks (Auto still picks 4 at 1280x800) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
b895ae99c0 |
Show the graph in the e2e 6 screenshot of the long name (#306)
The check for the long animation name ran after a play and stop, which leave the editor on the scene tab. Its screenshot showed the scene, not the node. The check itself read the row through the test hook and was not affected. - The step switches back to the graph tab before it renames the animation. Tests: - e2e 6: all passed, 149 checks. The screenshot shows the node, with "animation Swi…" ending before a whole "progress". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
2aa635f453 |
Keep an output's name readable beside a long inline value (#306)
An input and an output in the same port row were cut to fit each on its own. The input text could reach x = 123 and the output label could start at x = 83, so a long inline value ran under the output's name. At Animation playing a long animation name covered "progress". - The output label is placed first and keeps its room. The input text is cut with an ellipsis 6 px before it, as decided in the issue. - The rule is a pure function in src/editor/graph/portRow.ts. A row without an output keeps the input's old limit of 108 px. - The value a run shows beside an output uses the same rule. Its left edge can no longer lie right of its right edge. - New test hook __gm.graph.rowSpans(nodeId) gives each row's input and output text as cut and placed, in node units. - NODE_WIDTH stays as it is, as decided in the issue. Tests: - Gate: 230 files, 3520 tests (6 new in port-row.test.ts). - e2e 6: all passed, 149 checks (148 + 1). - Counter-check: with the rule switched off, the input text reaches x = 121.4 under "progress" at x = 114.1, and the new check fails. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
a40e629fde |
Make e2e 56 pass on its own, not only after module 55 (#307)
Check 56a-ii clicks the read-only value of a private variable and types into it. A read-only field takes no focus, so typeText found no input and threw when the module ran alone. In the full run the keystroke went into whatever input an earlier module had left focused, and the check was green by luck. - New harness helper typeBlind sends the keystrokes without checking where they land. It is meant for keystrokes that should be refused. - 56a-ii now also checks that no field holds the focus after the click (__gm.focusedRect() is null), the same way module 28 checks its read-only Asset row. - No other module types into a field that is meant to be locked. Tests: - e2e 56 alone: ALL PASSED, 10 checks (was E2E EXIT 1 before) - e2e 55,56: ALL PASSED, 27 checks - Counter-check with the second lock click left out: 56a-ii fails with a focused field and timer 9, three later checks follow - Gate: 229 files, 3514 tests, CHECK EXIT 0 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
808e1eefc2 |
Clean up duplicates and dead code, and fix the + menu check (#85)
Refactoring round 9 covers everything since round 8 ( |
||
|
|
a526e1b40f |
Use a shader graph as a node in other shaders (#303)
A calculation needed in several shaders (a greyscale, a noise, an outline) had to be built again in every one of them. A shader function keeps it as an asset of its own, called like a behavior is since #16. - New asset type `shaderfunction` ("Shader function"), edited in the Shader tab. The panel beside the graph shows no picture, the readout counts its inputs and outputs. - Two new nodes: `Function input` and `Function output`. Their declared ports, numbers or colours only, are the ports of every call. One of each per function, like Start and End. - The call is node `ShaderCall`, named after the function. The palette lists every function, it can be dragged in from the Assets panel, and a double-click opens it. - The compiler sets the function in where it is called (`inlineFunctions`). It renames the copied nodes `<call>/<node>` and turns an unconnected port into a constant. Passes, Time, the Edge cut and the corner walk then see a plain graph. - A problem inside a function is reported at the call, in the function's words. The function's own tab checks it on its own (`checkShaderFunction`). - `ShaderLibrary` keys a shader on its graph and on every function it calls, however deep, so a change reaches every caller. - The port commands mirror a new, renamed or removed port into every call, as for a behavior's Start and End. `NodeDef.portKinds` holds the two ends to Number and Color. - As agreed in the issue: no `Property`, `Color property`, Output or Vertex output in a function, and `Time` is the caller's clock. - Own decision: a `Pass` may stand in a function. It becomes a pass of the calling shader, so a wide blur can be a function. - A function calling itself, also through another, says so at the node and is not set in. The palette does not offer such a call. - The Shader tab showed only the compiler's problems. A call nothing reaches was never compiled and looked healthy, so a call now also shows its own problem there, as in a behavior. Tests: - Gate: 228 files, 3510 tests, CHECK EXIT 0 (+13: 11 in tests/core/shader-functions.test.ts, 2 for the new icon) - e2e 28: 100 checks, all passed (+3 in 28l-viii). Called and in place both #606060/#a2a2a2, halved in the function #303030/#515151. - Counter-check: a fingerprint without the called functions fails 1 unit test, and in the browser the change check (#606060 stays). - The first e2e runs found two faults, both fixed: a double-click only selected the function, and an unreached call of itself showed no problem. - e2e total 1728 is computed (1725 + 3). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
2e55071fa5 |
Lay a shader over a camera's whole picture (#299)
A shader so far worked only on its own wearer. Effects over the whole game picture, such as a vignette, grey, scanlines or a red flash on a hit, had no place to live. A camera now wears one, drawn the way a field shader of a particle source is (#170). - The camera gets a Shader row and, while one is picked, a Shader resolution row. The shader's properties follow as camera rows. They load, animate and appear in a Properties node under Type Camera. - Every view keeps masks and objects in one container. CameraShade renders it into the camera's picture, cleared to the background, and draws the shader over the frame. Game tab and play.html take the container off the stage while the picture stands in for it. - Screen pixels gives the picture as many pixels as it covers on screen. Camera pixels uses View width x View height and scales up without smoothing. - A pixel counts as a camera pixel in both (the user's choice). The uniform uPixelScale makes Texture size read View width x View height, so a Blur keeps its reach at any game scale. - Each camera wears its own shader, overlay cameras included. A red flash on the game camera leaves the HUD of an overlay camera alone. - The scene view's eye has a Shader switch on the Camera row, off by default. With it on, the main camera's shader lies over its frame, below symbols and gizmos, and the world around it stays drawn. - New tool tools/perf-camera-shader.mjs measures the cost. Measured on stein2 (HD 630, Game tab at Fit 1310x679): a camera shader that hands the picture through stays inside the scene's own spread of 3 to 5 fps between runs. A Blur of 4 cost 4 to 8 fps. Tests: - Gate on melektaus-atx: 227 files, 3497 tests (3487 + 10). - e2e 63 alone: 11 checks (6 + 5), e2e sum 1725 counted. - 63e-63i: pass-through is byte-equal and upright at 100%, flat red reaches the corners while the overlay keeps its green, Texture size reads 640 x 360 in both resolutions at Fit, no shader draws as before, and the scene view shows it only with the switch on. - Counter-checks: a camera left out of the shader wearers fails 1 unit test. Counting screen pixels fails 63g with #ab6000. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
b2326085ed |
Show each camera only its layers and lay overlay cameras on top (#302)
A camera could only show the whole scene, so a HUD drawn in the world would run through a camera shader (#299) with everything else. Render layers say which cameras see an object, and overlay cameras draw their own picture over the main one. - The project has a list of up to 16 render layers, edited in Project settings next to the collision layers. It is a list of its own, read by the same rules (`layerNamesOf`). - Sprites, texts, tilemaps, particle sources and containers carry a row `Render layer` under their switches. Its default `Inherit` takes the parent's layer, at the top the first one. - A camera has `Sees` (all layers by default) and `Overlay`. The main camera is the first one switched on without Overlay, so two plain cameras still draw through the first one only. - Every view through a camera keeps only what that camera sees and lays one see-through view per overlay camera over itself. That view is a `WorldViewport` of its own, handed its view and frame from below. - An overlay is fitted into the main camera's whole picture without distortion. One of the same size and zoom covers it exactly. - `z` sorts within one camera only. The pictures lie on top of each other as a whole, which is what a shader per camera (#299) needs. - The Game tab's preview now collects the scene once per redraw. Before it collected it twice, with the cameras it would have been five times. - `SetCollisionLayersCommand` became `SetLayerNamesCommand`, which takes the list it sets. Follow-ups: #304 (the mouse through a chosen camera) and #305 (change the render layer at run time). Tests: - Gate: 226 files, 3487 tests (3480 + 7) - e2e 63 (new): 4 checks. A green overlay sprite at z -10 covers a red main sprite at z 1000, in the preview and in the running game. A `UI` sprite in front of the main camera and a `Default` one in front of the overlay stay away. Without Overlay the second camera draws nothing. The settings dialog adds a render layer at the end. - Counter-checks: inheriting from parents switched off fails 1 unit test. Overlays never drawn fails the two overlay checks in e2e 63. - e2e total 1720 (1716 + 4), computed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
f03ca0d382 |
Draw a shader in passes with a Pass node (#301)
A Blur stops at radius 8 in one pass, and bloom needs a softened copy of the picture beside the original. A Pass node cuts the graph: what feeds it is drawn into a picture of its own first, and the nodes that read a picture read that one when it feeds them. - Pass has color and alpha like the Output, a Resolution setting (Full, Half, Quarter) and a picture output. Texture color, Texture alpha, Sample texture, Blur and Texture size get a picture input. Unconnected it is the wearer's own picture, so the original stays readable beside any pass. - picture is a port type of its own. It plugs only into a picture input, not even into an any port. - The walk shared by the compilers can stop at a node (cut). The shader compiler draws each Pass it meets as a fragment of its own, earlier passes first. A ring through a picture says Feeds itself, and more than 8 passes say so at the node. - A pass draws untinted. The tint comes on when the last part reads a picture, so a particle source draws its passes once for all of its particles (user decision). - A pass's picture is the wearer's picture and its Padding, at the pixels of the picture times Resolution, at most 2048 per side. It is read with the picture's own filter and continues past its frame as the Output's Edge says. - PassPictures keeps one render texture per pass and wearer. It draws again only when picture, size, Padding or a uniform changed. A field's picture is a render texture that changes under the same id, so a field draws its passes every time. - Sprite, sliced sprite, text, particle source, field shader and the Shader tab's preview all draw passes. syncShader takes a fixed picture for a 9-slice, which holds its corners but keeps the margin. - texelAt and blur take the sampler they read. Existing shaders compile to the same program apart from that argument. - A field shader's Blur and Texture size count a pixel as a unit of the frame (uPixelScale), not of the screen. Before, a field's blur grew when zooming out and shrank when zooming in. This came with #170 and #300 and is fixed here because passes inherit it. - tools/perf-shaders.mjs and tools/perf-field-shaders.mjs measure two passes with GM_PERF_PASSES=1 and GM_PERF_PASS_RESOLUTION. Below Full an open Pass shrinks the picture first, as the wiki's recipe does. Found on the way: - Destroying the pictures before the shader made Pixi warn that a texture was still bound. The shader goes first now, and a picture of a new size is resized rather than replaced. - Two passes in a row could draw plain white the first time. Pixi builds the code that binds a program's resources once and finds them by position. A pass of one graph is often the same program as a pass of another with fewer slots, so it bound a placeholder. Every shader now declares all 8 pass slots and its uniform group. Tests: - Gate on melektaus-atx: 225 files, 3480 tests (3467 + 13), exit 0. - e2e 28 and 31 on melektaus-atx: 97 checks (94 + 3) and 44 checks (43 + 1), all passed, no Pixi warning in the console. - 28l-vii: a Blur across, a Pass and a Blur down draw the 5x5 spot of a Blur of Both (difference 0.000). On a grey sprite a pass's picture added to the original is 231/60/60, each tinted once. Two Passes in a row after a graph with one show red and green, not white. - 31v-iii: a Blur of 8 fades a field's frame edge over 8.0 units at 50 % and at 200 % zoom. - Counter-checks: a tinted pass fails 1 unit test. Pictures never bound fail both e2e checks (spot 0.20 instead of 0.04, 243/158/158). Pass slots by the number of passes draw #ffffff, and a field without its pixel scale ramps over 16 and 4 units: exactly the two new checks fail. - e2e sum 1716 is counted, not yet measured (1712 + 4). - Measured on stein2 (HD 630, one run per row): 50 field shaders at radius 8 go from 11.4 to 41.3 fps in two passes, 44.4 at radius 16 (three passes at Half). 800 sprites at radius 8 go from 13.9 to 24.5 fps. At radius 4 one pass stays faster from 400 sprites on. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
df61b08bd1 |
Soften a shader's picture in one pass with a Blur node (#300)
A blur already worked with Sample texture, but only by wiring every sample by hand: 25 nodes for a 5x5. Averaging their colours also gave a dark seam wherever a neighbour was transparent. The Blur node writes the loop itself and averages the right way. - Blur takes radius (pixels of the picture, 0 to 8, fractions allowed) and u/v like Sample texture, and gives color and alpha. - Kind is Box (default) or Gaussian, Direction is Both, Horizontal or Vertical. Both are arguments of one prelude helper, blur(). - The samples are averaged premultiplied and only then made straight, so a transparent neighbour lowers the opacity and adds no black. - The outermost ring counts by the part of the radius that reaches it, so a radius from a Property changes smoothly and compiles nothing again. - A node may now declare prepare, a vec4 the compiler works out once into a local. Color and alpha read that one sum instead of blurring twice. - A reached Blur counts as a shifted sample, so the picture comes with the Output's Edge. It cannot feed the Vertex output. - Both perf tools take GM_PERF_BLUR=<radius>. Wider blurs and bloom need several passes. That is #301. Measured on stein2 (HD 630, one run per value, fps), Box blur: - 48x48 sprites at 100/400/800: radius 2 59.8/58.4/55.6, radius 4 53.1/51.4/36.3, radius 8 46.6/24.4/14.4. - 256x256 field shaders at 1/10/50: radius 2 60.2/56.0/49.0, radius 4 60.1/49.0/27.1, radius 8 52.4/34.9/11.4. Tests: - Gate: 224 files, 3467 tests, CHECK EXIT 0 (+7). - e2e 28: ALL PASSED, 94 checks (+5 in 28l-vi). Pixels match the arithmetic to 1: the last red texel at radius 0.5/1/4 reads 187/173/154 (expected 188/173/154). - Counter-checks: Blur not counted as shifted fails 1 unit test. Blur output left premultiplied fails the seam and spot checks. - e2e total 1712 is computed (1707 + 5). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
d6d38a6422 |
Lay a shader over a particle source's whole field (#170)
A shader on a source runs per particle and stops at each quad, so a glow or blur across the whole cloud was not possible. A source now has a second shader row that is laid over everything it draws at once. - New rows Field shader, Field width and Field height (256) on a particle source. The size rows show only while a field shader is picked (new `shownWhenSet` on a row definition). - The particles are drawn into a RenderTexture of the frame, and a sprite mesh wearing the field shader draws that picture. The frame is centred on the source and turns and grows with it. - UV run 0 to 1 across the frame. Texture color, Texture alpha and Sample texture read the picture of the particles. - The picture has as many device pixels as the frame covers on screen, at most 2048 per side. A frame off screen draws no pass. - The field shader's Padding and Vertex output do nothing: the frame is the edge, and its corners stay put as on a nine-sliced sprite. - A name both shaders declare is one row, and both uniforms read it. - The scene view draws the frame dashed round a selected source. The Particles tab preview wears the field shader too. - `tools/perf-field-shaders.mjs` measures 1, 10 and 50 sources. On the HD 630 they ran at 60.0, 60.0 and 57.6 fps against 60.1, 60.0 and 59.2 without, one run per row. Tests: - Gate: 223 files, 3460 tests (3452 + 8), CHECK EXIT 0. - e2e 31: 43 checks, 3 of them new. Sum 1707 is computed (1704 + 3). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
bf27e42e8a |
Let a shader declare a color the sprite sets (#298)
A shader could only ask its sprites for numbers. A tint, an outline colour or a glow colour had to be built from three Property nodes. - New node Color property next to Property: Name and Default (a colour, white unless picked), output `color`. It becomes a colour row below the Shader dropdown and a vec3 uniform in the program. - Number and colour properties share one set of names. A clash is the existing "Another property is named" problem. - Like Color it cannot feed the Vertex output. - Placed from the palette it is called tint, then tint2. - The preview in the Shader tab shows a colour field for it. Like the sliders it starts at the sprite's value and never writes into the project. - `declaredRows` takes the row builder, so the particle graph keeps its number rows unchanged. Tests: - Gate: 222 files, 3452 tests (3445 + 7) - e2e 28: 89 checks (28l-v: default colour drawn, then the colour the sprite sets) - e2e total 1704, counted (1703 + 1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
4f4c149211 |
Duplicate an asset from its right-click menu (#297)
A new asset that starts out like an existing one (a death animation
next to "Break", a second slime) had to be built again by hand. Now
the asset panel's right-click menu has Duplicate.
- The copy lands right below the original and is named the way a new
asset would be ("Crate 2"). It is selected and its tab opens where
it has one. A copied scene does not become the active one.
- One undo step takes it out again. A redo brings back the same id.
- The copy is a deep copy: values, graph, tracks, layers and objects
are its own, and editing it leaves the original alone.
- Frames of a texture or of pixel art get fresh ids, because a sprite
names a frame across the whole document.
- Objects of a scene or container get fresh ids the way pasted ones
do. Parents and object references among them follow the copy.
- Whatever pointed at the original keeps pointing at the original: a
sprite wearing it, an animation's root, a placed object.
Tests:
- 3 new unit tests (DuplicateAssetCommand). One counter-check run:
without renaming the references the scene test fails.
- e2e 04: 2 new checks with the real menu (copy named "Crate 2" right
behind the Crate and selected, one undo removes it).
- Gate: 221 files, 3445 tests, CHECK EXIT 0.
- e2e total 1703 is computed (1701 + 2). Full suite left to CI.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
f1e3bd8378 |
Let a joint glide on a rail and drive it with a motor (#154)
Step 2 of the joints: a door that opens by itself, a lift and a piston need a joint that slides along one line and a motor that turns or pushes. A graph also needs to read how far such a joint stands. - Slider: a fifth joint type on Rapier's prismatic joint. Its rail is the joint's own x axis, so turning the joint turns the rail. Joints now carry Scale, Rotation and Mirror like a sprite. - Limit distance with Min and Max, in world units from where the object was placed. - Motor on hinge and slider: Off, Speed or Target, with Motor speed, Motor target and Max force (0 = no limit). Target goes there at no more than the speed and slows down for the last stretch. - Changing speed, target or force keeps the engine joint. Switching the motor on or off builds it again. - New node Joint position: value and speed of a hinge (degrees, counting past a full round) or a slider (world units). - Scene view draws a slider's rail dashed, with a tick at each limit. - shownFor in the schema takes a list of choices that must all hold. Findings, measured with a probe against Rapier 0.20: - A prismatic joint holds both bodies at one angle. Held to the scene or to an object without a rigidbody, the joint's own anchor body is turned by the placed difference, so nothing jumps. Between two rigidbodies the held one is turned to match, and the console says so once. - Rapier reads no joint angle back. The position is worked out from the two bodies. - A hinge motor is weak on a small body at the end of a long arm (box 20 on an arm of 100 does not lift itself). More solver iterations would help but cost every scene. A door of 100 x 20 reaches -90 and holds it. This is written in the wiki. Tests: - Unit: 16 new in joints.test.ts, 1 in joint-links, 3 catalog cases. - e2e 41: 27 checks (25 + 2), block 41f with a real mouse in the inspector and the motor in play (59.1 of 60 units in 1 s). - 9 of 9 unit counter-probes fail as they should. - Gate: 220 files, 3436 tests (3416 + 20), CHECK EXIT 0. - e2e total 1701 is counted (1699 + 2), the full suite did not run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
4716726b6b |
Let a shader move the corners of the picture it wears (#79)
A shader could only say what colour a pixel has. Where the picture is drawn was fixed to the four corners of its rectangle, so nothing could wave, bend or wobble. - The new node Vertex output takes x and y, in pixels of the picture, and moves every corner of the wearer's grid by them. The UVs stay with the corners, so the picture bends along. - Subdivisions on that node says how fine the grid is: cells per axis, 1 to 64, 8 by default. 1 is the plain quad. A shader without the node keeps the quad every sprite shares. - A graph holds one Vertex output at most. UV, Time, Property, Number and the math nodes may feed it, and one node may feed both outputs. - Texture color, Texture alpha, Sample texture, Texture size and Color cannot feed it. The node says "Not available per corner" and the shader does not compile. - Sprites, texts and the preview in the shader tab wear the grid. A nine-slice sprite and particles draw as if the node were not there. - Picking, the selection frame, the size and colliders stay with the rectangle. A wearer with a Vertex output is never culled, as with Padding, and with Padding the grid covers the room around the picture too. - tools/perf-shaders.mjs takes GM_PERF_SUBDIVISIONS to measure a grid against the plain quad. Measured on stein2 (Intel HD 630), frames per second, one run per row: at 800 sprites 58 with a shader on the quad, 46 with 8 cells, 41 with 32. Up to 400 sprites a grid hardly shows. Every sprite holds its own grid. Tests: - Gate on melektaus-atx: 220 files, 3416 tests, CHECK EXIT 0. - 15 new unit tests for the compiler and the grid. - Four new e2e checks in modules 28 and 30: a fixed offset moves the picture, 8 cells bend only the lower half where 1 cell shears it all, Texture alpha is refused at the node, a text moves. - e2e 28,30 on melektaus-atx: 110 checks, all pass. - The e2e total of 1699 is counted (1695 + 4), the full suite did not run. - No counter-probe, no screenshot. Not looked at: the preview, Time in the browser, flipped sprites, Padding together with the grid. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
963f8a152a |
Open a scene framed, and thin out two inspector sections (#295)
Three small things asked for in one issue. A scene came up wherever the view happened to stand, and the inspector carried a button and a list that are better reached somewhere else. - A scene comes up framed as if Fit had been clicked: after a project is loaded and when another scene is opened. From then on the view belongs to the user. - An empty scene has nothing to frame and stands at the origin, one pixel per unit. A scene the running game loads is not framed anew. - The Container tab is left out. It keeps the view of each container as before (#171). - The Unpack button and the two lines under it are gone from the inspector of a placed container. Unpack container stays in the right-click menu of the hierarchy and of the scene view. - The corners of a polygon collider are no row in the inspector any more. They are edited on the outline in the scene view. The new schema option noRow keeps a property out of the inspector while it stays a property. Tests: - Gate on melektaus-atx: 219 files, 3401 tests, CHECK EXIT 0. - One new unit test in tests/core/polygon.test.ts: the corners are a property of the collider and no row. - Three new e2e checks: a loaded project is framed like Fit (module 03), an opened scene is (04), and after a real reload a click on Fit moves nothing (09). - Module 09 unpacks through the right-click menu now and checks that the inspector offers no button. - Full suite on melektaus-atx: 1695 checks, all pass. Smokes 24, 19 and 21, all pass. - No counter-probe evaluated, and nothing looked at after the build. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
ca5b2db449 |
Keep where each divider was dragged to across a reload
A panel dragged wider was back at its first size after every reload. The sizes are now kept in the browser, next to the folded groups. - editor/panelSizes.ts holds one entry in localStorage, game-maker.panel-sizes: a name per divider and its width, height or share. - A panel reads its size once when it is built, held to the bounds its divider keeps today. Every drag writes the new size. - Kept are the columns left and right of the centre, the console, the split of the left column, the tilemap panel, the preview of the Shader, Particles and Pixel art tabs, each under its own name, and the split of the user management. - The sizes belong to the browser, not to the project. They hold for every project opened there. - A browser that refuses the storage, or holds rubbish under the entry, starts with the sizes the editor comes with. Tests: - Gate on melektaus-atx: 219 files, 3400 tests, CHECK EXIT 0. - Seven new unit tests in tests/editor/panel-sizes.test.ts. All five counter-probes bite. - Smokes on melektaus-atx: 24, 19 and 21, all pass. - One new e2e check in module 48: the left column dragged 40 px wider is as wide after a reload, and dragged back after. - Full suite on melektaus-atx: 1692 checks, all pass. It ran before the fix to the animation players of the commit before last. - Browser counter-probe: without the write to the browser exactly that check fails. - Only the left column is measured across a reload. The other dividers go the same way and have no check of their own. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
8f9d373d7b |
Scroll the + menu to Vector art before picking it in e2e 47
The suite to
|
||
|
|
7dd3ae1131 |
Tie two objects with a hinge, a spring, a rope or for good (#154)
Two bodies could not be connected: no door on a hinge, no lamp on
a rope, no wheel on an axle. This is the first step of the issue,
the four joints without a motor. The slider and the motors follow
in a second step under the same issue.
- Joint is a new asset type, placed as a child of the object it
holds, like a collider or a rigidbody. "Add joint…" in the
object menu makes one or places one the project has. An object
takes as many as it needs.
- Where the joint sits is the point it holds its object at. A
hinge and a fixed joint have that one point only. A spring and a
rope end at a connected anchor on the other object, counted in
that object's own units, or in the scene's without one.
- The rows follow the type: length, stiffness and damping for a
spring, length for a rope, an angle limit for a hinge. Length 0
takes the distance the two ends were placed at. The limit counts
from how the two were placed.
- The connected object is an object reference in the schema of the
type. Pasting, containers and removing treat it as they treat
every reference since #258.
- shownFor is a new schema option: a row is shown while an enum of
the same bag holds one of the named values.
- The object needs a rigidbody, its own or one further up. Without
one the joint holds nothing and the console says so once.
- A connected object without a rigidbody is a moving anchor: the
joint has a kinematic body of its own that follows it.
- A connected object destroyed while playing makes the joint let go
with a hint. Removed in the editor the reference empties, so the
joint holds on to the scene. The issue asked for "inactive" there.
- "Add joint" ties two objects while the game runs, from a joint
asset as the template. Destroy on the joint lets go, there is no
Break joint node. Destroy with keep children takes the joints
along with the object.
- The scene view draws a line from each joint to what it holds on
to and rings the held point. A selected spring or rope has a
round handle on its far end, and dragging it is one undo step.
- Joints hold in Play only. The editor run simulates nothing
(#148), and the inspector of a placed joint says so.
- Rapier counts a revolute joint's limits absolutely, not from the
start, so the placed angle is added. Measured with a probe.
- The + menu has 25 types now and no longer fits under its button
on a canvas 800 high. It scrolls there, and e2e 04 scrolls to
the last entry instead of expecting it in view.
Tests:
- Gate on melektaus-atx for this state: 218 files, 3390 tests
(3336 + 54), exit 0.
- e2e 1-16,24,39-46,51,52,57,58 on melektaus-atx: 831 checks, all
pass. Module 41 alone: 25 checks (21 + 4).
- 54 new unit tests: 40 in joints.test.ts (rope, spring, pendulum,
limit, fixed, the other end, the node, containers, paste), 6 for
the line the scene view draws, 4 catalog cases of the node. The
remaining 4 come from tests that make a case per asset type.
- Four new e2e checks in module 41: Add joint from the menu, the
rope to the hook with its line and handle, dragging the handle
with one undo, the bob hanging in Play next to one that falls.
- e2e 04, 40 and 46 count the new type in their lists.
- 21 of 22 unit counter-probes fail their tests. The other one is
equivalent: Rapier ignores removing a joint that left with its
body, the guard stays.
- Three browser counter-probes fail their checks (2, 1 and 2).
- One screenshot looked at: icon, line, handle and the rope rows.
- The e2e sum of 1691 is counted (1687 + 4, 1687 measured by CI
on
|
||
|
|
e066613583 |
Watch a held body for a time, not for a count of frames
The suite to |
||
|
|
e933fad446 |
Give a shader room to draw around the picture it wears (#69)
A shader ran only inside the rectangle of what wears it: the quad has the size of the sprite, and outside of it no fragment runs. An outline or a glow built with Sample texture was cut off at the border. The issue still spoke of Filter.padding, but since #77 the shader is no filter any more, the limit is the geometry. - The Output node has a number Padding next to Edge: so many pixels of the picture per side the wearer draws beyond it. 0 is the default and draws as before, 256 is the most. - The quad grows and its UVs grow with it, so the picture keeps its place and size. In the margin the UVs run on below 0 and above 1. - Transparent is a fourth Edge mode: outside of the picture there is nothing. It is cut in the fragment (texelAt), so it needs no copy of the picture with an address mode of its own. - Edge holds everywhere alike, also for Texture color, Texture alpha and unconnected inputs. - Padding above 0 with Edge on Clamp puts the hint "Padding with Clamp smears the border" on the Output. The shader still compiles. A chosen Clamp and the default cannot be told apart. - All four wearers draw the margin. A sprite counts in pixels of its frame, a text in its units, a particle source grows every particle about its centre. - A nine-slice sprite gets a ring around its nine fields, as wide as so many border pixels. The fields themselves stay. - Without a picture a pixel is one unit of the wearer's own size, or of the particle size. - Particles now take the Edge mode of their shader. It had no effect on them before. - An object wearing a padded shader is never culled, or its margin would vanish at the edge of the screen. - The preview in the shader tab shows the margin and leaves room for it. - Picking, the selection frame, the size and the collider stay with the rectangle without the margin. Tests: - Gate on melektaus-atx right before this commit: 216 files, 3336 tests (3319 + 17), exit 0. - e2e 28,30,31,37,41 on melektaus-atx: 178 checks, all pass. e2e 1-37,47,48,58 ran before the last change (no culling with padding): 1281 checks, all pass. - 17 new unit tests: the compiler (padding, Transparent, the hint) and the geometry (quad, nine-slice ring). - Seven new e2e checks: four at the sprite in module 28, one each for text, particles and nine-slice. - 17 unit counter-probes all fail their tests. Three browser counter-probes fail their checks (5, 3 and 1 of them). - Three screenshots looked at: the sprite with Clamp and margin in the scene, the preview in the shader tab, the text. - The e2e sum of 1687 is counted (1680 + 7), the full suite and the smokes did not run. - Not seen: the hint at the node in a picture, particles and nine-slice in a picture, the game view while playing, sprites without a picture, vector and pixel art pictures. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
46c620ae3b |
Give a behavior more ways in and out, and hear it as an event (#294)
A behavior run by another one had a single flow port at its Start and at its End, so it could not branch for its caller. And it could not be used as an event: a call needs a pulse at its flow input, and the events of a called graph never fire. An input like "Confirm" (Space or Enter) had to be built again in every graph. - A declared port of a Start or an End node can be of the kind Flow. It sits above the values and shares their names, exec stays reserved. Only these two nodes declare one, a struct does not. - Run behavior shows a flow port of the Start as a further flow input. A call arriving through it leaves the Start through the output of that name and not through exec. - Run behavior shows a flow port of the End as a further flow output. The call goes on through the one the End was reached by last, and then through exec. exec fires always, as before. - On behavior is a new event node without a flow input. The behavior it names runs at the node for as long as the behavior around it runs, with all of its own events and one set of variables per node. - Its outputs are those of the End. It fires once the inner pulse is through, by the output the End was reached by last, exec only for End.exec. A pulse that never reaches the End is not seen. - The value ports of the Start are its inputs, read anew for each inner pulse. The Start itself never fires there. - It nests to any depth. Under a Run behavior it stays silent. A behavior hearing itself, however far round, is not started and the node says "A behavior cannot listen to itself". - The palette offers every other behavior with an End once more as "On <name>". A node naming one without End says so. - An On node removed while the game runs ends its behavior with On disable and On destroy. The sounds of the entity end only with the behavior placed on it. - Renaming and deleting a declared port follows the edges at On behavior nodes as it does at Run behavior nodes. - The flow outputs of the Start of a behavior placed on an object never fire, like its exec. - The engine prompt of the benchmark names flow ports and On behavior. Tests: - Gate on melektaus-atx right before this commit: 215 files, 3319 tests (3278 + 41), exit 0. - e2e 19 on melektaus-atx: 39 checks (+4), all pass. e2e 1-6,17-24,26,36,51: 628 checks, all pass. - 19 unit tests in flow-ports.test.ts, 19 in on-behavior.test.ts on a real world, the rest in the node catalog. - Four new e2e checks in module 19: a flow port declared by mouse at the End, On Confirm in the graph, the call leaving through the further output while playing, Enter and Space on a real keyboard. - 19 unit counter-probes all fail their tests. One did not at first (a removed node kept its instance), a test was added. Two browser counter-probes fail their checks. - One screenshot looked at: On Confirm with exec and pressed, the second flow pin at the calling node. - The e2e sum of 1680 is counted (1676 + 4), the full suite and the smokes did not run. - Not seen in the browser: flow inputs at the Start, nesting, the ring, values through On behavior, the palette entry. No test holds that the sounds of the entity go on when a heard behavior ends. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
b45bbcc976 |
Hold in tests that a graph edit reaches a running behavior (#216)
The issue said a behavior that runs in the editor keeps the graph it started with, until it is switched off and on. Measured before building anything, that is no longer so. An edit runs from the next frame on. Since when, was not found out. - No code changes. A command raises the project event, EditorRun refreshes, and syncFromModel of the behavior system empties the graph cache. - Decided in the issue: the instance carries on and is not started over. Its variables and timers stand, and On start does not fire again. A restart would write what On start writes or spawns into the saved scene with every edit. - Starting over stays what it was: Run in editor off and on, or Play. - Module 43 of the e2e no longer switches the behavior off and on before it measures the changed layers of a ray cast. Tests: - Gate on melektaus-atx for this change alone: 212 files, 3264 tests (3260 + 4), exit 0. The gate right before this commit ran on the tree of this and the next commit together: 213 files, 3278 tests, exit 0. - e2e 18,20,43 on melektaus-atx: 57 checks, all pass. The check in 43 is the old one, it now holds without the workaround. - Four unit tests in editor-run.test.ts: a changed node setting, a node wired in and one cut off, a flow hung on On start that does not fire until the switch goes off and on, and an undo. - One counter-probe: without the emptied cache the wiring test fails. The setting test does not, settings change in place. - No full run and no smokes. The sum of e2e checks stays 1676. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
c7dc0e4733 |
Leave hiding and deleting a link to its right-click menu
The inspector of a picked link carried the buttons Hide link and Delete link below its facts. Both actions are in the right-click menu of the line since #289, and Delete removes a picked link as well. The user asked for the buttons to go. - buildEdgeSection in editor/inspector/edgeSection.ts only describes now: the two ends, the type, and for a transition its name field. - The button under several picked links and Delete transition in a state machine go with them. They came from the same line of code, and the menu offers both. - The note under several links stays. It speaks of the Delete key. Tests: - Gate on melektaus-atx: 212 files, 3260 tests, exit 0. - e2e 4,6,26 on melektaus-atx: 275 checks, all pass. - No full run and no smokes. The sum of e2e checks stays at 1676, which is reckoned. - Module 06 now checks that the inspector of a picked link shows neither button. Its menu check finds Hide link and Delete link only in the menu, where it had to take the one drawn last before. - Module 26 checks that a picked transition shows no Delete transition. - No unit test names the buttons. No counter-probe was run. The old checks found both texts in the inspector, which is the state the new ones would fail on. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
f02adabcf9 |
Show the type an arithmetic shader node really puts out (#219)
The result of Add, Multiply, Mix and the other arithmetic nodes of a shader graph was drawn as any, whatever went in. The compiler already lifts a colour on any side to a colour result, so the pin said less than the code knew. A colour result could also be wired into a number input, and the shader then failed to compile. - liftedType in core/shader/nodes.ts is the one rule. The compiler reads it when it lifts two operands, and the editor reads it through liftedPortType for the ports it draws. - A SharedPortGroup may bring its own rule as agree. Without one it is agreedType, as before. The thirteen arithmetic nodes share their type by liftedPortType. - Only the any inputs decide. An input nothing feeds counts as the number typed into it. t, edge, exponent, min and max have no say. - The type travels through a chain of such nodes without code of its own, because #210 hands a shared type on. - In a ring the result stays any, unless a colour has settled it. - A number may feed a colour input. Without that a number result no longer fitted Output.color, although the compiler spreads it over three channels. The rule sits in portsCompatible, but only the shader graph has colour ports. - A colour result no longer fits a number input. An edge laid before the colour came in stays and is drawn in the danger colour, and the node at its end names the problem. - The nine shaders in the stored projects (108 edges) all stay valid under the new rule. Tests: - Gate on melektaus-atx: 212 files, 3260 tests (3240 + 20), exit 0. The gate right before this commit ran on the tree of this and the next commit together, with the same numbers. - e2e 28 on melektaus-atx: 81 checks, all pass. - e2e 1-31: 1059 pass, 1 fail in module 18 (the editor clock read 1.97 s after a stop). A rerun of 1-18 passed with 602 checks and 0.18 s. 1-31 was not run in full again. - No full run and no smokes. The sum of 1676 (1673 + 3) is reckoned. - Twenty unit tests in shader-port-types.test.ts, one of them runs all thirteen nodes through every mix of number and colour and compares what is drawn with the code made. - Two tests in shaders.test.ts now lay the edge first and bring the colour in after, the only order that still wires such a graph. - Three e2e checks in module 28 with the real mouse: a colour into Multiply, the red edge with its message, and the colour result refused at alpha and taken at color. - Fifteen unit counter-probes and two browser counter-probes all fail their tests. - Not checked in the browser: the one-input nodes, Mix and the ring, and a number wired straight into a colour input. Nothing was looked at after the build. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
9ca220a285 |
Hand a shared port type on through a chain of nodes (#210)
Since #207 the output of First not empty or Select takes the type
its inputs agree on. A second node of that kind behind the first
stayed any, because the first one answered with the any it was
registered with. That was the guard against a circle of such nodes
asking each other forever. The issue asks for the type to travel
through the chain. Answered there: measure with 50 nodes and build
no cache, and the painted input pins follow along.
- shapedDef in core/graph/nested.ts takes a Trail in place of the
flag inherit. Its set open holds the nodes still being asked. A
node reached a second time reports the any it was registered
with, which ends a circle.
- The map done of the Trail keeps each answer of one question. A
node feeding both inputs of the next is asked once, so the work
does not double with every link of a chain.
- ownShape is the part of shapedDef that builds the ports before a
shared group has its say. It is split off, nothing in it changed.
- Inputs stay any and only outputs inherit, as in #207. An edge
that turns invalid further down is left in place and drawn in the
danger colour.
- paintedInputs uses one Trail for all pins of a node, so the pins
show the type handed on through the chain.
- Nothing is cached between two changes. A chain of 50 resolved in
about 10 ms where it took 2 ms before, measured earlier in this
round and not again for this commit.
Tests:
- Gate on melektaus-atx: 211 files, 3240 tests (3227 + 13), exit 0
- e2e 1-6 on melektaus-atx: 298 checks, all pass
- e2e 4,6,18,19,20,23,24,26,28,31,36,43,53: 558 checks, all pass
- No full run and no smokes. The sum of 1673 (1671 + 2) is reckoned,
1671 is what CI counted for
|