Commit Graph
15 Commits
Author SHA1 Message Date
adminandClaude Opus 5.5 6097307b6d Move repo, CI and the video worker over to the name Feuerwerk (#422)
feuerwerk / gate (push) Successful in 4m30s
feuerwerk / suite (push) Successful in 18m38s
Stage 3 of the rename. The repo now lives at meleki/feuerwerk, the
runners carry feuerwerk labels and the WSL distro on melektaus-atx is
called feuerwerk-ci. The code has to follow, or CI finds no runner and
the suite no longer picks the GPU path.

- ci.yml: workflow name feuerwerk, jobs on feuerwerk-ci (gate, stein2)
  and feuerwerk-wsl (suite, melektaus-atx).
- GAME_MAKER_* variables are now FEUERWERK_*. This includes
  FEUERWERK_BROWSER=wsl, which the runner config on melektaus-atx sets.
- The video worker runs as container feuerwerk-video with the image
  feuerwerk-video and works on /srv/feuerwerk.
- e2e.sh starts the unit feuerwerk-e2e, the CI image keeps its paths
  under the new name.
- README and repo links point to git.nockal.com/meleki/feuerwerk. The
  old URLs still redirect with 301.

Both runners keep their old labels until the first CI run with this
ci.yml is through. Then they go, together with the image tag
game-maker-ci:latest and GAME_MAKER_BROWSER in the atx config.

Tests:
- Gate on melektaus-atx through the renamed distro: CHECK EXIT 0,
  307 files, 4756 tests.
- e2e 60 on melektaus-atx: ALL PASSED, 20 checks. Renderer is the
  RTX 4070 through D3D12, so FEUERWERK_BROWSER reaches the GPU path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 13:42:52 +02:00
adminandClaude Opus 5.5 93190e185c Route a video's sound through its own Master routing node
An editor recording had no sound while the video tab played a video.
The tab played its <video> straight to the speakers, past the Web
Audio graph, so the recording tap behind the limiter never heard it.
Fixing only that would have left videos as the one sound source the
Master routing cannot reach, so they get their own node, like sounds.

- A video asset with sound has a routing node labelled "Video". One
  without sound has none, it would have nothing to send.
- Its links take the sound after volume and FX. Several links or
  another level give the video its own bus, as with a sound.
- A new video follows the bus most other videos use. With none linked
  yet it goes to the Master, not to where the sounds go. Songs and
  sounds no longer follow videos either.
- Players only know their file, so videoBusesOf maps each file to the
  bus of its first asset. setMix takes that map and moves the gains of
  running videos at once when the routing changes.
- In the game, played in the editor or released, a video's sound
  follows its links. The tabs (video, composition, animation preview)
  play straight into the Master, as the sound tabs have since #368.
- The video tab's sound now goes through the Master, so its volume, FX
  and limiter apply there and the editor recording hears it.
- The recording badge beside the menu is an HTML element over the
  canvas now. The second render that drew it on the desynchronized
  canvas let the screen show a frame without it, so it flickered. The
  element is never in the copy, and clicks go through it.

Tests:
- Unit master-routing and sound-library: 3 new, green. Red without
  video in the routing kinds, without moving the gains, and with the
  old rule for the links of a new video.
- e2e 66e-iii: the tab plays a video linked to a bus, the level is
  0.09 on the Master and 0 on the bus. 66g: in the game the bus has
  0.09. Both red with the old ways.
- e2e 67e reads the badge from a real screenshot, still green.
- The README names 2043 e2e checks.
- Gate on melektaus-atx: 304 files, 4694 tests green.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 16:15:59 +02:00
adminandClaude Opus 5.5 080df2d211 Let the video tab's Play start at the playhead and reset its button
game-maker / gate (push) Successful in 4m18s
game-maker / suite (push) Successful in 17m57s
Two faults in the video tab. A click into the ruler while a clip
played stopped the playback, but the button kept showing "Stop". And
Play always started at the start of the clip, wherever the playhead
stood.

- The tab now reports when the ruler stops the playback, and the
  button switches back to "Play".
- Play starts at the playhead when it lies inside the clip. Without
  a selected clip the whole video counts. Outside or at the end,
  Play starts at the beginning.
- So Stop and Play continue where the playback stopped. A clip that
  ran to its end without loop starts again at the beginning.

Tests: gate on melektaus-atx green. e2e 66 ALL PASSED, 32 checks (2
new in 66e-i). With both fixes taken back, both checks are red with
exactly the reported faults: the button shows "Stop", and Play runs
from 0.71 s, i.e. from the clip start at 0.6 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 13:55:13 +02:00
adminandClaude Opus 5.5 322fc39d16 Zoom the video tab's time smoothly like the other tabs (#377)
game-maker / gate (push) Successful in 4m41s
game-maker / suite (push) Successful in 17m13s
The video tab jumped on every wheel step and on Fit. The Sound,
Pattern and Song tabs already ease the zoom since #377. The video
tab now takes the same transition, so all tabs feel alike.

- VideoCanvas runs wheel and Fit through `axisTween`. The duration
  comes from the General settings. Quick wheel steps add up, panning
  ends the transition, and the view that is stored is the one the
  transition lands on.
- Opening a video still fits at once without a transition. Only the
  Fit button eases, as in the Sound tab.
- `__gm.videoTab.zoom()` reports the zoom and whether a transition is
  on its way.

Tests:
- e2e 66 on melektaus-atx: ALL PASSED, 30 checks (1 new). The new
  check uses `sampleZoom` like the Sound tab. At 1000 ms the zoom is
  on its way two frames after the wheel (319 -> 321) and lands where
  the jump does (370.63). No counter-check against the old code.
- Gate on melektaus-atx: CHECK EXIT 0, 299 files, 4586 tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 21:52:29 +02:00
adminandClaude Opus 5.5 36ffd9a61a Land a driven video's seek without a frame so it is never empty (#404)
game-maker / gate (push) Successful in 4m9s
game-maker / suite (push) Successful in 17m18s
Since b9fcf48 a seek of a driven video only counted as done once
Chrome handed over the next frame. For some seeks Chrome hands over
none. The player then waited for good, the view left the sprite out
after 0.3 s, and the picture stayed empty until the next jump.

- A seek now lands after `seeked` with the next frame, or after
  150 ms at the latest. The worst case is a picture 150 ms late.
- The landing is set up in the `seeked` handler instead of `seek`.
  A frame from before the last seek of a chase no longer counts.
- `__gm.videos()` reports `landing`, so an empty picture can be told
  apart from a player that is still loading.

Findings:
- The empty picture could not be reproduced in Chrome headless.
  The test video is 320x180 and decoded in software. The user's
  videos are 1080p60 H.264, likely decoded in hardware.
- The user confirmed the fix in the editor.

Tests:
- Unit video-library + video-follow: 20 green (1 new). The new test
  plays a video that hands over no frame after a seek. With the old
  code it stays not ready, so it fails.
- e2e 66 on melektaus-atx: ALL PASSED, 29 checks. The new 66r cuts
  LATE ten times quickly and was also green with the old code.
- Gate on melektaus-atx: CHECK EXIT 0, 297 files, 4563 tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 19:04:40 +02:00
adminandClaude Opus 5.5 b9fcf48097 Show a cut into the middle of a clip without its old picture (#404)
A switched-off driven video waits at the video time it has where it
is, before its first key mostly the start value. Switched on at
another spot, its player has to seek first. Until the seek is done the
texture still holds the old picture, so the start of the clip flashed
for a frame or two at the cut.

- A view holds its last picture while a driven video that just came
  into sight is still seeking, for at most 0.3 s. At a cut the old clip
  stays one frame longer and the right picture of the new one follows.
- A seek counts as done only when the browser hands in the next video
  frame (requestVideoFrameCallback), and the texture is uploaded right
  then. The seeked event alone was too early, one red frame was left.
- A seek that takes longer than 0.3 s leaves the sprite out until its
  picture is there. The wrong picture does not show either way.

Tests:
- e2e 66q switches a sprite off at 0.2 s (red) and on at 1.2 s (blue)
  and reads every frame in the browser. No red may show, blue must
  stand at the end.
- Against the old code 66q fails with 2 red frames before the blue.
  With only the seeked event it still fails with 1 red frame.
- e2e 66 on melektaus-atx: ALL PASSED, 28 checks, run twice.
- Unit video-library, video-follow: 19 passed.
- Gate on melektaus-atx: CHECK EXIT 0, 297 files, 4562 tests.
- tsc, eslint, Prettier clean.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 18:18:56 +02:00
adminandClaude Opus 5.5 3a1e420806 Read a driven video's speed on the frame clock so 1x stays 1x (#404)
game-maker / gate (push) Successful in 4m12s
game-maker / suite (push) Successful in 16m58s
A driven video measures how fast its wearer's time runs and sets its
playbackRate from that. The time moves on with the ticker, but the
reading took its clock from performance.now() while drawing. A frame
that had more to do before the draw shifted that clock against the
time, and a 1x animation read as up to 1.09. Outside 0.9 to 1.1 the
video goes mute, so the sound could drop out for a moment. In CI this
made e2e 66k red now and then.

- VideoLibrary reads the speed with the timestamp of the frame
  (document.timeline.currentTime), the same clock the time runs on.
  It falls back to performance.now() where there is no document.
- debugInfo() reports the measured speed of a driven player.
- e2e 66k checks the measured speed and the correction of the rate
  apart. The rate alone may differ from 1 by up to 10 percent on
  purpose, so checking it at +-0.1 was too strict.

Tests:
- Unit video-library: 8 tests green, 1 new. With performance.now()
  back in place the new test is red and reads 1.089.
- e2e 66 on melektaus-atx: all passed, 27 checks, speed 1.000 in
  all 5 readings.
- Gate on melektaus-atx: CHECK EXIT 0, 297 files, 4562 tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 17:49:45 +02:00
adminandClaude Opus 5.5 dd60a795e1 Wait for driven videos before a run starts so no sprite flashes (#404)
game-maker / gate (push) Successful in 4m28s
game-maker / suite (push) Failing after 16m24s
The game view opens a player for each driven video only when it draws,
and it draws only while its tab is visible. Without a detour over the
Game tab the players came up with the click on Play. Until a video had
loaded, its sprite showed its base colour, white in the user's cut.
Sharing the players of the scene view would not have helped. From the
Animations tab those are discarded after two to four seconds.

- Play holds the run at second 0 until every driven video in the game
  view has its first picture, at most 3 s. The status line says
  "Loading the videos… · Esc stops".
- The game clock stands while it waits. Behaviors and sounds start
  only after it, so picture and sound stay together.
- Load scene in a running game waits the same way.
- A driven video without a picture is not drawn at all instead of
  white. That holds everywhere, also on a switch to the Game tab
  without Play and at a cut whose video is not ready.
- The exported player waits the same way.
- __gm.playInfo() reports waiting for the e2e check.

Tests:
- e2e 66p puts the sprite in front of the camera and waits 4.5 s so
  the game view has no player left. Then it reads every frame of the
  run: 14 frames of waiting on #22252c at time 0, then blue, no white.
- Against the old code 66p fails: 14 frames #ffffff, then blue.
- Unit video-library: 7 passed, 1 new for loading().
- e2e 66 on melektaus-atx: ALL PASSED, 27 checks.
- Gate on melektaus-atx: exit 0, 297 files, 4558 tests.
- tsc, eslint, Prettier clean.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 17:07:53 +02:00
adminandClaude Opus 5.5 843b110bcb Hold a driven video at the end of its file instead of restarting (#404)
A driven video plays on for about 100 ms after the last key, until the
speed reading says it stands. With the key close to the end of the
file, that runs the video past it. The browser then marks it ended,
and follow() called play() on it in every frame. Per the HTML spec an
ended video starts over from the beginning of its file on play(). The
sprite flashed the first picture one to three times before it held.

- An ended video now counts as standing at its end. follow() holds it
  and seeks to the wanted picture instead of playing it again.
- The end of the file counts as an end too, not only the end of the
  clip.

Tests:
- e2e 66o plays an animation in once mode to 1.95 s of the 2 s test
  video and records the player time in every frame. It must never jump
  back, and the last picture must stand.
- Against the old follow() 66o fails, the player jumps back to 0 once.
  A first version with the key at 1.9 s stayed green, the video never
  reached the end of the file.
- e2e 66 on melektaus-atx: ALL PASSED, 26 checks.
- Unit video-library, video-follow, video-clock: 31 passed.
- tsc, eslint, Prettier clean.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 15:46:03 +02:00
adminandClaude Opus 5.5 43fd22a763 Keep a driven video ready while its sprite is off (#404)
A cut between two videos flashed white: the sprite switched on had
no picture yet, because its player only opened then or had been
dropped meanwhile. A driven video now waits muted at its time while
its sprite is off, in the editor and in a run.

- A disabled sprite with Video clock Driven asks for its player
  without drawing it. A Free video still opens when shown.
- A run's draw list keeps disabled driven sprites, marked as off.
  Before, collectWorldItems dropped every disabled object, so the
  game view never held a player for them.
- VideoLibrary.wear() counts as asking. The sweeper dropped a held
  player after 2 to 4 s and it was loading again at the cut.
- The speed of the asked time is read over a window that grows to
  500 ms. Over 100 ms one 1/60 s step more or less read as 17 % off,
  the playback rate jumped and the sound dropped out.
- A stream feeds every packet it has loaded to the decoder at once
  and waits once. One round trip per 20 ms packet let the decoder
  fall behind on a busy main thread, which made e2e 34 flaky in CI.

Tests:
- Unit streaming, video-library, video-follow: green, 4 new
- e2e 34 + 66 (atx): ALL PASSED, stream skipped 0, 66m new
- e2e 66 (atx), two runs: ALL PASSED, 25 checks, 66n new
- Gate (atx): CHECK EXIT 0, 297 files, 4557 tests
- Counter-checks: without the draw list fix 66n finds no player,
  without the wear() fix and with the old speed reading the new
  unit tests turn red

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 15:37:16 +02:00
adminandClaude Opus 5.5 774207208c Key a video to its end at 1x and zoom the timeline further out (#407)
To play a clip at its real speed the user had to know its length,
work out the last whole frame by hand and then scroll far to the
right to set the end key. The timeline now does the sum, and it
zooms out far enough to see a long clip in one piece.

- The context menu of a Video time row gets "Video at 1x to its end
  from frame N". It keys the playhead with the row's value there and
  the last whole frame before the clip ends with the matching time.
- The start key turns linear and keys in between are removed. The
  whole run is one undo step (KeyStraightRunCommand).
- The clip is the one the sprite wears at the playhead, also when
  the texture is keyed in the same animation (rowVideoAt).
- The entry is greyed out for relative rows, without a video, while
  the length is unknown, or when the playhead is already at the end.
- The status line names the result, e.g. "frame 572 shows 57.2 s of
  57.27 s".
- The timeline zooms out to 0.02 px per frame instead of 4 px. The
  ruler labels follow a 1-2-5 step without an upper limit.

Tests:
- Unit video-clock + timeline-view: 27 tests green, 8 new
- e2e 66 (atx, alone): ALL PASSED, 23 checks, 66l new
- Gate (atx): CHECK EXIT 0, 297 files, 4553 tests
- Counter-check: the command keeping the keys in between turns
  exactly the command test red

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 14:51:20 +02:00
adminandClaude Opus 5.5 c9e8a3f6ac Let an animation drive a sprite's video picture by picture (#404)
game-maker / gate (push) Successful in 4m16s
game-maker / suite (push) Failing after 16m12s
A video used to run on its own clock as soon as a sprite wore it. An
animation could switch the sprite on and off, but never say which
picture shows at which frame. Cutting a video in an animation needs
the opposite: the animation sets the time and the video follows.

- Sprites wearing a video or a clip get two rows, Video clock and
  Video time. They show only while the texture holds a video.
- With Video clock on Driven the video does not play by itself. It
  shows the picture at Video time, counted from the clip's start.
  Past the end it goes round with Loop and holds the last picture
  without.
- Video time is a number like any other, so keys, step, easing and
  curves give stills, slow motion and ramps.
- A driven sprite gets a player of its own, keyed by the wearer's
  name. The same clip can run twice at different times.
- Following has two ways (`videoFollow.ts`). Running forward at 0.25x
  to 4x the video plays at that rate and is nudged. A still, a scrub
  or a step backwards seeks, and only the newest seek counts.
- Sound is heard only near 1x. The animation tab now plays it too, so
  a cut can follow the sound. Scrubbing is silent.
- Scene, game view, the inspector's PREVIEW and the shader preview
  show the driven picture. In a running game Play animation sets it.

Found on the way:

- After a scrub the video in the animation tab kept running. The
  preview redrew only while the playhead moved, so nobody asked for
  the video once it stood. `AnimationEditor.tick` now redraws it every
  frame while it shows a video.
- Every sprite has two more defaults (`videoClock: 'free'`,
  `videoTime: 0`). Eight tests in five files pinned the old list and
  are updated.

Tests:
- Unit: video-clock and video-follow new, 4 files with 42 tests green.
- e2e 66 (melektaus-atx): ALL PASSED, 22 checks, 3 new. 66i: two
  sprites on one clip stand at 0.2 s red and 0.7 s green. 66j: a
  scrub to frames 2, 7, 12 and 3 shows red, green, blue, red, and
  each colour still stands 0.7 s later. 66k: playing runs 10-30 ms
  behind at rate 1.00-1.03 and is not muted.
- Counterchecks: a seek that keeps the rate turns the seek test red.
  Not seeing Driven turns 66i, 66j and 66k red.
- Gate (melektaus-atx): exit 0, 297 files, 4546 tests.
- Not tested in the browser: Play animation in a running game.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 14:23:07 +02:00
adminandClaude Opus 5.5 a62d1d0faf Give e2e 66 its own lane so the video sprite is not covered (#406)
game-maker / gate (push) Successful in 4m36s
game-maker / suite (push) Successful in 16m31s
In the full suite e2e 66f saw the same purple #cc00f4 fourteen times
instead of the clip's green and blue. Alone the module was green. The
video sprite stood at (0, 0), and there earlier modules keep objects
on purpose that later modules still need. Every other module has its
own lane for that reason, only 66 had none.

- scaffold.mjs: new lane `video` at y -7000.
- 66-video.mjs: the sprite and lookAt use that lane, and the cleanup
  looks back at (0, 0) like the other lane modules do.

Found with a diagnosis in a full run on melektaus-atx: under the
sprite's centre lay o3, otex (z 900) and filler40 to filler1 (z 5)
from e2e 10 to 12, only then vd66-object. Beside them the sprite
showed green as it should. The purple is otex's texture in the state
later modules leave it in. It has nothing to do with video seeking:
task 645 saw the same colour while seeking was still broken.

Tests:
- e2e 10-12 + 66, old state (melektaus-atx): 66f FAIL, otex in front
- e2e 10-12 + 66, with the fix: ALL PASSED, 81 checks, 66f sees
  #00ff01 and #0000ff
- Prettier, eslint: clean

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 13:40:43 +02:00
adminandClaude Opus 5.5 ce933a4f32 Let a drag change only the picked clip in the Sound and Video tab (#403)
game-maker / gate (push) Successful in 4m15s
game-maker / suite (push) Failing after 15m40s
A drag with a clip picked could span a new clip in the strip or span
the picked one anew over the wave. Both happened by accident while
working on a clip. Now a drag only makes a new clip while none is
picked. With one picked it moves its edges or the whole of it.

- With no clip picked, a drag in the strip or over the wave (in the
  Video tab the track) spans a new clip, as before.
- A click over the wave or in the track picks the clip at that point,
  as a click on its band does. Beside the clips it picks the sound or
  the video. The picked clip wins where clips lie over each other.
- With a clip picked, its handles move its edges as before. A drag on
  its band in the strip moves the whole clip, its length kept and
  inside the file. Any other drag does nothing.
- The rules live in waveView.ts (clipGrip, clipToPick, movedSpan) and
  both tabs use them. The cursor shows grab over the picked band.
- The Video tab's track draws the clips not picked faint, as the wave
  does, so a click there has something to aim at.

Tests:
- Unit wave-view.test.ts: 16 tests (4 new)
- Counter-check: a strip drag beside the picked clip spanning a new
  one again fails the picked-clip rule test
- e2e 34 + 66 (atx): ALL PASSED, 162 checks, 1 new check in each
- Gate (atx): 295 files, 4527 tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 12:51:46 +02:00
adminandClaude Opus 5.5 d48f174caf Play videos on sprites, converted on the server with clips (#402)
game-maker / gate (push) Successful in 4m15s
game-maker / suite (push) Failing after 15m49s
A video becomes an asset like a sound: uploaded once, converted to one
format by a worker of its own, cut into clips in its tab, and worn by a
sprite through its Texture row. Only Chrome is a target, without alpha.

- Server: POST ?r=videos queues the upload under the hash of its bytes,
  so the MP4's name is known at once. The worker (VideoWorker over
  VideoFormat) writes H.264/AAC, yuv420p, constant frame rate, a
  keyframe every 15 frames, at most 1920x1080, with a poster and the
  details beside it. A failed job says why and is tried again on the
  next upload.
- An upload may be up to 2 GB. It is streamed from the request to disk
  and hashed there, so php-fpm holds none of it in memory. The other
  routes keep their 256 MB.
- The worker runs in the container game-maker-video on stein2, a
  compose project of its own (tools/video/), and in CI beside the PHP
  server. The CI image has ffmpeg for it.
- Model: asset type video with size, length, frame rate and sound, and
  clips like a sound's. The clip helpers and commands serve both.
- A sprite's Texture row offers videos and clips after the pictures.
  resolvePicture names a playing video by a key, and the texture library
  hands it to a VideoLibrary with one player per key.
- The editor shows a placed video as a quiet preview. Play starts it
  over at its clip's start and lets it be heard through the master bus,
  a pause holds it, Esc makes it quiet again.
- Video tab: the picture, a ruler, the clip strip and a track on the
  Sound tab's time axis. A drag spans a clip, a handle moves an edge,
  Play plays the picked clip.
- The picker shows a video on its way with its progress and adopts an
  upload from it once it is ready. Dropping a video file does the same.

Decided on my own:

- Two sprites wearing the same clip share one player and show the same
  picture. Control per instance belongs to the follow-up with nodes.
- A video plays in real time, the game's time scale does not slow it.
- The container may use 3 of the 4 cores of stein2.

Findings:

- The first real upload (279 MB) got 413: the API's own ceiling of
  256 MB for every request, not nginx or the Brandmauer.
- Chrome has no picture of a video that never played, and a Pixi
  VideoSource drawn before the first frame stays empty for good. The
  texture is made in the first frame callback (openVideo).
- A player nobody asks for in 2 s is let go. The inspector's PREVIEW
  asked only on an edit, so with the sprite not on screen its texture
  was destroyed under it and the batcher threw on every frame. The
  preview now redraws every frame while it shows a video, like a Time
  shader. A let-go texture keeps an empty source instead of none.
- VideoSource.load() reloads the element and cuts off an earlier
  play(). Playing starts after loadeddata.

Tests:

- PHP videos.test.php: 68 checks, with the real ffmpeg
- 300 MB upload through Brandmauer and nginx: 201 in 21 s, php-fpm at
  13.7 MB RSS at most
- Unit videos.test.ts: 20 tests
- e2e 66 (stein2, atx was off): ALL PASSED, 18 checks; e2e 04 with the
  new asset count green
- Gate (atx): 294 files, 4520 tests. serialize.test.ts used video as
  its example of an unknown type and now uses a made-up one.
- Counter-checks: a keyframe gap of 30 fails the PHP check, a key that
  keeps the video's end fails 2 unit tests, a clip without its end
  fails "plays only its stretch" in e2e 66

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 12:12:39 +02:00