d01e2b832e91167abbae6b473e99f1a6191d8ef7
552
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
d01e2b832e |
Let the agent edit animations, frames, tiles and tilemaps (#429)
After stage 5a the agent could build behaviors and scenes, but not animate an object, cut a texture into frames or paint a tilemap. Stage 5b adds three areas with 30 commands, 115 in all. - animation (12): set the root, add, retarget and make rows relative, set, move, copy and delete keys, easing, insert or remove frames, and curve points. - textures (8): add, change and delete frames, slice a grid, nine slice, sorting line and collision outline. - tiles (10): the images of a tile, painting tilemaps, layers (add, copy, move, change) and the border of a placed tilemap. - get_asset names the targets of an animation with their animatable properties. A tilemap up to 400 cells shows its cells, a bigger one only their count. - After such a call the editor opens the tab of the animation, curve, texture, tile or tilemap. A frame edit selects the frame there. - One paint step takes rectangles and single cells. Rectangles go first, so a cell can cut a hole into a rectangle. Cells outside the grid, ids that are no tile and more than 20 000 cells are refused. The grid is the tilemap's largest, a grown placement included. Left out on purpose: Generate collision, because it reads the pixels of the texture, and pasting keys, which goes through the clipboard. Found on the way: - Nine slice without fill now keeps the fill the image has. The model's default is repeat, not stretch as first described. - Painting ran the other way round, rectangles overwrote cells. Tests: - agent-art (new, 11) and the other agent tests green, 64/64. 5 bends of the code, all red. - e2e 71 on atx: 23/23, the new check opens the agent's animation in its tab and reads its targets. - Gate on atx: 321 files, 4969 tests. - Live on a test project: a Spin animation of 360 degrees in 30 frames matched 12 degrees per frame to 0.01 frames in the game. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
0d8fd6ff52 |
Let the agent use the graph, scene and project commands (#429)
The agent knew 23 of the editor's commands. It could not point a Variables node at a variable, group nodes, edit ports, transitions, containers, colliders or the project settings. Stage 5a brings the catalog to 85 commands in five areas and keeps every answer short. - The catalog lists names and descriptions only. With a query (an area or command names) it adds the arguments. All schemas at once would pass the 60 000 character limit of one answer. - add_port points a Variables node at a variable. The port takes the variable's kind, and an unknown name is refused with the list of the ones that exist. - add_group and add_comment take nodes instead of coordinates. They follow the nodes after the arrange step, in the same undo step. - The rule for which list a port picks from moved into portChoices.ts. Inspector and agent now share it. - The frame of a group around nodes moved into annotationDrag.ts, so the button and the agent get the same frame. - list_assets returns the project settings. get_asset shows the content of placed containers with the ids overrides need. get_graph shows declared ports and frame colors. - A new node is placed at the height of its port, in the free spot whose links cross the fewest others. A source goes left of the node it feeds. Before, new nodes always went right of the rest. - Arrange breaks a tie in crossings by putting the flow on top, and exec links pull harder when a column is sorted. - A comment over a group keeps clear of the group's title bar. - The agent's instructions and lint_graph ask to look at crossings too and to call arrange_graph when there are more than needed. Left out on purpose: file assets (no upload), paste from the clipboard, internal wrapper commands and switching plugins on. Found on the way: - Variable commands on a Struct asset did not run through WithStructTypes. New fields did not reach variables of that type. - A query "variables" also matched set_variables_scope. A word that names an area now means only that area. Tests: - agent-catalog (new, 10), agent-edit, graph-arrange (3 new) and relay green. 11 bends of the code, all red. - e2e 71 on atx: 22/22, the new check sees port speed and a group around all 4 drawn nodes. - Live on a test project: a speed variable of 300 moved the player to x 150.03 in 0.5 s. A re-placed Variables node landed below Axis input with 4 crossings instead of 7. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
1943539a7a |
Let the agent play the game and read its state through MCP (#429)
An agent could build a game object but not see whether it works. It had to ask the user to press the keys. Now it can play the game in the editor itself, send keys and mouse input and read what happened. - play_input sends steps into the running game: hold or tap a key, keep it down or let it go, click, press, release or move the mouse, turn the wheel, wait. Holding and waiting count game seconds. - A mouse target is a point in world units or an object by id or name. A click on an object reports whether it landed on it. - The answer names the game time used, the keys still held, the mouse and the new console lines. - One tool instead of press_keys and pointer from the plan. A step list can then mix both, like holding Shift during a click. - get_runtime reads the running game: objects with id, type and position, the own variables of their behaviors, the Variables assets, the camera, keys, mouse and the console. Named objects come with all their properties. - A hidden editor tab gets no frames. play_input says so at once. If game time stands still for 25 s it stops, names the step and lets go of every key it held. - get_view and play say when the scene has no active camera. The first agent test missed that and only saw it on a screenshot. - The relay instructions tell the agent to test what it built. Found in the live test on a real project: - A click on an object by its name reported hit false. The hit test looked the target up by id only. - get_runtime listed the built-in switches of a behavior (enabled, runInEditor) as its variables. Tests: - agent-play (new), agent-edit, agent-view, relay green. - 5 counter-checks red: hold counts one frame, key never released, click ignores the object, hidden tab not refused, no variable filter. - e2e 71: 21/21. The agent holds ArrowLeft 0.5 s (x -120 to -270) and clicks the object (hit true). - Gate: CHECK EXIT 0, 319 files, 4944 tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
97e0d93168 |
Let the editor follow the agent's edits on screen (#429)
The user asked to see what the agent is working on through MCP. Until now the agent had to call open for that, and mostly did not. - After run_commands the editor shows what the call worked on last. A graph opens its tab with the touched nodes selected, deleted ones left out. A scene or container shows with its objects selected. Any other asset shows in the inspector. - Objects in another scene switch the scene inside the same undo step. A switch of its own would be its own step, and Ctrl+Z would take back the switch before the agent's edit. - A graph tab that is open already moves its camera after the nodes. It stays put while they are in view, moves only as far as needed with the zoom kept, and frames them like Fit only when they do not fit. This is revealBoxes in framing.ts and the state's reveal event. - A tab that has just opened frames itself first and moves after that. - The account menu has "Follow agent" below "Agent", on by default and kept in the browser. While the game plays the editor never follows. - The run_commands description and the relay's instructions tell the agent about it. Tests: - agent-edit.test.ts: 3 new tests, each red with its bent-back fix (scene switch outside the undo step, deleted nodes selected, switch ignored) - graph-framing.test.ts 29/29, 6 of them new - e2e 71 on melektaus-atx: ALL PASSED, 20 checks. 71g4 adds a node 3000 px to the right of an open tab: it ends 24 px inside the edge, zoom still 1 - Tried live on a test project through a real MCP session: the tab opened, the nodes and the object were selected. With the old build a far node stayed out of view, after the reload the camera went there - Gate on melektaus-atx: CHECK EXIT 0, 318 files, 4931 tests Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
732989a452 |
Keep Fit inside the song tab's header again (#429)
Suite 719 for |
||
|
|
0dd671ea26 |
Count each connected agent once in the editor badge (#429)
The Agent badge showed 2 or 3 while one Claude Code session was connected. The relay log against the open TCP connections in the container showed two causes. - A client may drop its event stream while the relay still asks the API about its token. The close was over before the relay listened for it, so the stream stayed in the list and counted for good. The relay now listens before the token check and keeps no stream that is already gone. It listens on the response, not the request. - Claude Code opens three sessions whenever it connects and keeps a stream on one of them. It never ends the others with a DELETE. Any session counted for two minutes after its last request, so the badge stood at 3 after each start. Now a session counts while its stream is open or it called a tool in the last two minutes. - The relay logs session start, stream open and close and each change of the count, with session ids cut to 8 characters. One case is left: after a relay restart Claude Code sometimes repeats its first call on a spare session. The badge then shows 2 for two minutes and drops to 1 on its own. Tests: - relay.test.ts: 27/27, three new. A dropped stream counts out, a stream dropped during the token check never counts, and spare sessions without a tool call do not count. - Counter-checks: without the early close listener the drop test turns red, with the old count two tests turn red. - Live: the badge showed 2 with one open SSE connection before. After the fix it shows "Agent · 1" and stays there after a normal call. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
b9d1602b94 |
Arrange graph nodes for the agent and with a button (#429)
An agent that built a graph through MCP left every new node in one row. In the first real test that put nine nodes on a line 2080 px long, with the Owner's links running straight through all the others. Placing nodes by hand cost nine move_node calls and guessed heights. The agent now gives no coordinates, and the editor lays the nodes out. - arrange.ts is a pure function. It puts the flow into columns from left to right and sets an Owner or Get right before the first node it feeds. It orders each column for the fewest crossings and lines nodes up by their linked ports without overlaps. - Node sizes come from the drawn NodeView in the browser and from an estimate in unit tests. Both share the constants in nodeBox.ts. - run_commands arranges the nodes that came without x and y when the call ends, inside the same undo step. Nodes that were there stay. - New command arrange_graph and new tool lint_graph. The lint counts overlaps, crossings, links through foreign nodes, backward links and the longest links, with example ids. - The relay instructions tell the agent not to give coordinates and to fetch far values with a second Get or Owner node. - An Arrange button next to the snap magnet in all eight graph tabs. It arranges the selected nodes, or all of them, as one undo step. Found while testing on a real project: - lint_graph missed a crossing where two links swap rows exactly mirrored. Both curves then share a sample point in the middle, and only a crossing between two sample points was counted. Links that meet on a point count now (segmentsMeet). - The Arrow-key graph with one Owner for four Moves cannot get below 8 crossings. The Owner feeds object in row 2 of Move, between exec and dt of the key, so it always crosses one of them. Tests: - graph-arrange, agent-edit, snap-toggle: 33/33. The swapped-rows test was red before the fix (0 instead of 1). - Four counter-checks that bend arrange or the edit plan, all red. - e2e 71 on melektaus-atx: 18/18. Lint after the build shows 0 overlaps, 0 links through nodes, 0 backward. Arrange takes a tangled graph from 3 backward links and 2 crossings to 0, and one Ctrl+Z restores it. - Gate on melektaus-atx: CHECK EXIT 0, 318 files, 4917 tests, measured before the lint fix. - Live through MCP on the user's project: lint_graph reports 8 crossings after Arrange, matching the screenshot. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
25af064249 |
Let an AI agent read and edit the open project through MCP (#429)
Stage 2 of #429. Until now an agent could only look at the editor. Now it reads the project and changes it through the same commands the user's own edits run, so every change shows up live and one Ctrl+Z takes back a whole agent call. - Read tools: list_assets, get_asset, get_graph and catalog. The catalog lists the commands with their arguments, the nodes of a graph from the same palette the user sees, asset types and variable kinds. - run_commands runs its steps as one undo step named "Agent: ...". A failing step rolls back the steps before it. The answer names the step and the reason the editor gave. - A step can name what it made ("as") and later steps use "$name". Each step is built after the one before has run, so one call can make a behavior and fill it. - 23 commands cover assets, variables, scene objects and graphs. Textures, fonts, sounds and videos need a file and are left out. - open, select, play and undo/redo drive the editor. - Deleting asks the user with the same text as their own delete. The tab sends "wait", and the relay then gives the call 2 minutes instead of 30 seconds. - tools.json is the only source of the argument schemas. argCheck checks every call against them before anything runs. - An open menu now names its first entries in get_view, instead of an empty title. - The new host functions live in editorToolHost.ts, so Editor.ts does not grow further. Found while testing: e2e 71 hung for 30 s in Page.navigate after a failure. The beforeunload guard of an unsaved project held the page and hid the real error. The module now reports the error first and waits for the save before it navigates. Tests: - Gate on melektaus-atx: CHECK EXIT 0, 317 files, 4903 tests. - agent-edit 14/14, agent-view 10/10, relay 24/24. - e2e 71 16/16 on atx and against the live site. One call builds a sprite with an arrow-key behavior, play moves it from x 0 to -125, and one undo restores the assets exactly. - Counter-checks turned red: no rollback, a partial undo, no delete question, a relay that ignores "wait". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
48ec722660 |
Let an AI agent see the open editor through MCP (#429)
Claude Code should be able to work inside the user's open editor tab. This first stage connects the two and lets the agent look: what the user sees and a picture of it. Changing the project comes in stage 2. - An agent token per account opens only /mcp, no API route. The dialog "Agent tokens..." in the account menu creates it once, shows the ready "claude mcp add" line and revokes it. The server keeps only its sha256 (api/lib/AgentTokens.php). - Wrong tokens count into the sign-in brake from #427. Changing the password, renaming or deleting an account ends its tokens. - The relay (tools/mcp/) is a small Node service without packages, WebSocket server included. It runs in the container feuerwerk-mcp because PHP-FPM cannot hold connections. Whether a token or a tab ticket is valid is still decided by the PHP API, through two routes that answer only with the relay key. - The tab joins over WebSocket on the same origin with the normal session, an Origin check and a one-time ticket. It connects only after "Agent" is switched on in the account menu. One tab per account is active, a new one takes over. Guests get nothing. - Tools: get_view (project, tab, document, selection with names, camera, play state, open dialog) and screenshot. The resource feuerwerk://view can be subscribed. Claude Code does not act on that reliably, so a changed view is also noted on the next answer. - A badge next to the account menu shows the connected agents. It counts only agents with an open stream or a request in the last two minutes. - CI starts the relay in front of php -S (tools/ci/serve.sh), and e2e 71 runs the whole path in the browser. Found on the live test: the badge showed four agents because old sessions of revoked tokens were still counted. Tests: - Gate on atx: 316 files, 4888 tests - relay protocol test with fake API and fake tabs: 23/23 - get_view, tools, socketUrl: 16/16 - PHP agents 29, rights 103 checks - e2e 71 on atx 10/10, against the live site 10/10, no CSP violation - real Claude Code against the live site: get_view answered - ZAP against /mcp: nothing at High or Medium - every new check fails when the guard it covers is taken out (6) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
edef2b6323 |
Serve the editor from feuerwerk.nockal.com as well
The subdomain proxies to the same folder, but the build had the path /feuerwerk/ baked in. On feuerwerk.nockal.com the page loaded its assets, the API and the data URLs from /feuerwerk/... and got 404. The session cookie had Path=/feuerwerk/, so the browser would never have sent it to /api/ there either. - site.ts takes the base path from the folder of the open page: /feuerwerk/ on nockal.com, / on the subdomain. Release links point at the address you are working on. - vite.config.ts builds with a relative base. The copy plugin rewrites the pages to ./dist/assets/ and stops the build if it finds nothing to rewrite. The dev server now runs at http://localhost:5175/. - Auth.php sets the cookie without a Path, so it belongs to the API folder the browser sees. Sign-in and sign-out also clear the old /feuerwerk/ cookie, otherwise it would keep signing in after a logout. Existing sessions stay valid. - README: the new dev server address. Tests: - curl on both addresses: pages, JS, CSS and API 200 - sign-in, session, sign-out on both addresses - e2e 70 against both addresses: 13/13 each, no CSP violation - tsc, lint, prettier clean, site and releases tests 14/14 - PHP tests 7 of 8 files green, videos.test.php fails only because the web-php-fpm container has no ffmpeg Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
5b9d084dfe |
Keep a clip that starts later playing at its first frame
The e2e 68 check "a clip that starts later" was red in 3 of the last 8 suites (CI tasks 697, 703, 712). Two causes sat on top of each other. - The check measured too late. It started sampling after play(), and under CI load the clock was already at 0.05 to 0.22 s. It now starts before the click, like 68d does. - Measured early, a real bug showed: the video held for one frame at its first picture. The first picture triggers a redraw, and the game takes its first step in the same frame. readSpeed got both calls with the same frame time and divided by 1 ms. The speed came out at 16.7x, which counted as a jump, so the player paused. - readSpeed now measures a step without elapsed wall time against one frame at 60 fps. A real jump still counts as a jump. - e2e 34 "the pause holds the stream" also failed in task 712. Its cause is still open. The failure message now names the state before the pause, so the next red run shows where the samples got lost. Tests: - video-follow.test.ts 18/18, the new test is red without the fix - e2e 68 on melektaus-atx: ALL PASSED, 21 checks - Gate on melektaus-atx: CHECK EXIT 0, 314 files, 4853 tests Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
925a39a276 |
Let guests try a demo project without an account (#428)
The editor was only open to somebody with an account. A guest now picks
a demo project on the sign-in screen and gets the whole editor on a copy
held in the tab. The server writes nothing for a guest, so there is no
guest session, no guest folder and no quota to keep.
- Demo copies are frozen copies like a release, in data/demos/. An
administrator makes, updates and removes one with "Demo copy..." in
the project's main menu. GET demos and GET demos/{token} need no
session. An empty project is just a demo that is empty.
- Demo material: an administrator offers an uploaded file with "Offer
as demo" in the pickers (PATCH {demo}, data/demo-files.json). Every
list carries it with demo: true, for accounts and guests alike.
Signed out, a list holds the demo material alone, without the owner.
- An offered file is only an administrator's to delete. A file a demo
copy names counts as used, like one a release names.
- The guest's project lives in MemoryProjectStore. Every store that
would write refuses in the tab before a request is made, and the
autosave is not attached at all.
- The main menu drops projects, snapshots and release for a guest. The
pickers drop Upload and Delete, a dropped or pasted file is refused,
and a recording is saved as a download.
- The toolbar says the work is not saved. The Account menu holds
"Sign in..." instead of Logout and the password.
- A guest's demo never goes into the address: a reload brings the
sign-in screen back, not the copy. Found by e2e 70.
- GET plugins needs no session any more, since a demo may switch one
on. It only reads the server's own modules.
Tests:
- e2e 70 (atx): ALL PASSED, 13 checks. Counter-test with the guest
autosave sent to the server: 70f and 70g red on the PUT.
- tests/core/guest.test.ts: 9 tests. PHP: demos 20, files 46,
rights 80 checks (the matrix asks every new route).
- Gate (atx): CHECK EXIT 0, 314 files, 4852 tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
cfd6d7a8df |
Lock the server down before the editor goes public (#427)
The editor is about to be opened to strangers: a post on Reddit and a guest access (#428). nginx served the whole workspace and ran every PHP file in it, the API let anybody guess passwords as fast as they liked, and one account could fill the disk. - nginx (shared web stack, outside this repo, backup in /root/config-backup-20261015/) hands out only the two pages, dist/, public/brand/, the uploads by kind and ending, and api/index.php. Everything else under /feuerwerk/ is a 404. - PHP behind it has display_errors off and no X-Powered-By. Plain HTTP forwards to HTTPS, the API refuses it. - Headers: a Content-Security-Policy without inline code or eval, nosniff, frame-ancestors, HSTS, Referrer-, Permissions-, COOP, CORP. - vite.config.ts writes the same policy into the built pages, so CI runs the whole suite under it. Pixi gets by without eval through pixi.js/unsafe-eval. The page CSS left its inline style for a file. - The harness fails a module for anything the policy refuses, from Chrome's log and from the page's securitypolicyviolation event. - LoginThrottle: five wrong passwords from one address within 15 minutes, and it gets 429 until 15 minutes after the last of them. The address is the last X-Forwarded-For entry, the Brandmauer's own. - Quota: 1 GB per account for uploads, projects and snapshots. Only a write that grows the account is refused (413). Admins have no limit. - A write whose Sec-Fetch-Site says another site is refused (403). - The session cookie carries Secure, nginx passes HTTPS on. - showFailure puts the message in as text, not as HTML. - Every PHP script outside api/ stops unless it runs on the command line. - The video worker sees the checkout read-only and can only write data/videos. No capabilities, 2 GB and 256 processes at most. - tests/php/rights.test.php runs the API under php -S on a scratch directory and asks every route as signed out, owner, stranger and admin. A route named in api/index.php but missing in the matrix fails it. limits.test.php covers the brake and the quota. Findings: - tmp/ was readable from outside, with 79 copies of project files. Closed on 15.10. at 20:02 with chmod 750. The log back to 11.10. shows only our own requests. - Port 8080 is bound on 0.0.0.0, so the LAN could reach nginx past the Brandmauer. Left as it is: the web stack is not this project's. - npm audit finds nothing in what is shipped. Dev tools (vitest, brace-expansion, source-map-js) have advisories, not touched here. Tests: check 313 files / 4843 tests. PHP limits 21, rights 60, accounts 56, snapshots 15, releases 20, files 46 (videos 50/51, no ffmpeg in the web container). e2e on stein2, atx was off: 34+38+41 197/198, the one red check (a click in 41) passes with 41 alone, 27/27. 67 misses frames at 1080p on stein2 with and without this. Path list from outside: 10 served, 26 refused. ZAP baseline against the live site: nothing High or Medium under /feuerwerk/. Counter-checks: rights.test.php without the cross-site guard, the quota, the brake, the admin check of users, or with an undocumented route fails each time. A policy without wasm-unsafe-eval fails e2e 41 with the harness's own CSP line. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
2f13f9a0b9 |
Keep a recording behind another tab from dropping its frames
In CI run 699 check 68k (real time behind a tab) got 21 of 120 frames. The beat ticked 100 times, but 83 frames were dropped as busy. Late in a long browser session the GPU behind a hidden tab took up to 1.5 s to finish a read back, so all three read-back buffers were still on their way when the next frame was due. Locally on melektaus-atx this showed only in the full run of modules 01 to 68, and not in every one. - The render loop no longer draws the screen while the page is hidden. Nobody sees it there, and the drawing was competing with the read backs for the slow GPU. - A recording of the editor copies the canvas, so it turns the drawing back on with `drawWhileHidden` while it runs. - `PictureReader.collect` reads the oldest buffer at once when the page is hidden and no buffer is free, instead of dropping the frame. The main thread waits for the GPU there, which nobody sees either. - More buffers are no fix: with 30 of them the frames piled up at the encoder instead (45 frames, 56 dropped). Tests: - render.test.ts: 18/18, the new test is red without the hidden check - Full run of modules 01 to 68 on melektaus-atx with a counting probe: read at once only 18 frames, 63 dropped; both parts 106 frames, 2 dropped - e2e 67 and 68 on melektaus-atx without probe: 68k green, 97 frames, 0 dropped. Red there only "a clip that starts later", the known timing wobble from task 697 - Gate on melektaus-atx: CHECK EXIT 0, 313 files, 4841 tests Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
406839baea |
Drag clips of videos and sounds straight onto a track (#425)
A clip of a video could not be laid on a composition track at all, and neither could a clip of a sound, although #418 and the wiki said so. The only place a drag starts is the asset panel, and it listed assets, not clips. The texture label of an item playing a video clip was dead too, because the reference lookup did not know video clips. - The asset panel gives a sound or video with clips a chevron of its own. It unfolds one row per clip and starts closed. - A clip row drags like an asset. One click shows the clip in the inspector without leaving the tab (`EditorState.glance`). A double click opens its sound or video with the clip picked. - A video clip dropped on a composition track becomes an item as long as the clip. It wears the sprite of its whole video, so a composition keeps one sprite per video, and the clip is a texture override on the object. Sprite, item and override are one undo step (`droppedPicture`). - A sound clip becomes an audio item as long as the clip. The song tab takes sound clips on the same way, through `findSound`. - `referencedTarget` lets a reference label link to the clip itself. One click pings the clip row, a double click opens the asset with the clip picked. The Plays row of an audio item reads "Sound / Clip". - `clipOwner` in `sound.ts` finds the sound or video a clip id belongs to. `activateAssetOrClip` uses it for the double clicks. Found on the way: the Blip of the e2e seed is only 0.25 s long, so a clip of 0.2 to 0.7 s on it is cut to one frame. 69s takes the Drone (0.75 s) instead. Tests: - tsc, eslint, Prettier clean - Unit on stein2 (composition, references, sound, video, editor): 94 files, 1238 tests green - e2e 69 on melektaus-atx: ALL PASSED, 25 checks, 2 of them new (69s) - Gate on melektaus-atx with the fix for 68k on top: CHECK EXIT 0, 313 files, 4841 tests - Counter checks, all red: no clip rows in e2e 69 (no drag starts), no clip override in the unit test, the link aimed at the asset instead of the clip - Not covered by its own e2e check: sound clips on the song tab Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
0701fe3856 |
Share the parts the sound devices and timelines repeated (#85)
Round 12 of the clean-up. Duplicate code had grown from 33 clones (249 lines) after round 11 to 140 clones (1263 lines), almost all of it in the panels and faces of the sound FX devices (#379 to #401). It is down to 57 clones (416 lines) now, and src is 816 lines shorter. - deviceParts.ts holds what the device panels built by hand: the head, rows of knobs, the screen style and the row layout for groups of knobs. Twelve panels use it. panel.knobs stays because the debug hooks read it. - DeviceFace is the base of the twelve faces. It finds the node and does wired, preview, commit, set, hide, reload and audition. compressorWired lives there as paramWired. - TextPool replaces ten copies of "make texts as needed, hide the rest" in the wave and video tabs, the rulers, the piano roll and the song and composition timelines. clipStrip.ts is the clip label strip the wave and video tabs share. - core/util/bytes.ts has the concat that ogg, matroska and avcColour each wrote out. - Dead code is gone: clear on the video players (never called since #402), posterFileOf and videoTimeAt (only tests called them) and easeOut (the same as glide(t, 3)). Values only used in their own file lose their export. - Comments over 125 columns are wrapped, and comments that told the history ("as before", "used to") say what is, in src and in the e2e modules. - Left alone on purpose: AnimationEditor and CompositionEditor share only the play button, and the curve panels are built differently. - Chip now places its boxes through put, which rounds. They were whole numbers already. The three row layouts are byte-identical before and after. - README lists the video and composition assets and the Videos and Compositions pages. Tests: - Gate (melektaus-atx): CHECK EXIT 0, 313 files, 4835 tests. - e2e 25, 34, 64, 65, 66, 69 (melektaus-atx): ALL PASSED, 330 checks. - TextPool without keeping its texts and framesToRoom without making room both go red. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
df6dbe97c5 |
Build only the meshes of the tiles a painted cell changes (#424)
Since #348 a painted cell worked out only the cells around it, but the layer's tiles still came as a new list. groupByTile grouped that list afresh, so meshFor saw no group again and every mesh on the layer was built anew. On 120x120 that was the last cost that grew with the map. - A patched list of tiles remembers the list it came from and which entries went and came (tilesPatch). A cell that is drawn the same keeps its entry, and a patch that changes nothing drawn hands back the very same list. - groupByTile and layerRows patch the groups of the list before (patchedGroups). An untouched group stays the same array, so its mesh is not built again. On a layer sorted by Y this holds for each row and for the groups in it. - A tile new to the layer still works the whole layer out again, as since #348. - Stage 2 of the issue (patching quads inside a mesh) is not needed. The tilemap geometry no longer shows in the CPU profile. Measured with the #348 probe, a brush stroke on a copy of Garden of Sacrifices on stein2: - Meshes built per frame: 21.9 to 0.94 on 60x60, 40.7 to 1.22 on 120x120. - fps while painting: 49.4 to 51.1 on 60x60, 42.0 to 45.8 on 120x120. - Painting now costs both sizes about 10 fps. The gap left between the sizes is drawing the larger map, which shows without painting too. Tests: - Gate on melektaus-atx green, 312 files, 4832 tests. - The random walk from #348 also compares groups and rows with a fresh grouping after each of its 400 steps, and counts reused groups. - A fixed test paints a cell and keeps every other group and row. - Three counter-checks turn red: entries not removed from a group, rows sorted by their first cell, groupByTile ignoring the patch. - e2e 39 + 40 on melektaus-atx: ALL PASSED, 107 checks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
88e1cf1c7d |
Work out only the cells around a painted one on a tilemap (#348)
A painted cell worked out the tiles and blocking cells of the whole map again, so painting got slower the bigger the map. One cell cost 16.8 ms on 60x60 and 54.6 ms on 120x120. Now it costs what is around it: 3.5 ms and 4.0 ms, against 3.4 ms without painting. - The brush, the paint command and its undo name the keys they changed. Each new version of the cells keeps them for the last 16 versions (cellsChangedSince). - The cell index, the tiles of a layer and the blocking cells patch only the changed cells and their eight neighbours. Everything else stays the same object as before. - Both lists are in cell order now, so a patch is one walk that keeps every other entry. - The rectangles are merged row by row. A patch joins again only the rows around it, one row further than the changed cells, and stacks the rest as they were. - A writer that names no keys costs one walk over the cells. A replaced cell object (load, resize, edits at run time) is worked out in full as before. - The tiles a map names come sorted by id. Two trigger tiles on one cell now keep that order, whichever way they were painted. - resolveTilemap walked all tiles every frame, even without painting. It now does so only when the list changed. Found on the way: deleting an outlined autotile can turn the one below into a full box, which cuts a scaled box two rows further down. Merging only the patched rows missed that. It is a test of its own now. A brush stroke on the map doubled to 120x120 runs at 42 fps instead of 28, p95 of a frame 52 ms instead of 92 ms. What still grows with the map is the mesh of a layer, which is built again on every paint. Tests: - tests/core/tilemap-patch.test.ts (new, 7): 400 random steps on two layers with every kind of tile, each compared with a fresh full run - Unit, tilemap and viewport: 1284 green in 57 files - e2e 39 + 40 (atx): ALL PASSED, 107 checks - Gate (atx): 312 files, 4831 tests Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
39c509a8e5 |
Keep a video item in a recording when the tab goes back and forth
A recording made while switching between tabs had stretches without the video item and without its sound, and in between the video stood for several frames. Two things went wrong behind another tab. - A player opened there got no first picture. It waited for requestVideoFrameCallback, which Chrome never calls behind a tab. A recording not in real time then waited 3 s per frame and took it without the video. Now the player takes its first picture as soon as it has data while the tab is hidden. - The span of a picture was learnt from steps that skipped pictures. After a time behind the tab it learnt 0.133 s instead of 1/30 s, left out seeks within that span and never corrected it. Now it learns only from steps where the time sought moved at most half a step, so no picture can lie between them. - Behind the tab a seek forgets which picture shows but keeps the span. A landing without a frame counts as the same picture only once the tab has been in front for 500 ms. Found in the user's recording 7ea9146b: 5363 frames, all 1/60 s apart, with the video missing at 10.63 s (8 frames), 43.17 s (5 frames) and 46.93 s (28 frames), and the sound gone with it. Tests: - Unit tests/viewport: 377 green, 4 new or rebuilt. With the old rule and the first picture only by requestVideoFrameCallback both new tests go red. - e2e 68 on melektaus-atx: ALL PASSED, 21 checks. New 68l: a video item that begins behind another tab has its colours exactly, and a tab switched every 350 ms gives colour changes at 0/30/60/90. - One run in five with switches had a change one frame late, so the check allows one frame either way. - Gate on melektaus-atx: green, 311 files, 4824 tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
abb8fa51d8 |
Keep a recording going behind another browser tab
Chrome stops calling requestAnimationFrame for a hidden tab, and Pixi's ticker hangs on it. The run, the drawing and the recording all stood still as soon as the user switched to another tab. A plain run keeps standing still there, so a forgotten game does not play on unheard. - backgroundBeat.ts ticks the editor from a worker at the recording's fps while a recording goes and the tab is hidden, or the page has not drawn a frame of its own for 250 ms. Once the page draws again, the beat keeps out of the way. - The worker looks every 4 ms whether a tick is due. A 16 ms interval came in bursts behind a busy page, and the recording threw away the second tick of each burst. - backgroundTimers.ts and timerWorker.ts give the short waits of a recording timers in a worker. Chrome slows the page's own timers to once a second in a hidden tab. This covers the waits while saving and the wait for a video's picture after a seek. - Behind a tab Chrome does not call requestVideoFrameCallback, but the picture is there at seeked. The wait there is 10 ms instead of 150 ms while the tab is hidden. - The beat runs for Start recording, the exported game and Start recording... in the main menu. Tests: - tsc, eslint, Prettier clean - tests/viewport: 374 green, 3 new in background-beat.test.ts - e2e 68k (3 checks) on melektaus-atx with the tab really hidden: not in real time all 120 frames, colour changes every 30 frames, the blip 8 ms from green; in real time 97 of 120 frames (73 with the tab in front) - Counter-check without the beat: all three 68k checks red, 3 frames in 60 s not in real time, 7 frames in real time - Gate on melektaus-atx: green, 311 files, 4821 tests Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
87b2ee9402 |
Record every frame with Real time off on Start recording (#423)
At 4K and 60 fps the user's computer reaches about 39 fps in real time.
Reading each frame back from the GPU takes longer than a frame, and
neither reading back later nor the canvas route helped. With Real time
off the run moves on by one frame of the recording at a time, however
long the computer takes, so the file has every frame.
- Start recording has a new check Real time, on by default. Only the
node records so, the editor recording stays in real time.
- SteadyPace in recording.ts is the pace as a pure class. A step is
1/fps once the frame before is taken and the sound up to it is made,
otherwise 0 and the run holds. A frame's time is its number.
- The sound is made apart, as decided in the issue. AudioSwitch routes
what the run plays to the live library, or to the offline library of
a SteadySound while such a recording lasts. That library has the same
mix, buses, FX and limiter and is rendered up to each next frame.
- A driven video holds at every picture asked for, and the frame waits
for it. Its sound is decoded from the MP4 and played in step through
its bus.
- Two Chrome quirks measured and worked around. A held video hands
over the picture of a seek before seeked, so the frame callback is
asked for when the seek begins. A seek right onto where a picture
begins shows that one or the one before, so such a seek lands half a
millisecond further. Together 30 s became 10.7 s for 2 s of video.
- The notice tells how far the recording has got, and the line at the
end how long it took.
Deviations and limits:
- Input is not ignored, it still reaches the game, which runs slower.
- At most ten minutes per recording. Chrome holds the whole buffer of
the OfflineAudioContext while rendering, 230 MB for ten minutes.
- A free video plays on in real time and does not fit. A sound playing
before the start stays at the speakers.
Found along the way:
- A check in e2e 60 was red since
|
||
|
|
77365f17d3 |
Say when a recording starts and when it cannot keep up
A 4K60 game view recording on the user's machine had 517 of 827 frames. The rest were skipped without a word: when the editor's tick came late, FrameGate let the due slots pass, and only frames lost to a busy encoder or read back counted as dropped. The file kept its time but stuttered, and nothing said so. - FrameGate counts the frames it took and the slots the recording's time has room for. The difference is skipped, next to dropped in __gm.recording(). - FramePace judges the missed frames over spans of 2 s. The first time more than 10 % are missed, the log warns once with the frames a second the recording really got. - At the end, when more than 5 % are missed, a warning gives the frames of the whole file and what helps (30 fps or a smaller resolution). It comes after the upload's notice, which would cover it at once in the editor. - The first second counts for neither warning. Without that, a short e2e recording warned about 3 frames lost while starting. - A recording that starts logs an info line with its size, fps, codec and sound. TakeHost.log takes a level for it. The editor recording keeps the line to the console, its notice would be in the video. - consoleLine is the one place with an eslint exception for console.info, so the console panel shows the line plain. Found along the way: headless Chrome in WSL records the editor with real stutter in e2e 67 (1280x800@60 27 %, 2304x1440@30 12-14 %). That is the software encoder there, and only now shown. Tests: - Gate on melektaus-atx: green, 309 files, 4810 tests. - Unit recording.test.ts: 36 green, 6 new for FrameGate.skipped and FramePace. With skipped stuck at 0 the test turns red. - e2e 67 on melektaus-atx: ALL PASSED, 15 checks, run twice. The new 67b-ii slows the editor to about 20 fps: a 640x360@60 recording says "about 19 of 60 fps" and saves with 95 of 192 frames. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
875c76026a |
Silence a free video once its sprite is destroyed (#420)
A sprite whose Video clock is free played its video on after it was destroyed, heard in a game, until the player was let go 2 to 4 s later. A driven one was already held and quiet after 100 ms. - The library holds and quiets a free player nothing asked for in HUSH_MS, as it did a driven one. That covers Destroy, a scene load, the end of a run and a sprite switched off. - Asked for again before it is let go, it plays on from where it was held, heard only in a run. A hidden page leaves it alone, as the sweep does. - A sprite only out of view keeps its free video playing and heard. Whether a video is heard should not hang on the camera. Before, it ran 2 to 4 s and was then let go. Now it plays on, which opens its player even where it was never in view. Tests: - Unit: video-library 1 new. A free player losing its wearer is held and quiet 200 ms later, stays so through a new run, plays on heard when asked again with the same element, and runs on on a hidden page. - e2e 66 on melektaus-atx: ALL PASSED, 36 checks, 66g-ii new. Heard 31 frames before Destroy, held and muted in every frame read after. - Counter-probe without hushing free players: the unit test and only 66g-ii fail, the player plays on heard (paused false, muted false). - Gate on melektaus-atx: 309 files, 4797 tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
acd12326dc |
Fade sounds and videos in and out on the items of a composition
A trailer needs its music and its clips to come in softly and leave
softly. Until now an item of a composition could only play at one
level, and a video item could not even be turned down on its own.
- Every item with sound gets a grip at its upper left and upper right
corner. Dragging one inwards fades the item in or out. Dragging it
back to the edge removes the fade. Lower down the same edge still
trims as before.
- The fade is drawn as its curve, with the part above it darkened.
The inspector shows Fade in and Fade out in seconds, each with the
curves sound clips already know.
- The model keeps fadeIn and fadeOut in frames plus curveIn and
curveOut on the item. Old projects load unchanged. An item shorter
than its fades plays them cut to its length but keeps the setting.
- Sound, pattern and song items ramp the gain of their own bus. The
scheduler plans the ramps on the audio clock next to the notes.
Starting in the middle of a fade picks it up from there.
- Video items set the new sprite property Video volume each frame.
It multiplies with the fade and is only shown for a driven video.
The player glides to each new level over 15 ms so it does not click
between frames.
Not covered: a container item that carries a video further down gets
no grips. Only a video on the item's own sprite counts.
Tests:
- tsc, eslint, Prettier clean.
- Unit tests for composition, music, song and sound (12 files): 269
passed, 7 of them new.
- e2e 69 on melektaus-atx: ALL PASSED, 21 checks. 69p drags a grip to
a fade in of 10 frames and hears the item bus rise from 0.127 over
0.229 and 0.339 to 1 within one second.
- Counter-checks as unit tests: without the audio ramps, and without
the level on the video sprite in the game, both go red.
- Gate on melektaus-atx: 309 files, 4796 tests passed, 8 more than
at
|
||
|
|
77ac9d1aaf |
Play sounds, patterns and songs on the tracks of a composition (#418)
A trailer needs its music and effects on the same timeline as its pictures. Until now a composition could only place objects, so sound had to be started by hand from a behavior and drifted against the picture. - Audio items: a sound, a clip of a sound, a pattern or a song dragged from the asset panel onto a track becomes an item as long as one pass of it. It may share a track with object and video items and overlap them. - Editing works as for other items: move, resize, trim on the left (moves the offset), fit to media, delete, copy and paste, box selection. Sounds draw their waveform, the colour tells the kind. - The inspector shows what it plays, start, length, offset, volume (-60 to +12 dB) and bus. The bus is "From asset", the master or one of the buses, as asked for in the issue. - An item longer than its material plays in rounds. Sounds and patterns start over, a song runs on in its loop. Loop marks on the item follow the same rule, a song's first mark sits at its loop end. - compositionAudio.ts turns the items into sequences, each with its own tempo. CompositionSound plays them, in the tab and in a game. - In the tab the items are heard from the playhead on, straight into the master like every tab. A scrub is silent. Playing a song or pattern in its own tab stops the composition, and the other way round. - In a game they are planned on the audio clock for the moment the composition clock reaches them, with the scheduler's LEAD made up. While the clock stands (waiting for a video, paused, placement off) they are silent and resume at the right spot. Found along the way: - The asset panel never let a song out of its list, so a song could not be dragged anywhere. Song and scene tab reject it as they reject a pattern. - The asset panel scrolled to the selected asset on every rebuild of its list, so folding a group far below the selection jumped back up. Only a new selection scrolls now. - At the end of the list, folding a group pulled every row down by the rows that went away. ScrollContainer has a new opt-in holdOffset, set only in the asset panel, that keeps the offset until the user scrolls up. Limits: a time scale other than 1 or a frame longer than 0.1 s moves the picture and not the sound. A baked song plays its notes here, not the bake. Tests: - tsc, eslint, Prettier clean. Gate on melektaus-atx green, 309 files, 4788 tests (4772 plus 16 new). - Unit: composition-audio.test.ts 13 new, scroll-container 3 new. Counter-checks: without catching up on load time or without the silence on pause the unit tests go red. - e2e 68h: the sound is planned 1.2 ms after the composition reaches 0.5 s. Without the LEAD make-up it is 54 ms late and red. - e2e 04 and 69 ALL PASSED, 86 checks. 69o-ii drags a pattern and a song onto a track, 20 frames each. - e2e 04: folding Sound at the end of the list keeps its header at y 234. With the old panel it jumps to 984 and the check is red. - Not run in a browser: the loop marks of a song item, copy and paste of audio items, the bus choice. - README and wiki count 2072 checks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
30b618d9a9 |
Show game view recordings in their true colours in other players
A game view recording looked darker and its colours off in VLC, and each round of recording a recording made it worse. Chrome's encoder, fed RGB frames, wrote full-range H.264 and left the colour out of the stream. Only our Matroska head said "full range", and VLC reads only the stream. Chrome itself plays the converted MP4, which was right. - The GPU now turns the game view into I420, limited range BT.709, before it is read back (PictureReader). The encoder gets the planes with that colour, as it does for a recording of the whole editor. - Where the planes lie in the read-back picture is a plain function, i420Layout in recording.ts. - Only 1.5 instead of 4 bytes a pixel come back from the GPU. - avcColour.ts writes the colour into the SPS of the encoder's avcC as well, so a player that reads only the stream knows it. An SPS with a VUI only gets its colour block replaced. One without gets a VUI that holds the colour. An SPS it cannot read stays as the encoder made it. - A first try sent the frames through a 2D canvas. That gave limited range too, but only 18 to 20 of 33 frames at 1080p30. Found on the way: - With the colour in the stream alone, VLC 3.0.8 still showed the full-range picture too dark. It ignores the range. - The one old full-range original on the server got the same SPS fix without loss: the clusters are byte-equal, the decoded frames are equal (framemd5), backup in /root/config-backup-20261013/videos/. - Older game view recordings stay full range. Fixing that in VLC would mean encoding them again. Tests: - tsc, eslint, Prettier clean. - Gate on melektaus-atx: CHECK EXIT 0, 308 files, 4772 tests. - avc-colour.test.ts new (9), expectations read back with ffmpeg trace_headers. Counter-check: the old bug in the no-VUI path turns it red. - e2e 67: ALL PASSED, 12 checks. 67d-ii reads tv/bt709 from the file and from the bare H.264 stream, red with the old code. - 1080p30 34 frames, 640x360@60 68 frames, none dropped (was 33/47). - Checked by the user: a new recording looks right in VLC 3.0.8. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
31ce6480b2 |
Give video posters the colours the video shows (#411)
The worker made the poster with a plain scale=320:-2. ffmpeg then read every picture as BT.601 in the limited range, so a red of 255 in a BT.709 video came out as 229 in the JPEG. The picker showed paler posters than the video in the editor. - VideoFormat::colour reads matrix and range of the converted MP4. When the file says nothing, it follows Chrome: BT.709 from 720 lines up, BT.601 below. That guess was measured in Chromium with a red H.264 video without colour tags. - posterArgs turns the picture from that matrix into BT.601 in the full range, the way a JPEG is read. accurate_rnd and full_chroma_int keep dark greys from landing two steps too low. - The search for the first moving video stream is now one helper, used by plan and colour. - Posters made before this change stay as they are. Tests: - videos.test.php in the worker image: ALL PASSED, 90 checks. Three red videos go through the real worker: BT.709 tagged, 720p without tags and 360p in BT.601. Red comes out as 254. - Counter-check with the old filter: 3 checks red, red is 229 again. - php -l clean on all three files. The gate was not run, the change has no TypeScript. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
d4ff652293 |
Keep descenders of a text whole and frame it at its drawn size
A text was drawn into a texture as tall as Pixi measures the string "|ÉqÅM". Whatever reached further, like the "g" of Manrope (0.015 em below the "q"), was cut off at the bottom. The blue frame around a text was only estimated from the font size and the number of letters, so it was far too wide for most texts. - textPicture.ts takes the height of a line from the font itself (fontBoundingBoxAscent and fontBoundingBoxDescent, as a browser does). Pixi draws with a margin, and the picture is cut to that box afterwards. Where Pixi's own box is larger, Pixi's box wins. - Both raster paths (linear and nearest) use the cut. - The height no longer depends on the letters typed, so a text does not jump while it is edited. - bounds.ts asks a measurer that textPicture.ts registers (setTextMeasure). Selection, picking in a running game, framing the camera, the drop preview and culling all get the measured size. The footprint functions take the font file for this. - Unit tests in node have no canvas and keep the estimate (estimatedTextSize). - Texts get taller: Source Sans 3 at 48 px is 68 instead of 55 px, Manrope is 1.366 em instead of 1.25 em. The glyphs stay in place, the centre moves by less than half a pixel. A shader on a text gets a taller UV rectangle. Tests: - Gate on melektaus-atx green: 307 files, 4762 tests. - 5 new unit tests for textBox, textCut and the measurer hook. - e2e 30: 26 checks, 30f-iii new. The texture is 68 high, the font box is 68, the frame equals the drawn picture (237x68). - Counter test: with Pixi's old box the texture is 55 high and 30f-iii is red. - Not checked in the browser: the Manrope caption of the user and texts with several lines. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
9445f39c8d |
Space the letters of a text object closer or wider apart
The Feuerwerk wordmark is set with -0.03em tracking. Text in the engine had no letter spacing, so the wordmark came out about 3 % wider than the logo. - Text has a new property Letter spacing, default 0, in the same unit as the font size. It goes between each two characters, so ten characters get it nine times. Negative values pull them together. - TextPicture hands the value to Pixi's text style. Both rasters, the smooth and the sharp one, use it, and it is part of the raster key. - The estimated text width in bounds.ts adds the spacing too, so selection, picking and "Add collider" fit the drawn text. - Like every number property it can be animated and set from a graph. That is not tried in the browser. Tests: - New unit test for the footprint with spacing. 102 tests in the three text files green. - e2e 30f-ii measures the width of "Handgloves" on screen: 237 without spacing, 282 at +5 (expected +45) and 210 at -3 (expected -27). e2e 30 on melektaus-atx: ALL PASSED, 25 checks. - Gate on melektaus-atx: CHECK EXIT 0, 307 files, 4757 tests. - No counter-check run for 30f-ii. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
6097307b6d |
Move repo, CI and the video worker over to the name Feuerwerk (#422)
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> |
||
|
|
043c224cc3 |
Rename Game Maker to Feuerwerk and serve it at /feuerwerk/ (#422)
The editor gets a name of its own that fits beside Brandmauer, Glutspeicher and Feuerwehr. This is stage 1, 2 and 4 of #422: the name, the URL and the stored state in the browser. Repository, CI runners, containers and the GAME_MAKER_* variables follow in stage 3. - All paths come from SITE_PATH in src/core/site.ts: the API endpoint, the data folders, the play page and the base in vite.config.ts. A later move is one line instead of fifteen. - The window title, the sign-in screen, the playground and the README say Feuerwerk. The package, __feuerwerkPlay, the take worklet and the writing app in Ogg and Matroska follow. - The session cookie is now feuerwerk_session, so everyone signs in once more. - localStorage keys are now feuerwerk.*. moveOldStoredKeys copies the old game-maker.* keys over on the first start, so settings, panel sizes and save games survive. - tools/ci/serve.sh serves the build under /feuerwerk/. The variable GAME_MAKER_BASE_URL keeps its name until stage 3. Outside the repository, nockal.com/feuerwerk/ is a second symlink to the same tree. nginx sends the bare /game-maker/ folder there with a 301. Everything else under /game-maker/ is still served directly, so shared play links and the API keep working. Finding: when nginx redirects a path without a trailing slash or with a query, the Brandmauer answers with its own 307 to the same URL plus a slash, placed after the query. A play link would lose its token that way, which is why only the bare folder is redirected. Tests: - Gate on melektaus-atx: CHECK EXIT 0, 307 files, 4756 tests (4 new in site.test.ts). - Counter-check: when the migration misses the old prefix, 3 of the 4 new tests fail. - e2e 60 under /feuerwerk/: ALL PASSED, 20 checks. - From outside: /feuerwerk/ 200, /game-maker/ 301, an old play link 200. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
f92d69d902 |
Rebuild the Acid node after the acid voice of nodeseq (#421)
The first Acid hardly squelched. Its tanh ate the resonance, and its sweep fell evenly in octaves. The user pointed at the acid tone of nodeseq.com/studio, so the node now follows how that voice is built from Web Audio parts. - The filter is a two-pole 12 dB low-pass with Q in dB up to 17.5, like a BiquadFilterNode. The ladder and its tanh are gone. - One ADSR drives both the volume and the filter. The sweep falls back in Hz, fast from the top and long at the bottom. - Every note glides from the one before. A new note cuts the last one off, and the node plays its release itself. - Knobs: cutoff, reso, env, attack, decay, sustain, release and glide. The defaults are the acid preset of nodeseq. - Accent, slide, drive and tuning are dropped. Old projects were not kept, the user had only placed the node for a try. - A hook __gm.acid.compare renders a line offline through our worklet and through the nodeseq graph, for e2e 34. Found on the way: in Chrome an oscillator that starts inside a render block runs its first samples at 440 Hz. The comparison evens this out. Tests: - tsc, eslint, Prettier clean - Unit: acid.test.ts rewritten with 12 tests. chip-presets and sound-library follow the new node. - e2e 34 on melektaus-atx: ALL PASSED, 148 checks. Against nodeseq the loudness per 10 ms window is at most 0.14 dB apart, and 95 to 97 % of the windows are equally bright. - With a linear Q instead of Q in dB the comparison is red. - Gate on melektaus-atx: green, 306 files, 4752 tests. That is 4754 less the 14 old and plus the 12 new Acid tests. - Not checked: the sound by ear, the master chain of nodeseq, the keyboard and a bake with the new node. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
d844f2734a |
Add an Acid instrument node for TB-303 style bass lines (#421)
The instruments could only come near a 303 with a Chip, a Filter and two envelopes. The biquad filter does not saturate, every note strikes anew, and there was no slide and no accent. - Acid is a new instrument node that plays a saw or a square through a four-pole ladder low-pass with tanh saturation, oversampled twice. Knobs: cutoff, resonance, env mod, decay, accent, tuning and slide time. - An accent comes from the velocity, from 0.9 on. It is louder, opens the filter further with a shorter decay, and accents close behind each other build up. - Two notes that touch or overlap slide. Each note stays a voice of its own, so the scheduler is unchanged. The notes of a line share one synth in the worklet, and the new voice takes it over from its first sample on. - The node sets its own volume, so it needs no envelope behind it. - The graph shows it as a device with the filter curve at rest and at the top of the sweep. - A preset Acid Bass puts an Acid in front of a short echo. Tests: - tsc, eslint, Prettier clean - Unit: 14 new tests in acid.test.ts for pitch, low-pass, resonance, sweep, accent, slide and the handover between two voices. - e2e 34 on melektaus-atx: ALL PASSED, 148 checks (3 new). The handover sits on sample 4800 and jumps by 0.0012, of three notes only the second slides. - Without the handover, or with a slide that strikes anew, the unit tests are red. - Gate on melektaus-atx: tsc, eslint, Prettier clean, 4753 of 4754 tests green. The red one was sound-library.test.ts, which did not know the new slid count in the stats. Fixed, and the file alone is green again with 128 tests. No second full gate. - Not checked: the sound by ear, slide at the keyboard, a bake with Acid, the CPU load of many notes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
2ba41c1bd0 |
Record 1440p and 4K, and choose sound and conversion in the node
The Start recording node only offered the game view, 720p and 1080p, and always recorded with sound, converted and imported. The recording from the main menu could already leave those out. - Resolution has 1440p and 4K. As with 720p and 1080p the height is fixed and the width follows the camera, so 16:9 gives 2560x1440 and 3840x2160. - Every resolution is capped at 3840x2160, as the game view already was. A 21:9 camera in 4K would be 5040 px wide, which the encoder levels are not made for. It is scaled down to 3840x1646. - The node has Sound, Convert and Import after conversion, all on by default as in "Start recording...". Import only counts with Convert. An old graph without the inputs records as before. - A released game ignores Convert and Import and downloads the MKV. - Both recordings now hand their upload options the same way, from GameRecorder over PlaySession to RecordingUploads. RecordingUploadOptions moved to core/recording/recording.ts. Tests: - tsc, eslint, Prettier clean - Unit recording and node-catalog: 451 green, recording 29 with 3 new tests and 1 extended - e2e 67 on melektaus-atx: ALL PASSED, 10 checks - Not checked in the browser: a real 4K recording, and the node with its switches off. - The gate ran once on this commit and the next one together. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
65d7bf6a1f |
Silence a composition video as soon as its item ends
A video in a composition plays on a driven player that the item tells where to stand every frame. When the item ended, its object was gone and stopped asking, but the player ran on with its sound until it was let go for being unused. That took 2 to 4 seconds, and in a pause it never happened at all. The same held for the composition preview. - A driven player nobody asked for in 100 ms is now paused and muted. When its wearer comes back it carries on where it should be, as before. It is still let go after 2 to 4 seconds. - A player still without its first picture keeps playing, without sound, because it only gets that picture by playing. - Leaving the animation or composition tab now silences their video preview at once instead of after 2 to 4 seconds. - Free videos (Video clock free) keep the old behaviour. A sprite destroyed by a behavior is still heard for 2 to 4 seconds. Tests: - Unit video-library: 10 green (1 new). Without the new timer the new test is red. - e2e 68: ALL PASSED, 11 checks (68g new). An item of 0.5 s with a 2 s video plays audibly while it is there, and the player is paused and muted in every frame from about 200 ms after it ends. - Gate on melektaus-atx: green, 305 files, 4737 tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
126a5a3a62 |
Let a composition item's clips move along with it
A clip only hung at its track and frame and worked on whatever item it
overlapped. Moving an item left its clips behind, so they started to
animate the next item that came along.
- A clip belongs to the item that lies on its track at its start frame.
With two there, it belongs to the one in front. A clip that only
reaches into an item stays with the item before. The model does not
change, the rule is worked out each time.
- Dragging an item, also onto another track, and typing its Start in
the inspector take its clips along in the same undo step.
- Where a carried clip would land on a foreign one, the whole move
holds at the last place where everything fits, as dragging clips
already does.
- Pressing Alt during the drag moves the item alone. Alt on the press
already starts the clip band since
|
||
|
|
480db9c4e4 |
Scroll the rows of the animation timeline up and down (#413)
An animation with more rows than the panel is tall cut the bottom rows off. There was no way to reach them, their fields or their keys. - The view has a new `scrollY`. Rows scroll up under the ruler, which stays where it is together with the tab head. - The wheel over the header column scrolls the rows. Over the cells it still zooms, and `Shift`+wheel still scrolls across. - A scrollbar at the right edge shows up only when the rows do not fit. Its thumb can be dragged, a click beside it jumps there. - The middle button pans both ways. `Shift`+drag stays across only. - A row picked or added out of view is scrolled just far enough to be seen whole. Value fields out of view are not drawn. - The tab keeps its scroll like its zoom when switching. Found on the way: `zoomCells` built a new view from `panX` and `cellWidth` only, so every zoom would have dropped the scroll. The type check caught it, and a unit test now holds it. Tests: - tsc, eslint, Prettier clean - Unit timeline-view: 20 green, 6 new. Bending `rowAt` to ignore the scroll or `revealRow` to do nothing turns exactly those tests red. - e2e 25 on melektaus-atx: ALL PASSED, 37 checks (25j, 25j-ii new). 32 rows, the wheel brings the last field into view and typing keys 77 there. Dragging the thumb up goes back to 0. - Gate on melektaus-atx: green, 305 files, 4730 tests. - Not checked in the browser: the middle button, revealing a picked row, the wheel right over a value field. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
0648853356 |
Select composition items with a band and copy clips too (#417)
The composition tab only took single clicks and Ctrl-clicks, and animation clips could not be copied at all. A band selection and Copy/Paste for clips make longer compositions quicker to arrange. - Dragging from an empty spot draws a band. It selects every item it touches, across tracks. Ctrl adds to the selection, because Shift plus drag already pans the view. - With Alt held the band selects clips instead and may start on an item, since items fill most of a track. - Ctrl+C copies the selected clips as their own clipboard kind. Ctrl+V places them like items: under the pointer over the tracks, otherwise at the playhead on the selected track. - Clips of a track never overlap. A paste onto taken frames moves the whole copy on frame by frame until every clip fits (`pastedClips`). - The paste is one undo step and selects the new clips. Tests: - Gate on melektaus-atx: tsc, eslint, Prettier clean, 305 files, 4724 tests (2 new). - e2e 69: ALL PASSED, 17 checks (69m new). Band over three items on two tracks, Alt band from an item picks both clips, first paste at frames 50 and 60, second paste moves on to 56 and 66. - Counter-check: without moving on, the paste unit test is red. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
7a952cb20d |
Start paused, step one frame and break from a behavior
Finding out what a game does in its first frames, or right at one spot of a flow, needed a quick hand on the pause button. Now the editor can start a play paused, step it frame by frame and let a behavior pause it. - The pause button can be pressed in edit mode. Then Play starts paused, and every further Play does too until it is released. A reload of the page clears it. - A third button, Next frame, right of Pause, or Ctrl+Shift+Enter. It works only while paused and runs exactly one frame of 1/60 s, whatever the frame took on screen. Hierarchy and inspector follow. - The Break node pauses the play like the button, at the end of the frame it ran in. The status line names the behavior's object. It is the editor's pause, not Pause game. - Break reaches the editor through a service only the editor hands to a play. In play.html and Run in editor the node does nothing. - Restart game keeps the running play's state. Start paused only counts when Play is pressed. - Wiki Concepts, Behaviors, Keyboard: the buttons, the node and the shortcut. Not tried: sound, free-running videos and a running recording while stepping. Sound and videos stay held during the pause. Tests: - tsc, eslint and Prettier are clean. - Unit pause, node-catalog, tooltip: 465 green. svg-icons now expects the step icon too. - e2e 18 and 46 on stein2 (melektaus-atx was off): ALL PASSED, 63 checks, 7 new in 18j. A step moves the box by 1.667 px at 100 px/s, Break stops the play with "Break in Breaker". - Counter-checks: a session that ignores the step turns 18j red. A Break that does not report its object turns the unit test red. - Gate on stein2: tsc, eslint and Prettier clean, 305 files, 4722 tests, 6 more than before. One was red at first, the icon list of the top bar did not know step yet. Fixed in svg-icons.test.ts and run again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
64d0d472e5 |
Show a spawned object's first animation frame right away
An object that appears during a frame and plays an animation in its On start was drawn once in its original state. A composition item showed frame 0 one frame late. An object from the Spawn node even ran its On start only on the next frame, after it had been drawn. - BehaviorSystem: at the end of the behavior pass, objects that came to be in this frame hear On start and On enable. This covers spawned objects and behaviors switched on for the first time. Their On update starts with the next frame. - AnimationSystem.showStarted: right after that, every animation started in this frame shows its frame 0. The clock runs from the next frame, and an end is told no earlier than then. A paused frame skips this, its animations stand still anyway. - What such a late On start spawns in turn starts in the next frame. Otherwise a spawn chain could stretch one frame without end. - Scene start is unchanged. There On start already ran before the first frame. - Wiki Behaviors, Lifecycle: the new timing. Tests: gate green on stein2 (melektaus-atx went offline mid-run), 305 files, 4716 tests (2 new). e2e 68 on melektaus-atx green, 10 checks. Without the late pass the Spawn test reads 7 instead of 0, without showStarted both new tests are red. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
b16fe8c718 |
Drop a picture or video onto a composition track (#419)
A track item has always carried an object, and an object needs an asset. Instead of objects without one, a dropped picture or video gets a sprite the composition owns, the way a song owns its sounds (#355). To the rest of the code it is a sprite like any other. - The asset list starts a drag for textures, vector art, pixel art and videos. Dropped on a track, they make the sprite and the item in one undo step. A video item is as long as the video and follows it. - One sprite per picture and composition. A second drop of the same video shares it. Once no item wears it, it goes in the same step. - Deleting a composition takes its sprites along. A duplicate gets sprites of its own. - Pasting into a scene or container turns the sprite into a project asset. Pasting into another composition gives that one a copy. The clipboard carries the sprite, so cut and paste loses nothing. - Owned sprites stay out of the asset list and the pickers. The inspector shows them with a dot in front. - "Make project asset" in the item's menu, on the timeline and in the hierarchy, turns the sprite into an ordinary one. - The first track of the Composition tab gets its top line, as the Song tab's has. Found on the way: loading kept an owner only for the asset types a song can hold. A composition's sprite would have lost its owner and turned up in the list after a reload. Tests: - Unit composition-sprites: 11 new. Without the rule 5 of them are red, without the loading fix the loading test is. - e2e 69 on melektaus-atx: ALL PASSED, 15 checks, 69k new - Gate on melektaus-atx: 305 files, 4714 tests Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
c425eea004 |
Key what an empty timeline row shows on a double-click (#412)
A double-click into a timeline row without keys set the property's
default, while the field in the row header showed the value the target
actually holds. A sprite standing on x 40 got a key with 0, so the
first key moved it. The double-click now keys the value the row shows.
- trackHeld in src/core/model/animation.ts says what a row without keys
holds. The field in the row header and SetAnimationKeyCommand both
ask it, so they cannot drift apart again.
- A relative row still gets 0, a row with keys still takes the value of
its curve at that frame, and a row whose target is gone still only
gives a notice and no key.
- README and the wiki page Development name 2047 checks.
Tests:
- Unit animation: 87 green, 1 new. A sprite on x 40 with the override
scale 2 gets keys with 40 and 2. Red with the old def.default.
- e2e 25 on melektaus-atx: ALL PASSED, 35 checks, 25i-ii new. The arm
stands on x 40 and a double-click on frame 3 keys { frame: 3,
value: 40 }.
- Gate on melektaus-atx: green, 304 files, 4703 tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
d4fc425233 |
Draw the editor's mouse pointer from SVG files, on screen too
The pointer drawn into an editor recording lived as paths in code, so it could not be looked at or edited by hand. The shapes are now SVG files, and the editor can show the same pointer on screen, so what is recorded is what was seen. - src/core/recording/cursors/ holds one file per shape: arrow, hand, open-hand, fist, move, crosshair, text and resize. Resize is turned for the vertical and diagonal directions. - data-hot="x y" on the <svg> is the hotspot in viewBox units. Width and height in the file are ignored, and anything before <svg>, such as <?xml ...> from a drawing program, is cut off. - The open hand had six fingers. It has a thumb and four now, the middle one longest. - CURSOR_SIZE = 0.8 scales every pointer the same on screen and in a recording, so the files keep their units. - General settings has "Custom mouse pointer", on by default. It hangs the files into Pixi's cursorStyles as CSS cursors with the keyword as fallback, so the system still draws the pointer and the canvas does not. Off gives Pixi's own values back at once. - A recording draws the pointer from the same files at its own size, so it stays sharp in 4K. It still reads the shape from the canvas when that holds a url(...) cursor. Tests: - Unit recording and view-tween: 3 new, green. They read viewBox and data-hot in an edited form, the boxes of turned shapes, the default size and old settings without the switch. Red without data-hot and without cutting before <svg>. - e2e 46i-ii: the canvas cursor is a url(...) ending in default and the image loads. Off gives inherit and stores false, on brings the file back. Red without useCursorFiles. - e2e 67e: the recording is still black at the mouse position. - The README names 2046 e2e checks. - Gate on melektaus-atx: 304 files, 4702 tests green. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
ba01d146e9 |
Choose size, rate and what follows when recording the editor
The editor recording had fixed settings: the canvas size at 60 fps,
always converted and imported. Recording in 4K needs a choice, and the
server shrank every video to 1080p anyway. Start recording... now opens
a dialog, and a recording always goes to the server as it is.
- The dialog holds name, resolution (Screen, 1080p, 1440p, 4K), fps
(60 or 30), mouse pointer, sound, Convert and Import after
conversion. Import is greyed out while Convert is off. The browser
keeps the last choice, not the name.
- A larger resolution makes the editor draw finer while it records,
so 4K is sharp, not stretched. It is never below the screen's own
resolution and goes back after Stop.
- Stop always uploads. Without Convert the file only stays on the
server. Without Import it is converted but no asset is added. Both
are found in the video picker.
- The server keeps the original next to each MP4 for every upload, so
Download in the video tab and the picker gives the original. Videos
from before have none, they still download their MP4.
- A file left unconverted shows in the picker as not converted, with
a Convert button. POST videos/{file}/convert queues it, and upload
takes convert=0.
- The worker now caps a video at 3840x2160 instead of 1920x1080. Videos
converted before stay as they are.
Tests:
- PHP videos.test.php: 81 checks green, with the original, convert=0,
a later Convert and the 4K cap.
- Unit recording: 5 new, green. Red without the rule that a recording
is never below the screen's resolution.
- e2e 67e-ii: the video tab downloads the .source.mkv, a Matroska
file. 67f: 1440p from a 1280x800 window gives 2304x1440 at 30 fps
without sound, kept and not imported, then converted in the picker.
Both red with the MP4 download and without the finer drawing.
- The README names 2045 e2e checks.
- Gate on melektaus-atx: 304 files, 4699 tests green.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
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> |
||
|
|
846a8b70b3 |
Let ?w=&h= fix the editor canvas to a set size
A recording of the editor is as large as its canvas, and so far the canvas always followed the window. With ?w=1920&h=1080 the editor is laid out at exactly that size in CSS pixels, whatever the window. - src/ui/pageSize.ts reads w and h (both needed, 200 to 8192 each) and works out how the canvas fits the window. - A smaller window shows the canvas shrunk to fit, centred, with the same aspect. A larger one shows it 1:1, centred on a dark page. - Clicks still land right, since Pixi maps them over the canvas's on-screen box. Switching projects keeps w and h in the URL. - The pointer drawn into an editor recording now scales against the layout size, so a shrunk canvas still records a full-size pointer. Tests: - Unit page-size: 5 new, green. - e2e 62d: ?w=1600&h=900 in a 960x720 window gives a 1600x900 canvas shown at 960x540, y 90, and a click on the menu button opens it. Red without the shrinking. - Gate on melektaus-atx: 304 files, 4691 tests green. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
f177ee90a4 |
Record the whole editor from the main menu
The game view could already record itself through nodes. Making a video of the editor itself, to show how something is done, needed an outside screen recorder. The main menu now starts and stops one, with no options to set. - "Start recording" sits in its own group before General settings, and reads "Stop recording" while one runs. The name is "Recording <time>", the size is the whole canvas in device pixels (an odd row or column is cut), 60 fps always. Sound is the master, saving goes the same way as for the game view. - The encoder, the sound and the Matroska writing of a take moved from GameRecorder.ts to RecordingTake.ts. A take now reads its pictures from a FrameSource, so the game view and the editor share it. - The editor's picture is copied from the canvas after each render, in the same frame, so nothing is drawn twice. A resting editor does not render, and the recording repeats its last picture. - The mouse pointer is drawn into the recording in the shape the editor shows (arrow, hand, resize arrows, I-beam, crosshair, move). - A red dot with the running time stands beside the menu button on screen only. It is drawn after the copy, and the toolbar makes room for it. - Drawing that dot made Pixi take it as the root for every hit test, and the menu would not open any more. The stage is set back as the last rendered object after it. Tests: - Gate on melektaus-atx green: 303 files, 4686 tests. - Unit recording: 20 green, 5 new. - e2e 67: 8 checks, 67e new. It records through the menu and decodes the MP4: 1280x800 at 60 fps, the pointer black at the mouse, the dot missing, 238 plain spots alike with the canvas. Without the pointer and with the dot drawn before the copy, 67e is red. - e2e 46: the main menu lists "Start recording". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
c0ba2673ba |
Keep the video tab's picture following while scrubbing (#417)
Scrubbing in the video tab set currentTime on every pointer move. Each new seek aborts the running one. With a large video a seek takes longer than the gap between two moves, so none finished and the picture froze. The composition tab never had this, because its players run through the VideoLibrary, which only keeps the newest wish while a seek runs. - The pending-seek logic moves into a shared helper, src/viewport/videoSeek.ts. The VideoLibrary uses it unchanged. - The video tab seeks through the same helper and uploads the picture again after each finished seek. - Play drops a pending scrub wish. Otherwise the video would jump back right after it starts. - Scrubbing before the first picture no longer jumps back to the time the tab opened with. The video stays at the last chosen time. Tests: gate on melektaus-atx green, 303 files, 4681 tests (2 new in video-seek). e2e 66 ALL PASSED, 30 checks. Without the helper the new unit test is red ([0.1, 0.2, 0.3, 0.4, 0.5] instead of [0.1]). There is no browser check for the freeze itself, the test video seeks too fast to show it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
b2b4cbad28 |
Keep a video's loading out of recordings and first frames (#417)
A Start recording right after Play composition still began with empty frames. The composition's clock waited for its video, but the recording went on and took every frame while the video loaded. A clip that starts later in the video also slipped through, because its player already had one picture while it still sought to the clip's start. - The view now counts every video sprite it leaves out as missing, not only one without any first picture. - GameRecorder takes no frame while the game view misses a video, for at most VIDEO_WAIT_MS (3 s) at a time. The recorded time stands and the sound of that time is dropped, so it stays in sync. - RecordingClock.start forgets a hold from before the first frame. Without that, the first real frames all got the time 0. - A followed player starts at 1x until the first speed reading. Before, it paused and sought every frame for about 100 ms, so the first pictures after a wait stuck and then jumped. A step faster than 4x and a time running backwards do not count as a start. - Test hooks: __gm.gameVideo(id) reads the game view's own player, TakeInfo.lastFrameAt shows when the last frame was taken. Tests: - Unit recording (clock after an early hold), video-follow (2 new, 1 adapted): 449 green in tests/core/recording, tests/viewport and tests/core/composition* - e2e 68e (a clip from 0.8 s shows that picture first and then plays), 68f (a recording that already runs takes no frame while the composition's video loads) - 68e is red without the start at 1x, 68f without the hold - README: 2038 editor checks Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |