admin and Claude Opus 5.5
4d0a696327
Keep composition sound in step and give compositions a bus of their own
...
A composition's sound ran on the audio clock while its picture ran on
the frames. A frame counts at most 0.1 s, so after a hitch the sound
stayed ahead of the picture until the next stop. Songs in a trailer
drifted off the cut. The items of a composition also had no bus of
their own, and a video sprite could not pick where its sound goes.
- CompositionSound.follow compares the audio clock with the picture
each frame. More than 40 ms apart, it starts the sound again where
the picture is. This holds in the tab and in a run.
- A time scale other than 1 now restarts the sound again and again.
It stays in time but comes in pieces.
- The sprite of a video item shows "Driven (by composition)" as a
fixed Video clock row. Video time is hidden there, because the
composition sets it every frame.
- Every composition has a bus in the mix and a node in the master
routing. A new one goes to the master unless other compositions are
linked elsewhere, apart from where the sounds go.
- Audio items pick From asset, To composition, Master or a bus. New
items start with To composition. Items saved before keep From asset,
so music already sent through a bus does not bypass it.
- Video sprites have a new row Video bus: From asset by default, the
master or a bus. In a composition the default is the composition's
bus, also for video sprites inside a container item.
- The chosen bus is part of the key of the video player, so a sprite
with another bus gets a player of its own.
Found along the way:
- Six tests with full lists of sprite defaults expected the old rows
and now list Video bus too.
Tests:
- tsc, eslint, Prettier clean
- Unit: 971 green in the 28 files touched
- npm run check on melektaus-atx: green, 309 files, 4804 tests
- e2e 68 (melektaus-atx): 13 checks green, 68i new. The sound item
and the video item go into mix:cp68-cut, which goes to the master.
- e2e 69 (melektaus-atx): ALL PASSED, 23 checks, 69q and 69r new.
After a hitch of 500 ms the sound restarted twice and then lay
0.5 to 5 ms off the playhead.
- Counter-checks in unit tests: without the comparison the hitch test
fails, without the bus in the preload the run test fails.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com >
2026-10-14 10:31:16 +02:00
..
2026-10-13 12:39:01 +02:00
Physics stage 1a runs on Rapier: a new Rigidbody child object (body type, mass, gravity scale, damping, fixed rotation, CCD) makes its parent fall, bounce and come to rest in play, colliders gain friction, bounciness and density, the scene gains gravity (0, 980), the new PhysicsSystem steps at a fixed 60 Hz with interpolation in world coordinates and treats a tilemap as static ground, On collision enter gains normal and impulse, play waits for Rapier and does not start without it, and the editor run keeps the old CollisionSystem; the existing collision tests run against both systems unchanged ( #146 )
2026-09-18 18:11:43 +02:00
2026-10-12 21:55:35 +02:00
2026-09-09 13:45:27 +02:00
2026-09-11 09:57:19 +02:00
2026-10-09 09:18:46 +02:00
2026-09-26 19:36:49 +02:00
2026-09-28 15:43:03 +02:00
2026-09-23 13:59:04 +02:00
2026-10-13 19:48:44 +02:00
2026-09-08 09:22:49 +02:00
2026-10-09 19:13:11 +02:00
2026-09-29 09:21:31 +02:00
2026-09-23 10:20:07 +02:00
Moving against a tilemap whose cells block with an outline no longer costs more than a whole frame: an outlined cell is its own polygon, split into convex parts, so a map that is a handful of merged rectangles when built from plain solid tiles is hundreds of solids under one entity - and Move brought every one of them into world coordinates and tested it exactly, including the ones on the far side of the map, which measured 148 ms per Move call on a 20x20 map with 16-corner outlines (34 ms at 8 corners, 0.4 ms with plain solid tiles), matching the report that standing still holds 60 fps while anything that moves stutters; the fix is a broad phase in two stages that decides nothing and only leaves out what cannot be reached - a move never ends further from its start than it is long, because sliding always shortens the rest, so every collider of the mover gets a rectangle grown by the move length and an obstacle whose rectangle does not meet it is never tested, a ray taking the rectangle around itself, and that rectangle is carried back into the map's own units with invert(m) and turned into cell coordinates (cellsWithin), a rotated map through its own axes, so a far cell is not brought into world form in the first place - solidShapesOf was O(cells) per call, which was the second cost block and the reason the 60x60 case still grew; CollisionSystem.overlaps takes the same range, so the editor run asks a map only for the cells within reach of the other side each frame, two maps narrowing nothing, and a Move obstacle now falls away at build time when its layer does not meet the mover's ( #152 ) instead of inside the loop; the measurements after: 148 -> 0.26 ms, 34 -> 0.27 ms, 60x60 20 -> 3.5 ms, plain tiles unchanged; this is no regression of the recent commits - the same benchmark in worktrees against 947a6b1 ( #141 ), 6a31f5e, 4f55f85 and ae3aa7d gives the same 85-148 ms, the cost has been there since outlined cells arrived with #141 ; new pure functions shapeBounds/grownBounds/boundsMeet in collider.ts and cellsWithin/cellsMeet in tilemapCollision.ts, 11 new unit tests in tests/core/broadphase.test.ts (the rectangles of all four shapes including a rotated box measuring its diagonal reach, touching counts as meeting, cellsWithin straight and turned by 90 degrees, a map handing out 400 cells without a range and under 10 with one, an 800 unit move from far away still stopping at the first cell, a rotated map blocking where its one painted cell was turned to, a ray across the whole map, and the editor run seeing a collider on a far cell), and four counter-checks hold: dropping the cell filter fails the counting test, padding without the move length fails 4 move tests, ignoring the rotation in cellsWithin fails 3, and a wrong range in the detection fails the touching test; one blunt test on the way - the first rotated map was painted on every cell, where even a wrongly computed cell range blocks and the counter-check stayed green, so exactly one cell is painted now and the test measures where it turned to; no e2e check and the check sum stays 1157, because a timing check in the browser would flutter under CI load while modules 40 and 42 cover the correctness anyway, the speed evidence being the node benchmark ( #184 )
2026-09-20 19:25:53 +02:00
2026-10-07 20:18:09 +02:00
2026-10-09 09:18:46 +02:00
2026-09-09 16:13:30 +02:00
2026-10-13 12:39:01 +02:00
2026-10-06 22:54:44 +02:00
2026-09-29 07:08:00 +02:00
2026-09-11 13:34:09 +02:00
2026-10-01 15:03:05 +02:00
2026-09-18 21:52:11 +02:00
2026-10-03 15:32:52 +02:00
One-way platforms, raycast and collision stay with contact data: a collider can be one-way with a side it blocks from (turning with its object, drawn thick in the scene view) and a tile can be one-way from above, one-way cells never merge with solid tiles, a contact blocks only when its normal is within 45 degrees of the blocking side, the new node Ignore one-way lets an object drop through for a while, and it all holds for Move and for dynamic bodies in Rapier, where the decision is made before each step because the hook may not touch the engine ( #155 ); the new Raycast node answers hit, object, collider, point, normal and distance in our own f64 geometry, the same in editor run and play, and the editor run draws each ray for 0.25 s ( #153 ); On collision stay gains an interval with its own clock per pair, and stay and enter gain the contact point next to normal and impulse ( #149 ); fixes that every Rapier collider of a tilemap had the shape of its last cell
2026-09-19 11:36:11 +02:00
2026-10-14 10:31:16 +02:00
2026-10-14 10:31:16 +02:00
2026-10-14 09:05:36 +02:00
2026-10-14 10:31:16 +02:00
2026-10-12 18:48:20 +02:00
2026-10-12 08:57:26 +02:00
2026-10-01 10:27:54 +02:00
2026-10-06 22:22:54 +02:00
2026-10-05 22:05:00 +02:00
2026-09-26 21:45:27 +02:00
2026-09-12 22:41:14 +02:00
2026-10-03 19:53:51 +02:00
2026-10-03 11:17:02 +02:00
2026-09-29 18:12:55 +02:00
2026-10-10 00:57:26 +02:00
2026-10-05 22:15:15 +02:00
2026-10-09 09:18:46 +02:00
2026-10-01 17:30:16 +02:00
2026-10-10 19:46:03 +02:00
2026-10-12 07:06:33 +02:00
2026-10-10 14:30:00 +02:00
2026-09-29 18:12:55 +02:00
2026-09-29 18:12:55 +02:00
2026-10-05 21:46:15 +02:00
2026-10-02 19:33:34 +02:00
2026-09-29 18:12:55 +02:00
2026-10-07 08:25:13 +02:00
2026-10-03 19:53:51 +02:00
2026-10-13 06:02:35 +02:00
A picture in the editor may now be drawn instead of uploaded, which is the piece #129 (Vector art) and #130 (Pixel editor) both need and neither could build alone: TextureRef.file has been a file name on the server, isTextureFile() checking a sha256 hash plus an extension and TextureLibrary fetching it over HTTP, so an asset that paints its own image had nowhere to put itself - the key now also takes the form gen:<assetId>@<revision>, which an uploaded file can never look like, and everything that only passes a ref along (sprite, tile, tilemap mesh, particle source, shaders, aspect ratio) is untouched because it still sees a Texture; the revision is a string, not a counter, which is the one deviation from the plan in the issue and costs nothing - gen:a1@7 and gen:a1@c0ffee go through the same door, so a generator may fingerprint its own content instead of relying on every command to remember to bump a number; two registries keyed by asset type as a plain string, so a plugin may bring its own type - registerPictureSource(type, read) in the new core/model/generated.ts tells the model canvas size and revision and resolvePicture() builds the ref from it (no rect, no slice, smooth from props.filter like a texture), and registerPictureGenerator(type, draw) in the new viewport/generatedPictures.ts hands out the finished texture, projectPictureFactory(() => state.project) tying the two together per session in App.ts; invalidation is a rule instead of guesswork at twenty call sites: GeneratedPictures<T> holds one picture per asset keyed on the revision and frees the old one as soon as the new is drawn, TextureLibrary destroying it and dropping the crops cut from it, while a generator that cannot draw yet is not remembered but asked again next time - the class imports Pixi as a type only, so the whole bookkeeping is measurable without a GPU; carried along are the picture picker (a drawn asset sits among the textures by name, without frames), the delete warning (counts tiles and animations for it too) and the asset context menu, where "Create sprite from picture" now covers it while "Create animation from texture" stays texture-only because it needs frames; one finding while probing - crop() could not have cut a generated texture at all, a RenderTexture having no source.resource to draw back from and drawImage running onto null, so it is handed out whole, a drawn picture having no sheet whose neighbouring row could bleed in; 18 new unit tests, 10 in tests/core/generated-picture.test.ts (building and splitting the key, an uploaded file is not one, no registry means no picture, resolving to canvas size, a new revision is a new key, a canvas without a size, the filter, the sprite size, picker order) and 8 in tests/viewport/generated-pictures.test.ts (drawing once, freeing the old revision and only for its own asset, null is not remembered, no key means nothing drawn, a session change gives everything back, peek), and three counter-checks hold: dropping generated assets from resolvePicture fails 5 of the 10 core tests, not freeing the old revision fails 1 viewport test, and narrowing the picker back to textures fails 3; no e2e check and the check sum stays 1157, because without a generator the groundwork shows nothing in the browser - that evidence comes with #129 ( #185 )
2026-09-20 20:09:42 +02:00
2026-09-22 21:15:21 +02:00
2026-10-07 15:06:32 +02:00
2026-09-16 11:24:34 +02:00
2026-09-29 10:36:10 +02:00
2026-10-09 09:18:46 +02:00
2026-10-04 19:13:58 +02:00
2026-10-09 09:18:46 +02:00
2026-10-07 08:25:13 +02:00
2026-09-29 18:12:55 +02:00
2026-10-10 19:46:03 +02:00
2026-10-03 10:09:45 +02:00
2026-10-07 19:13:32 +02:00
2026-10-04 18:38:06 +02:00
2026-10-02 17:59:22 +02:00
2026-09-29 09:21:31 +02:00
2026-09-30 22:08:22 +02:00
2026-10-07 21:13:18 +02:00
2026-09-26 22:33:20 +02:00
2026-09-28 07:23:00 +02:00
2026-09-26 21:12:44 +02:00
2026-09-18 16:01:12 +02:00
2026-09-29 18:12:55 +02:00
Physics stage 1a runs on Rapier: a new Rigidbody child object (body type, mass, gravity scale, damping, fixed rotation, CCD) makes its parent fall, bounce and come to rest in play, colliders gain friction, bounciness and density, the scene gains gravity (0, 980), the new PhysicsSystem steps at a fixed 60 Hz with interpolation in world coordinates and treats a tilemap as static ground, On collision enter gains normal and impulse, play waits for Rapier and does not start without it, and the editor run keeps the old CollisionSystem; the existing collision tests run against both systems unchanged ( #146 )
2026-09-18 18:11:43 +02:00
2026-10-14 10:31:16 +02:00
2026-10-13 13:27:43 +02:00
2026-10-14 09:05:36 +02:00
2026-09-29 18:12:55 +02:00
2026-09-28 14:35:17 +02:00
2026-10-13 10:08:54 +02:00
2026-09-29 18:12:55 +02:00
2026-09-29 18:12:55 +02:00
2026-10-01 07:28:49 +02:00
2026-09-28 14:35:17 +02:00
2026-10-09 20:27:32 +02:00
2026-10-02 19:33:34 +02:00
2026-09-21 07:29:26 +02:00
2026-09-23 10:20:07 +02:00
2026-09-19 16:30:52 +02:00
2026-10-10 12:40:11 +02:00
2026-10-03 14:51:18 +02:00
2026-09-23 06:57:43 +02:00
2026-10-06 17:38:44 +02:00
2026-10-09 15:31:33 +02:00
2026-10-07 12:11:54 +02:00
2026-09-18 16:56:47 +02:00
2026-10-01 09:52:57 +02:00
2026-10-06 11:34:30 +02:00
2026-10-07 13:15:52 +02:00
2026-09-29 06:01:27 +02:00
2026-09-09 13:09:13 +02:00
2026-10-02 23:52:49 +02:00
2026-09-29 18:12:55 +02:00
2026-09-28 13:54:57 +02:00
2026-10-05 21:47:00 +02:00
2026-09-29 18:12:55 +02:00
2026-10-04 19:13:58 +02:00
2026-10-13 19:48:44 +02:00
2026-09-23 16:44:19 +02:00
2026-10-13 13:27:43 +02:00
2026-10-03 21:45:04 +02:00
2026-10-14 10:31:16 +02:00
2026-10-07 21:27:22 +02:00
2026-10-07 21:04:16 +02:00
2026-09-22 10:23:57 +02:00
2026-09-23 06:06:53 +02:00
2026-09-28 10:00:22 +02:00
2026-09-19 16:30:52 +02:00
2026-10-14 10:31:16 +02:00
2026-10-03 14:51:18 +02:00
2026-10-03 11:43:54 +02:00
2026-10-03 17:09:53 +02:00
2026-10-11 18:35:20 +02:00
2026-10-02 16:50:50 +02:00
2026-10-11 18:35:20 +02:00
2026-10-14 10:31:16 +02:00
2026-09-19 12:54:53 +02:00
2026-10-09 15:31:33 +02:00
2026-10-13 13:27:43 +02:00
2026-10-09 19:13:11 +02:00
2026-10-08 21:22:59 +02:00
2026-10-09 15:31:33 +02:00
2026-10-08 21:13:22 +02:00
2026-10-10 20:30:59 +02:00
2026-10-09 20:27:32 +02:00
2026-10-04 18:15:56 +02:00
2026-10-04 18:38:06 +02:00
2026-10-09 20:27:32 +02:00
2026-10-09 20:53:49 +02:00
2026-09-12 18:00:23 +02:00
2026-09-29 16:16:37 +02:00
2026-09-12 13:35:02 +02:00
2026-09-29 16:16:37 +02:00
2026-09-24 10:55:42 +02:00
2026-10-08 10:36:25 +02:00
2026-10-13 15:18:59 +02:00
2026-10-13 15:18:59 +02:00
2026-10-14 10:31:16 +02:00
2026-09-27 21:07:04 +02:00
2026-10-07 08:25:13 +02:00
2026-09-29 19:00:22 +02:00
2026-10-02 07:28:39 +02:00
2026-09-25 06:18:41 +02:00
2026-09-18 15:12:07 +02:00
2026-09-18 21:52:11 +02:00
2026-09-29 19:00:22 +02:00
2026-10-01 09:52:57 +02:00
2026-09-18 13:58:03 +02:00
2026-10-07 16:54:59 +02:00
2026-09-14 09:31:53 +02:00
2026-09-21 08:00:30 +02:00
2026-09-14 08:25:49 +02:00
2026-09-29 16:16:37 +02:00
2026-09-30 22:08:22 +02:00
2026-09-23 13:59:04 +02:00
2026-09-29 18:12:55 +02:00
2026-09-29 18:12:55 +02:00
Refactoring round 5 covers the code since round 4 without changing behaviour: four identical copies of dot() and three of "nearest point on a segment" stood in the collision math, in the hit area of the collider outlines and in the polygon handles, so they moved into the new module src/core/util/vec.ts; the warning line, the busy state, info() and the caret reset were written out line by line in both the sign-in screen and the password dialog and are now FormStatus; the closing row "Cancel plus a confirming button, optionally one on the left" was built by hand in FormDialog, FilePicker, OpenProjectDialog and PasswordDialog and is now dialogButtons(); the header bar was drawn by the toolbar, the TabHeader and the Breadcrumb each on its own and is now drawHeaderBar(); AddColliderCommand and AddRigidbodyCommand share a base, each subclass only builds its own asset; Editor.ts derives the panes from assetEditors instead of repeating eight entries and clamps its splitters with clamp(); jscpd over src falls from 0.19 % to 0.14 %; dead code is gone - listScenes() has been orphaned since the Scene picker fell away ( #183 ), DEFAULT_FILTER ( #152 ) was never used, the two test hooks projectPickerRowAt/rowPosition and particleCompiles are read by no tool, and four functions lost an export they did not need; seven comments that told history ("used to", "no longer", "since #...") describe the state of things now, and that this changed nothing was proved by a rebuild - the bundle is md5 identical, b4ad0dfe... before and after; the README said Logout sits at the far right of the toolbar, which has been the Account menu since #182 , and wiki/Architecture.md was missing src/core/util/ altogether; 4 new unit tests cover vec.ts, and switching the clamp on the segment off makes exactly one of them fail while none of the 76 neighbouring tests notice, so that clamping was covered nowhere before ( #85 )
2026-09-20 17:24:42 +02:00
2026-10-01 23:13:43 +02:00
2026-10-01 23:13:43 +02:00
2026-10-01 23:13:43 +02:00
2026-10-02 11:20:55 +02:00
2026-10-01 23:13:43 +02:00
2026-10-01 23:13:43 +02:00
2026-10-11 14:51:20 +02:00
2026-10-14 10:31:16 +02:00
2026-09-18 21:52:11 +02:00
2026-10-01 10:27:54 +02:00