admin and Claude Opus 5.5
4407c02078
Keep who played an animation beside its player, not on it
...
d6fd000 wrote the behaviors that played an animation on the player
itself. A player is plain data, and those behaviors are entities of
the run with their parents and children. The actor smoke reads the
world as JSON and threw "Converting circular structure to JSON" as
soon as its graph had played a walk animation.
- The starters live in a WeakMap next to the player and are read
with startersOf. The player carries no field for them any more.
- The animation system reads them from there when a pass ends. What
a behavior hears does not change.
- Only the hook the check scripts read the world through was hit.
The editor and a running game never serialise a player.
Tests:
- Gate on melektaus-atx: 219 files, 3400 tests, CHECK EXIT 0. It
ran on the working tree, which also held the fix to e2e 47 and
the kept panel sizes that are not committed yet. Seven of the
tests belong to those.
- One new unit test: a player played by an asker that reaches back
to its holder stays plain data, through JSON and structuredClone,
and names the asker once. Its counter-probe bites.
- Smokes on melektaus-atx: 24, 19 and 21 checks, all pass. Before
the fix the third stopped after 9 checks.
- e2e 6,17-26 on melektaus-atx: 480 checks, all pass.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com >
2026-10-02 23:15:21 +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-02 23:15:21 +02:00
2026-09-09 13:45:27 +02:00
2026-09-11 09:57:19 +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-09-08 09:22:49 +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-09-09 16:13:30 +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-01 16:16:43 +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-01 17:30:16 +02:00
2026-10-01 10:27:54 +02:00
2026-09-27 15:58:05 +02:00
2026-09-26 21:45:27 +02:00
2026-09-12 22:41:14 +02:00
2026-09-29 18:12:55 +02:00
2026-09-27 15:58:05 +02:00
2026-10-01 17:30:16 +02:00
2026-10-02 19:33:34 +02:00
2026-09-29 18:12:55 +02:00
2026-09-29 18:12:55 +02:00
2026-10-02 19:33:34 +02:00
2026-09-29 18:12:55 +02:00
2026-09-13 20:09:59 +02:00
2026-09-16 18:13:11 +02:00
2026-09-21 10:27:17 +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-09-16 11:24:34 +02:00
2026-09-29 10:36:10 +02:00
2026-10-01 17:30:16 +02:00
2026-09-29 18:12:55 +02:00
2026-10-02 13:53:10 +02:00
2026-09-29 18:12:55 +02:00
2026-09-29 18:12:55 +02:00
2026-10-02 21:43:12 +02:00
2026-10-01 17:30:16 +02:00
2026-09-29 18:12:55 +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-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-09-29 18:12:55 +02:00
2026-09-28 14:35:17 +02:00
2026-10-02 21:43:12 +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-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-09-23 06:57:43 +02:00
2026-09-18 16:56:47 +02:00
2026-10-01 09:52:57 +02:00
2026-09-29 06:01:27 +02:00
2026-09-09 13:09:13 +02:00
2026-09-19 12:54:53 +02:00
2026-09-29 18:12:55 +02:00
2026-09-28 13:54:57 +02:00
2026-09-26 23:50:49 +02:00
2026-09-29 18:12:55 +02:00
2026-09-23 06:57:43 +02:00
2026-09-23 16:44:19 +02:00
2026-09-29 13:30:38 +02:00
2026-10-01 17:30: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-01 17:30:16 +02:00
2026-10-02 16:50:50 +02:00
2026-10-02 20:33:20 +02:00
2026-09-19 12:54:53 +02:00
2026-10-02 14:41:30 +02:00
2026-09-15 13:24:26 +02:00
2026-09-29 18:12:55 +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-09-11 18:40:47 +02:00
2026-09-29 06:01:28 +02:00
2026-10-01 17:30:16 +02:00
2026-09-27 21:07:04 +02:00
2026-09-18 12:14:01 +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-09-29 19:00:22 +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-09-18 21:52:11 +02:00
2026-10-01 10:27:54 +02:00