Files
RUWIO/claude.txt
T
thandClaude Opus 4.7 9e25cf155a 1.2.10: wiggle — deterministic procedural shake modifier per layer
Per-layer modifier that adds noise to the sampled position and
rotation each frame. Pairs thematically with kaleido: both stack
on top of the keyframe sample, both per-layer, both deterministic
so editor and exported runtime render identically.

Math (src/lib/wiggle.ts):
- hash32 + layerSeeds(layerId): three uncorrelated 32-bit seeds per
  layer, derived from the id so the same layer always wobbles the
  same way.
- wiggleNoise(seed, t): sum-of-three-sines in [-1, 1]. Smooth (C∞),
  cheap, no noise-lib dependency. Phase constants are irrational
  ratios picked so the streams don't collapse into a low-period
  pattern.
- applyWiggle(props, layer, time): adds amp * noise(seed, t*freq)
  to x / y / rotation. Defaults: 8px position, 2° rotation, 2Hz.

Wire-in:
- src/lib/interpolate.ts sampleLayer: post-processes the keyframe
  sample with applyWiggle. Every renderer goes through sampleLayer
  (canvas2d, Konva, button-state, clip-internal), so the modifier
  lights up everywhere from a single hook.
- src/lib/exportPlayer.ts engine: byte-for-byte JS mirror of
  hash32 / layerSeeds / wiggleNoise / applyWiggle, plus the same
  post-process branch in the engine's sample(). RUNTIME_VERSION
  31 → 32.

Data + store:
- Layer.wiggle?: { amplitude?, rotation?, frequency? }. All sub-
  fields optional with the documented defaults.
- store.setLayerWiggle(id, patch | null): patch object updates
  individual fields with magnitude clamping (≥ 0); null deletes
  the whole wiggle field.

UI (PropertiesPanel + i18n):
- New "Wiggle" section between Mask and Kaleidoscope. Toggle
  (PhWaveSine icon) seeds the pleasant defaults; three RangeSliders
  for position amplitude (0-64 px), rotation amplitude (0-45°),
  and frequency (0-10 Hz). Hidden for button-asset layers
  (consistent with Kaleido's hit-area constraint).

Tests (8 new under "wiggle — procedural shake modifier"):
- wiggleNoise determinism + [-1, 1] bounds across 4 seeds × ~100
  time points.
- layerSeeds stability + the three seeds are pairwise distinct;
  different ids yield different triples.
- applyWiggle on a layer without `wiggle` is a no-op (returns the
  input reference unchanged).
- sampleLayer with wiggle: drift visible across a 1s window and
  always within the configured amplitude bands.
- sampleLayer with wiggle is deterministic — same time, same drift.
- setLayerWiggle roundtrip + magnitude clamp + null-delete.
- PropertiesPanel Wiggle toggle writes the wiggle field with the
  documented defaults.
- Exported runtime carries wiggleNoise / applyWiggle / wiggleSeeds
  and bumps RUNTIME_VERSION to '32'.

Plan for the next two backlog items (motion-path overlay + canvas
eyedropper) is in claude.txt for later.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 20:24:23 +02:00

109 lines
4.5 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
FUTURE FUN: THREE FEATURES TO LAND
==================================
End-of-day backlog of ideas Claude pitched and the user liked. Each
sized for ~one patch bump. Wiggle goes first; the others can wait.
1. WIGGLE — procedural shake modifier (per layer)
-------------------------------------------------
Add a "Wiggle" property to Layer. When on, every sampled frame
adds deterministic noise to position and/or rotation, giving any
layer that classic animator wobble. Pairs thematically with Kaleido:
both are per-layer procedural modifiers stacked on top of the base
keyframe sample.
Data model (src/types/scene.ts):
wiggle?: {
amplitude?: number // pixels for x/y
rotation?: number // degrees for rotation
frequency?: number // Hz (cycles per second)
}
// Defaults when `wiggle` is set but a sub-field is missing:
// amplitude=8px, rotation=2°, frequency=2Hz. Toggle off ⇒ delete.
Math (src/lib/wiggle.ts) — shared truth:
function layerSeed(id: string): [number, number, number]
// 3 stable 32-bit ints derived from the layer id, used as
// independent seeds for x / y / rotation noise streams.
function wiggleNoise(seed: number, t: number): number
// Deterministic [-1, 1] output. Sum of three sin waves with
// different irrational phase offsets — cheap, smooth, no
// dependency on a noise lib.
return (
sin(seed * 12.9898 + t * 78.233) +
sin(seed * 4.31 + t * 11.71 ) +
sin(seed * 7.71 + t * 23.5 )
) / 3
function applyWiggle(props, layer, time): props'
// Adds amp * noise(seedX, t * freq) to props.x, etc.
Wire-in:
- src/lib/interpolate.ts sampleLayer: post-process the result of
the keyframe sample with applyWiggle. ALL renderers go through
sampleLayer (canvas2d, Konva, button-state, clip-internal), so a
single hook here lights up everywhere except the export engine.
- src/lib/exportPlayer.ts: mirror the math in JS inside the engine
source so exports look identical. RUNTIME_VERSION bump.
UI (src/components/PropertiesPanel.vue + i18n):
- New "Wiggle" collapsible section, between Mask and Kaleido feels
natural. Toggle (PhWaveSine icon), then three RangeSliders:
amplitude (0–64 px), rotation (0–45°), frequency (0–10 Hz).
Hidden for button-asset layers (consistent with Kaleido).
Store (src/stores/scene.ts):
- setLayerWiggle(id, patch) with same delete-on-default semantics
as Kaleido (rotation=0 ⇒ delete the field, etc.).
Tests:
- wiggleNoise determinism: noise(seed, t) === noise(seed, t).
- wiggleNoise smoothness: |n(t) - n(t+1/60)| stays bounded.
- sampleLayer with wiggle: x/y/rotation drift around base, return
to base when wiggle is off.
- Engine source carries wiggleNoise; same sample at the same time
matches between editor and engine.
- PropertiesPanel renders the new section + writes wiggle fields
back to the store.
2. MOTION-PATH OVERLAY (selected layer's position keyframes)
------------------------------------------------------------
Visualize a layer's keyframed path on the canvas as a faint dashed
line connecting consecutive position keyframes, with each keyframe
rendered as a draggable dot. The position-keyframe drag-handle
infrastructure (PathFrame, activeSegments in CanvasStage.vue ~2562)
is already 70% there — currently only used when a position-kf is
explicitly being dragged. Promote that overlay to "always on while
the layer is selected".
Scope:
- Toggle in the View menu (or a slash-key shortcut) called "Motion
paths" — global flag persisted in localStorage like
`useShowLocked`.
- For the selected layer (and only the selected layer), draw the
overlay even when nothing is being dragged. Reuse the existing
segment / curve-sampling helpers.
- Color follows layer.color so two layers' paths don't collide
visually.
Bigger lift than Wiggle but mostly leveraging existing code.
3. EYEDROPPER (color sampling from anywhere on the page)
--------------------------------------------------------
Tiny feature using the browser's EyeDropper API. Adds a small
pipette icon next to every color swatch in the PropertiesPanel
(fill, stroke, shadow color). Click → fires `new EyeDropper().open()`
→ user picks any pixel on screen → result sets the swatch.
Caveats:
- Chromium-only API. Hide the button when `window.EyeDropper` is
undefined (Safari + Firefox) — a clean progressive enhancement.
- Permission prompt is browser-handled, no extra plumbing.
Smallest of the three. Good "polish day" snack.