Commit Graph
39 Commits
Author SHA1 Message Date
bflatasticandClaude Opus 5.5 cb892b5239 M4.12: docs: ROADMAP, parity, web, preferences and agents brought up to date after M3 and M4
- parity: the status note covers the M3.9–M4.12 passes (feature counts unchanged: they fixed
  behaviour inside features already counted done) and the GPU effect count matches EFF-5 (280);
  the Layers, Compositions, Interface, Project and Preview rows name what is left and what
  landed; landed items in "Highest-value gaps" are struck through with where they landed, the
  gaps the passes left are items 16–18, and the closing paragraph no longer says 69 menu entries
  are disabled.
- ROADMAP: what is left includes the preview gaps.
- web: a restored session with unsaved changes stays modified and asks before closing.
- preferences: Adaptive Resolution Limit applies while dragging, not to playing previews.
- agents: nested comp settings and New Comp from Selection for several items.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 06:35:00 +07:00
bflatasticandClaude Opus 5.5 a12d4ae1fa M3.9: a browser session restored with unsaved changes stays modified
The web app reopens the last visit's project and, when it had unsaved changes, marked it
modified with `bump()`. Since M3.9 the modified mark follows the saved snapshot, and a bump on
the very project just opened counts as "back at the saved state", so restored changes looked
saved (no `*`, no unsaved-changes prompt). `Session::mark_unsaved` forgets the saved snapshot
instead: the project stays modified until saved, whatever is undone. Regression test included.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 06:33:40 +07:00
bflatasticandClaude Opus 5.5 b21e04b301 M4.12: never crash: hostile input in New Comp from Selection, an empty work area in Cache Frames When Idle
Following AGENTS.md's Never crash rules for the M3 / M4 additions:
- Cache Frames When Idle panicked (`clamp` with min > max) when the work area was inverted
  (start set past the end); an empty work area now has nothing to cache.
- `file.newCompFromSelection`: the sequence overlap times the layer count overflowed for a huge
  `overlapDuration` (wrapping to a wrong comp length without overflow checks); negative or
  non-finite values fall back to 1 s and the product saturates. The lead item is read with
  `get()` instead of indexing.
- Layer Settings' enablement reads the first selected layer with `first()`.
- Regression tests for both (each failed before the fix).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 06:30:46 +07:00
BFlat 7ee6d6a301 Merge pull request #38 from storytold/m4-nested-comps
Previews (M4): Preserve frame rate / resolution when nested, nested comps past their end, one motion blur gate
2026-10-05 02:08:13 +07:00
bflatasticandClaude Opus 5.5 e295679b7f M4.11: docs: architecture, parity and ROADMAP cover nested comps and motion blur
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 01:56:17 +07:00
bflatasticandClaude Opus 5.5 995d99f938 M4.11: one motion blur gate for layers, masks, effects and Advanced 3D
`Renderer::mb_on` (render options × the comp's switch × the layer's switch as the precomp
layers above limit it) now decides everywhere:
- Mask motion blur checked only the comp's switch and the layer's own switch, so Render
  Settings ▸ Motion Blur ▸ Off for All Layers and a precomp layer with motion blur off still
  blurred masks; it now takes the gate (`masks::MaskBlur`) and the comp's Samples Per Frame
  (four in Draft) instead of a fixed eight.
- Effects that read the shutter (`EffectEnv::shutter`) get it only when the layer is motion
  blurred in this render.
- Advanced 3D layers follow the precomp layers' Motion Blur switches too.
- The layer cache key of a layer with masks or effects includes whether it is blurred and the
  comp's shutter and samples, so a buffer blurred for the viewer is never reused by a render
  with motion blur off.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 01:55:34 +07:00
bflatasticandClaude Opus 5.5 0b5cff1743 M4.10: a nested comp shows nothing before it starts or after it ends
A precomp layer trimmed out (or time-remapped) past its nested comp's end kept drawing the
nested layers whose bars ran on. Like After Effects, the nested comp now shows nothing outside
its own span (`Comp::covers`), flattened, through a proxy or with Collapse Transformations, on
the CPU and the GPU (both go through `Renderer::source` / `collapse_into`).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 01:47:54 +07:00
bflatasticandClaude Opus 5.5 0fe6db00e8 M4.9: Preserve frame rate when nested or in render queue, Preserve resolution when nested
Composition Settings ▸ Advanced has both options (they were missing from the model, the
renderer and the dialog):
- Preserve frame rate: nested in another comp, the comp shows only its own frames
  (`EvalCtx::nested_time` floors the precomp layer's source time to the nested comp's frame
  rate; used for the nested frame, its proxy, aux channels, collapsed transformations and the
  viewer's internal wireframes), and the Render Queue renders it at its own rate whatever the
  Frame Rate override (`RenderSettings::rate`).
- Preserve resolution: nested in a comp rendered at Half / Third / Quarter, it renders at full
  size and the buffer's scale places it.
- `comp.settings {preserveFrameRate, preserveResolution}`, `comp.info` reports them, scripts
  read and set `CompItem.preserveNestedFrameRate` / `preserveNestedResolution`, Pre-compose
  copies them, and the dialog registers `dialog.comp.preserveFrameRate` /
  `dialog.comp.preserveResolution`.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 01:33:14 +07:00
BFlat 4e98b8c4bd Merge pull request #32 from storytold/m4-preview
Previews (M4): RAM preview keys and eviction, Auto resolution while playing, Cache Before Playback that fits, Cache Frames When Idle, purges
2026-10-05 01:13:37 +07:00
bflatasticandClaude Opus 5.5 da3177954f M4.8: docs: parity covers the cached playback fixes
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 01:04:21 +07:00
bflatasticandClaude Opus 5.5 fa27d621e0 M4.8: Edit ▸ Purge clears what it names: disk and snapshot purges keep the RAM preview, memory purges drop decoded footage
- Every purge emptied the viewers' RAM preview; Purge ▸ All Disk Cache and Snapshot now leave
  it (Image Cache Memory, All Memory, All Memory & Disk Cache, All and All 3D Cache still
  clear it).
- The memory purges also drop the decoded footage frames (`FootageSource::purge`, the media
  pool's `clear_frames`; decoders stay open), so footage reads decode again.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 01:03:47 +07:00
bflatasticandClaude Opus 5.5 5ce641c6b3 M4.7: Cache Frames When Idle renders the work area in the background
Composition ▸ Preview ▸ Cache Frames When Idle was only a saved checkbox. After a second
without input or edits it now renders the work area's frames (from the current time on, then
from the start) into the RAM preview, a few at a time behind the viewer's own frame, until the
work area is cached or the RAM preview budget is full.

Docs: preferences.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 00:56:49 +07:00
bflatasticandClaude Opus 5.5 6214aecdb9 M4.6: previews: Auto resolution keeps its scale while playing, Cache Before Playback plays what fits, achieved frame rate in the Info panel
- Viewer Resolution Auto rendered at half its scale while a preview played, so frames cached
  while scrubbing were not played and frames cached by the preview were re-rendered once it
  stopped. Auto now follows the zoom alike playing and paused (Fast Previews still lowers it
  while dragging).
- Cache Before Playback waited for every frame of the range: a range larger than the RAM
  preview budget evicted its first frames and never started. The range is now cut to the
  frames that fit (90 % of Settings ▸ Memory & CPU's budget) with a message, then cached and
  played.
- The Info panel shows the frame rate a playing preview achieves and whether it is real time
  (`Playback::achieved_fps` / `real_time`, automation id `info.previewFps`).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 00:52:50 +07:00
bflatasticandClaude Opus 5.5 5648fdb1c2 M4.5: RAM preview: render options in the frame key, least-recently-shown eviction, stale frames freed at once
- `FrameKey` carries a hash of the render options (`frames::opts_hash`): switching Fast
  Previews (Draft / Fast Draft) or Realtime Shadows no longer shows frames rendered the other
  way.
- Eviction drops the least recently shown frame (`Frames::get` touches it), not the oldest
  inserted: a looping preview a little over budget no longer evicts the frame it shows next.
- Frames of earlier project revisions are dropped on every change (`Frames::drop_stale`); they
  could never be shown again but held memory (and GPU textures) until evicted.
- The green cache bars (Timeline, Preview panel) count the frames of exactly what the viewer
  shows — scale, 3D view / region of interest and options (`EffectcraftApp::shown_series`) —
  instead of any frame at that scale.
- Playback loops compute the key's fixed part once (`EffectcraftApp::frame_series`).
- Docs: architecture.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 00:43:56 +07:00
BFlat 249cc145e3 Merge pull request #8 from storybored379/pr/fullscreen-shortcut-off-mac
Shortcuts: don't bind macOS-only Ctrl+Cmd shortcuts off macOS
2026-10-05 00:16:09 +07:00
BFlat 9ec7680291 Merge pull request #20 from storytold/m3-layer-settings
Project operations (M3): Layer Settings, lock-aware layer commands, New Comp from Selection, menu fixes, safe project files
2026-10-05 00:15:20 +07:00
bflatasticandClaude Opus 5.5 cb6e1beebc M3.14: docs: ROADMAP marks project operations done
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 00:08:29 +07:00
bflatasticandClaude Opus 5.5 948f06fbf2 M3.14: project files name the version that wrote them, never save unreadable, keep their format
- `Project::to_file_json` writes `savedBy` (the EffectCraft version) ahead of the project and
  checks that the text reads back before anything is written: JSON has no NaN or infinity, so
  such a value made a file that saved but could never be opened. Save now fails with a message
  and the file on disk is left as it was.
- Opening a project saved by a newer EffectCraft warns that settings this version doesn't know
  are lost on saving; `file.open` reports `savedBy`. A file that isn't a project names itself in
  the error.
- Increment and Save keeps the extension (`.ecprojx` stays XML); Save a Copy writes XML for
  `.ecprojx` / `.xml` paths, like Save As.
- Docs: architecture (document model), parity.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 00:08:07 +07:00
bflatasticandClaude Opus 5.5 6fc39eb133 M3.13: menu bar: Group/Ungroup Shapes in the Layer menu, Window ▸ Learn, flowchart shortcuts, Center In View shortcut, Reveal in Explorer on Windows
- Group Shapes (Ctrl+G) / Ungroup Shapes (Ctrl+Shift+G) sit in the Layer menu itself, as in
  After Effects (they were under Mask and Shape Path).
- Window ▸ Learn opens the Home screen's Learn tab.
- Composition ▸ Composition Flowchart is Ctrl+Shift+F11 and Window ▸ Flowchart Ctrl+F11 (the
  flowchart had the panel's shortcut and the panel none).
- Layer ▸ Transform ▸ Center In View shows Ctrl+Home.
- "Reveal in Finder" reads "Reveal in Explorer" on Windows and "Reveal in File Manager" on
  Linux (`menus::platform_label`).
- Docs: parity (View ▸ New Viewer noted as not yet).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-04 23:58:00 +07:00
bflatasticandClaude Opus 5.5 a34d0c421b M3.12: Pre-compose keeps the composition's settings; New Comp from Selection is one undo step with After Effects' options
- Pre-compose makes the new composition with the original's pixel aspect, background, motion
  blur and frame blending settings and 3D renderer (`Comp::nested_like`); it kept only size,
  rate and background.
- File ▸ New Comp from Selection records one undo step (two per item before) and takes each
  item's pixel aspect. Several items selected open the New Composition from Selection dialog:
  Single / Multiple Compositions, Use Dimensions From, Still Duration (default from Settings ▸
  Import ▸ Still Footage), Add to Render Queue, Sequence Layers with Overlap, Duration and
  Transition. `file.newCompFromSelection {single, dimensionsFrom, duration, addToRenderQueue,
  sequence, overlap, overlapDuration, transition}`; `layer.addItem {duration}` for stills.
- Docs: parity.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-04 23:48:54 +07:00
bflatasticandClaude Opus 5.5 89653f37de M3.11: layer commands respect locks, keep children in place on delete, arrange step by step, keep depth and separated dimensions, keep links on duplicate/paste
- Locked layers can't be selected (Timeline click, Select All, Ctrl+Up/Down; shy layers while
  hidden too) and are left alone by Clear/Delete, Arrange, Layer ▸ Transform and Center Anchor
  Point, which fail when every target is locked (`commands::unlocked`, `commands::selectable`).
- Deleting a parent unparents its children where they are, in the same undo step: the
  pick-whip compensation moved out of `layer.setParent` into `layer::parent_fixes` /
  `ParentFix::apply`, which now also handles separated X/Y Position.
- Bring Forward / Send Backward move each selected layer one step (a non-contiguous selection
  was squeezed into one block).
- Center In View / Fit to Comp keep a 3D layer's Z (and Fit keeps Scale/Anchor Z); Reset /
  Center / Fit / Center Anchor Point work with separated dimensions (`layer::update_position`).
- Duplicate and paste keep parents and track mattes, following the copies when both were
  copied; paste dropped them always.
- Docs: agents, parity.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-04 23:33:54 +07:00
bflatasticandClaude Opus 5.5 753de070a0 M3.10: Layer Settings opens Solid Settings with "Affect all layers that use this solid"; settings dialogs register every control
- Layer ▸ Layer Settings on a solid or adjustment layer opens Solid Settings on the layer's
  solid (it ran an empty command before): name (renames the layer and the solid), size, Pixel
  Aspect Ratio, colour, and "Affect all layers that use this solid". It starts off, as in After
  Effects, so changing a solid other layers share gives this layer its own copy.
  `layer.settings {pixelAspect?, affectAll?}` → `{layer, solid?}`; `layer.newSolid {pixelAspect?}`.
- Nulls ask for their name; text and shape layers have no settings (the entry is disabled).
- Composition Settings / New Composition and Solid Settings register an automation id for
  every control (`dialog.comp.*`, `dialog.solid.*`). Composition Settings takes any typed frame
  rate besides the list, and New Composition starts from the last settings used.
- Colour buttons in both dialogs show the stored sRGB colour (`widgets::srgb_color_button`);
  egui's linear picker showed it lighter.
- Docs: agents, parity.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-04 23:22:09 +07:00
BFlat 3861b6d043 Merge pull request #13 from storytold/m3-project-operations
Unsaved changes: ask before closing a modified project; the modified mark follows undo
2026-10-04 23:05:45 +07:00
bflatasticandClaude Opus 5.5 e0ed28f55d M3.9: closing a modified project asks to save it first; the modified mark follows undo
- Quit and the window's close button, File ▸ New Project, Open Project, Open Recent, Close
  Project, the demo project and a Home template show Save / Don't Save / Cancel when the
  project has unsaved changes (Save asks for a path when untitled; cancelling that keeps the
  project open). File ▸ Revert asks to discard. Before, all of them dropped unsaved work
  without asking.
- The window title shows the project's path and `*` while it is modified.
- The modified mark follows undo: the session keeps the snapshot it last opened or saved
  (`Session::saved_project`), so undoing, jumping in the History panel or rolling back a batch
  to the saved state clears it.
- An edit that changes nothing (a value set to what it already is) records no undo step and
  doesn't mark the project modified (`Project::same_content`, comparing untouched comps by
  pointer).
- Agents' `engine.execute` / MCP calls are never asked; `ui.menu.invoke` shows the prompt like a
  click (automation ids `dialog.unsaved.save` / `dontSave` / `revert` / `cancel`), and the
  control channel's `app.quit {force: true}` skips it.
- Docs: architecture, agents, control protocol, parity.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-04 23:02:14 +07:00
BFlat 58163a28fb Merge pull request #5 from storytold/docs-accuracy
Docs: accuracy pass (README, ROADMAP, parity, preferences)
2026-10-04 01:16:08 +07:00
BFlat 8a7142f9eb Merge pull request #4 from storytold/timeline-properties
Timeline: working rename and twirl arrows, every property shortcut (AA…UU, Alt+Shift keys, Ctrl+`)
2026-10-04 01:16:00 +07:00
bflatastic 2c056ae6cf Docs: accuracy pass — README (306 effects, the web build ships, HEVC/AV1 export, pure-Rust encoders are FilmCraft's plus our own, an accurate 'still to come' list instead of features that exist), ROADMAP (M11 done, M12 mostly done, M13 done, all effects in full), parity summary (no simplified effects left), preferences (web storage is OPFS/IndexedDB; the in-window macOS menu bar setting), CLAUDE.md (the Illustrator sibling is ../vectorcraft) 2026-10-04 01:13:21 +07:00
bflatastic f95eface83 M13.20: docs — README (Timeline shortcuts and Puppet tools), control protocol (reveal / keyAt / twirlSelected UI commands, timeline.layer.<id>.row and viewer.puppetPin.<uid> element ids), agents guide (Timeline: layer.arrange above, reveal shortcuts, keys.toggleTransform, rename), architecture (puppet selection, rigging and follow-through; per-thread expression context recovery), parity (M13.17–M13.20) 2026-10-04 01:10:36 +07:00
bflatastic f9990f7387 M13.20: every Timeline property shortcut — double presses AA (Material Options), PP (paint / Roto Brush / Puppet), SS (selected properties), RR (Time Remap), TT (Mask Opacity), MM (all mask properties; a single M now shows Mask Path), FF (missing effects), EE (expressions) next to LL and UU; Alt+Shift+A/P/S/R/T add or remove a keyframe at the current time on every selected layer in one undo step (keys.toggleTransform: separated X/Y/Z Position, Orientation and X/Y/Z Rotation on 3D layers); Ctrl/Cmd+` twirls the selected layers open or closed (from a reveal, their full tree) 2026-10-04 01:10:33 +07:00
bflatastic aef72378f8 M13.19: the Timeline's rename field and twirl arrows work — the name field took the keyboard focus every frame (Enter and clicking away could never leave it, which also silenced every single-key shortcut); it now focuses once with the name selected, Enter or a click elsewhere commits, Escape cancels; the layer row's click area sat on top of the twirl arrow and the name field (it was registered after them) and now sits under them; Ctrl-click a layer's arrow opens every group inside 2026-10-04 01:09:20 +07:00
BFlat ba3748f79e Merge pull request #3 from storytold/timeline-viewer-fixes
Viewer pan no longer snaps back; drag to reorder layers in the Timeline
2026-10-04 00:33:54 +07:00
bflatastic 52cf74e997 M13.18: drag layer rows in the Timeline to reorder them — a line shows where they land, the selected layers move together (an unselected row moves alone), one undo step, nothing for a drop in place; layer.arrange {above: layer} for agents 2026-10-04 00:31:59 +07:00
bflatastic bb973e2c76 M13.17: the viewer keeps its pan after a middle-button or Hand drag — egui reports no total drag on the release frame, so the pan was recomputed as the drag's start and snapped back 2026-10-04 00:31:59 +07:00
BFlat bb06a66083 Merge pull request #2 from storytold/puppet-rigging
Puppet tools: pin editing, rigging to nulls, follow-through; expression runtime fix
2026-10-04 00:26:24 +07:00
bflatastic 78dceea638 M13.16: docs: parity cites the renumbered puppet tasks (M13.14–M13.16) 2026-10-04 00:22:30 +07:00
bflatastic a02bcc8441 M13.16: puppet Follow-Through — puppet.follow (Puppet tool bar ▸ Follow-Through…) makes pins trail a leader pin for hair, cloth and overlapping action: each follower's Position adds the leader's displacement from rest delayed by delay (the k-th nearest by k × delay with Cascade), scaled by Amount, keeping its own keyframes; leader = the first selected pin, followers = the others; one undo step; reachable over MCP (execute_command) 2026-10-04 00:19:26 +07:00
bflatastic 6ec177de83 M13.15: rig puppet pins to nulls — Points Follow Nulls / Nulls Follow Points (Window ▸ Create Nulls From Paths, paths.* {pins?}) take selected pins, named pins, or every pin of a layer with no path: a null per Position / Advanced pin drives its Position (fromComp of the null's anchor, so parented nulls rig limbs), or rides on it (toComp of the pin's Position) to carry other layers; one undo step 2026-10-04 00:19:25 +07:00
bflatastic 6869cff51b M13.14: puppet pin editing — click / Shift-click selects pins (puppet.selectPins), a drag from empty canvas or Alt-drag marquee-selects them, Select All with a pin selected takes every pin of that kind, Delete removes the selected pins instead of their layer; Advanced and Bend pins rotate by their ring and scale by its square (Shift: 15° / 5 % steps, one undo step per drag); ⌘/Ctrl-dragging one of several selected pins records them all; pins in their kind's colour, larger under the pointer; pushpin icons for the five pin tools 2026-10-04 00:19:24 +07:00
bflatastic 244cee4687 M8.1: expressions keep working on long-lived threads — boa leaves values on its VM stack when an exception unwinds through call frames (every pending host request does), so after tens to hundreds of evaluations that read layers or effects every expression on the thread failed with "maximum stack size"; a run that hits the limit now gets a fresh context and is retried once (regression test: 6000 host-reading evaluations on one thread) 2026-10-04 00:19:22 +07:00