Skip to content

Latest commit

 

History

History
279 lines (233 loc) · 15.6 KB

File metadata and controls

279 lines (233 loc) · 15.6 KB

slosh format 1

sloshgen accepts a JSON recipe and produces a self-contained .slosh.json asset. All numbers must be finite. Unknown recipe fields fail rather than being silently ignored. Input errors are FormatException with the field name; CLI usage errors exit 64 and generation errors exit 1. Failed generation preserves an existing output. SVG and recipe files are limited to 1 MiB each.

Recipe

Required fields:

Field Contract
version Integer 1
shape Exactly one of polygon, pathData, or svg; see below
pivot [x,y], in source viewBox coordinates
duration Seconds, 0.01–60
fill 0–1, fraction of the enclosed area
loop Boolean; requests validated seamless repetition
rotation 2–1000 keys, increasing times spanning exactly 0 to duration
layers 1–4 ordered layer objects with unique IDs

Optional liquid parameters: damping (ratio, default 0.8, 0.001–10), naturalFrequency (Hz, default 2, 0.05–20), response (default 0.5, 0–10), and quality (object, defaults below). A rotation key has time in seconds, angle in clockwise radians (−1000 to 1000, never wrapped), and optional easing: linear (default) or smooth (cubic smoothstep). Easing belongs to the segment starting at that key. At interior keys the following segment supplies derivatives; the last key uses the preceding segment's terminal derivatives.

Each layer requires an id of 1–64 characters. Optional values:

Field Default Range / units
color 0xff38bdf8 Unsigned integer ARGB, 0–4294967295 (JSON uses decimal)
opacity 1 0–1, multiplied by color alpha
amplitude 0.015 0–0.25 normalized distance
frequency 1 0–32 cycles across swept station interval
speed 1 −32 to 32 temporal cycles/second
phase 0 Radians, absolute value <= 1e6

A loop with decorative waves requires speed * duration to be an integer for each nonzero-amplitude layer. Use different phase, frequency and amplitude for layered depth. Each layer retains the same nominal fill amount.

Shapes

  • Polygon: {"polygon":[[0,0],[100,0],[100,100],[0,100]],"viewBox":[0,0,100,100]}. Polygon lists implicitly close; repeating the first point at the end is also accepted. SVG contours require explicit Z.
  • Raw path: {"pathData":"M0 0 H100 V100 H0 Z","viewBox":[0,0,100,100]}.
  • SVG file: {"svg":"../shapes/pathverse.svg","clipPathId":"clippath"}. Relative paths resolve against the recipe file. pathId can select a path by its own ID. clipPathId selects paths below that clipPath. Exactly one path must result. An explicit recipe viewBox overrides the SVG viewBox.

Supported SVG commands: M/L/H/V/C/S/Q/T/Z, absolute and relative, repeated arguments, and implicit line segments following M. Arcs, multiple subpaths, holes, nested SVG coordinate systems, objectBoundingBox clips, and transforms on the selected path or its ancestors are rejected. CSS transforms are rejected. This is contour extraction, not SVG scene rendering: fills, strokes, gradients, and unrelated painted paths are not imported. A single contour inside a clipPath is selectable, but arbitrary scene clipping/compositing is not applied.

Curves flatten within geometry tolerance. Contours must be simple and have positive area after normalization. Up to 4096 normalized vertices are accepted. Coordinates are bounded to ±1e6; normalized swept radius is limited to 100. Normalization subtracts the viewBox origin and divides both axes by its larger dimension, preserving aspect ratio. The pivot follows the same transform.

Quality

Field Default Accepted range / units
stations 32 Integer 8–256
hz 240 Integer 30–960; duration * hz <= 20000
geometry 0.0005 1e-8–0.05 normalized distance
surface 0.002 1e-8–0.1 normalized displacement
area 0.005 1e-8–0.1 absolute fraction of container area
rotation 0.001 1e-8–0.1 radians
seamPosition 0.002 1e-8–0.1 normalized distance
seamVelocity 0.02 1e-8–1 normalized distance/second
seamRotationVelocity 0.01 1e-8–1 radians/second
warmup 20 Integer 1–100 repeated cycles
maxBytes 2097152 Integer 128–16777216 encoded UTF-8 bytes

All failures are explicit, including nonconvergent loops and unattainable spatial, area, or output budgets. Lower tolerances may increase file size and generation time. Increasing keyframes cannot repair insufficient spatial resolution. Validation samples the requested dense grid and independently integrated midpoints at twice hz, plus authored keyframe times. Reported maximum errors are sampled observations, not continuous mathematical bounds.

Baked asset

Required version-1 fields:

Field Representation
version, model 1, "surface-modes-1"
duration, fill Same units/ranges as recipe
loopSafe Boolean, set only after generation seam validation
polygon 3–4096 normalized [x,y] vertices, implicitly closed
pivot Normalized [x,y]
bounds [left,top,width,height], enclosing the whole swept vessel
stations 8–256 strictly increasing normalized x coordinates covering bounds
layers 1–4 objects containing id, decimal ARGB color, opacity
frames 2–10000 objects containing time, angle, surfaces

Frame times strictly increase and include 0 and duration. surfaces contains one y-coordinate array per layer, with exactly one value per station. For fill 0 or 1 every layer's surface array is empty; the player draws nothing or the whole vessel. The player enforces encoded size before decoding, validates all required fields, finite coordinates, counts, simple geometry and swept bounds, and constructs immutable data. It rejects unknown versions/models.

At time t, interpolate unwrapped angle and corresponding y values linearly between neighboring timestamps. Draw piecewise-linear surfaces in the fixed presentation frame (x right, y down), closing below swept bounds, clipped to the vessel rotated around its pivot. Scale all geometry uniformly. Keep container and surface on the same clock. Unrelated ancestor rotation rotates the entire presentation and does not change the baked gravity direction.

The asset contains no source paths, timestamps or machine metadata. Generation uses stable JSON field ordering and deterministic integration. Canonical bytes are covered by repeated-generation tests. Generator/player interoperability is covered by checked-in assets and dense numeric references.

Surface model

Two damped modes use frequencies omega and 1.6 * omega, where omega = 2*pi*naturalFrequency. For each rotating vertex offset (x,y), wall horizontal velocity is -angularVelocity*y and acceleration is -angularAcceleration*y-angularVelocity^2*x. The bounded drive is clamp(0.6*velocity+0.04*acceleration,-5,5). Average it for mode 0 and project onto sin(pi*x/radius) for mode 1. Modal acceleration is 0.06*response*drive*omega^2 - 2*damping*omega*v - omega^2*q. Semi-implicit integration substeps are bounded by both the validation cadence and omega*20*max(1,damping). The surface combines a linear tilt mode, a sine mode, and each layer's decorative sine wave. A bracketed height solve corrects clipped area to a tighter internal tolerance before keyframe compression.

The model is stylized. It does not conserve separate trapped pools, simulate spilling or droplets, or reproduce full centrifugal fluid dynamics. Translation, animated scale, live gesture physics, and true 3D are outside version 1.

Version 2: stylized pool and droplets

Version 2 is the opt-in contour format included in the initial package release. The current development status is in the implementation checkpoint. The input controls below are implemented; complete example acceptance is tracked separately.

Common shape, viewBox, pivot, duration, rotation, fill, layers and wave-response fields retain their version-1 conventions. policy is required: surface uses a pool, droplet uses rounded bodies, and hybrid permits both. emission requires hybrid; surface rejects droplet controls and explicit seeds. Unknown fields fail. Old experimental parcels, maxParcels, grid/density controls, contactDamping and transitions are retired and rejected.

Setting Default Meaning / bounds
wall.friction 0.5 Artistic tangential entrainment, 0..1
droplet.adhesion 0.25 Wall retention, 0..1
droplet.viscosity 0.2 Deformation damping, 0..1
droplet.cohesion 0.5 Resistance to stretch/split, 0..1
droplet.gravity 1 Normalized distance/s² toward positive y, 0..10
emission.enabled false Hybrid only
emission.side auto Presentation left/right or automatic contact
emission.threshold 0.5 Minimum abs(angularVelocity) * friction, 0..20 /s
emission.cooldown 0.25 Seconds between eligible pickups, 0.05..10
emission.dropShare 0.02 Fraction of initial total liquid per pickup, >0..0.25
emission.minDropShare 0.005 Minimum fraction of initial total, >0..dropShare
emission.styleId droplet default Optional valid named style
quality.maxDroplets 8 Active attached/free bodies, 1..32
quality.contourPoints 32 Samples per simple loop, 16..128
quality.relativeArea 0.05 Approximate total visible area error, 0.001..0.1

Use inherited bounded quality.hz, duration, layers and byte controls. Cap total frames at 10000, serialized coordinates at 2 million, and active vertices at 4096. Named styles are bounded to 32, with 1..64 character IDs. Keep split/merge/event history bounded to 256 events per bake. These limits bound the product of contours, layers and samples. Failed CLI generation preserves an existing output.

Rotation is unwrapped radians, positive clockwise in screen coordinates. Negative angular velocity is counterclockwise. Pickup left/right is relative to the presentation pivot; all sides require a wetted, upward-moving wall contact. Clockwise motion lifts the left wall; counterclockwise lifts the right wall. An incompatible explicit side suppresses pickup instead of launching a drop. Zero friction or stationary motion produces no wall pickup.

A picked-up bead begins submerged, clings to the inner wall and travels with it. Friction controls tangential carry/slip; adhesion controls retention against gravity. When the inward gravity load exceeds retention, the bead releases with its carried velocity and falls back into the pool while rotation continues. The emission name is retained for this pickup configuration. Diagnostics record pickup, release, absorption and suppression; contour split/merge events also represent visible separation from or reconnection with the pool.

Translation and shaking

Version 2 optionally accepts translation alongside rotation:

"translation": [
  {"time": 0, "offset": [0, 0], "easing": "smooth"},
  {"time": 0.12, "offset": [0.1, 0.035], "easing": "smooth"},
  {"time": 3, "offset": [0, 0]}
]

Supply 2..1000 strictly increasing keys spanning zero to duration. Offsets are finite source distances in presentation axes (x right, y down), normalized by the viewBox scale. Translation applies after rotation about the source pivot. Omitting the track means zero translation. Outgoing easing is linear (default) or smooth cubic smoothstep, using the same convention as rotation.

Rapid changes in linear or angular velocity accumulate transient droplet strain. Sufficient stretch causes necking and breakup; contacting fragments recombine. Steady translation or rotation does not continuously add breakup strain. Integration stops at keys in either track, and a linear velocity discontinuity is counted once. Smooth tracks contribute finite velocity changes over each step. This is a bounded animation model, not a fluid solver.

Optional initialState.seeds contains 1..32 circles with a unique id, center: [x,y], optional velocity: [x,y] (default zero), positive share and optional styleId. Shares sum to one within 1e-10. Centers are source coordinates, normalized by the viewBox and transformed by the initial rotation. Velocity is in source units/second in presentation space and is divided by the viewBox scale. Radius follows the assigned area. Circles must fit and not overlap. Default hybrid/surface initialization is settled pooled water; default droplet initialization is a settled rounded body when it fits. Empty/full states reject explicit seeds. Initial seed count must respect maxDroplets.

Appearance references

appearance.styles maps 1..64-character IDs to {color?, opacity?} with at most 32 entries. Input color accepts #RRGGBB or an unsigned ARGB integer. Opacity is 0..1, default 1. appearance.poolStyle and appearance.dropletStyle name optional defaults. emission.styleId and each seed's styleId can override the droplet default. All references must exist. Omitted appearance inherits layers.

A split inherits parent style. A merged drop uses the larger contributor's style (equal amounts choose the lexically smaller stable body ID). Absorption transitions to the pool style. Styles represent presentation, not pigment mixing. Recoloring must preserve geometry, amounts and event timing.

The Flutter Slosh and SloshPainter accept styleColors and styleOpacities maps in addition to the existing layer colors/opacities. Color precedence: runtime style override, baked style color, runtime layer override, baked layer color. Resolved style opacity multiplies resolved layer opacity.

Contour playback contract

Assets use version: 2, model: hybrid-contours-1, policy, state (empty, full, liquid), and common vessel/rotation/appearance metadata. bodies records IDs, amounts, parent IDs and model history. segments covers zero through duration without gaps; each segment fixes its contour layout and stores at least two ordered frames including its endpoints. A layout contains id, body, layer, hole, parent, points, optional styleId, and optional endStyleId. Each frame has time, angle, interleaved x/y coordinates rows and optional normalized offset: [x,y] (default zero), plus optional per-contour styleMix values in 0..1. Contours already include the world offset. The player translates the shell and clip once, without translating liquid coordinates a second time. Empty/full states use the same pose without contour payload. Fixed bounds enclose the swept translated vessel and prevent fit-scale jitter. Offset scalars count toward payload budgets. Motion keys survive compression; loop checks include translated endpoint position and velocity. Holes share their exterior's styles and mix. Missing mix is zero; nonzero mix requires an end style.

Adjacent segments contain outgoing and incoming geometry at the same timestamp. Exact boundary sampling selects incoming geometry; duration selects the last frame. No cross-segment point interpolation occurs. Optional style transitions are sampled on the same geometry, so reverse seeks need no event replay. Missing style metadata preserves existing contour-fixture layer appearance.

Generation preserves scalar liquid amounts; visible area has an approximate relative total-area budget. Optional emissions that cannot fit or exceed the active cap are suppressed before debit and reported. Existing droplets are not deleted to meet a budget. Unsupported geometry, mandatory event or output budget failures reject generation, preserving an existing output file. A requested loop must pass endpoint checks; a one-shot recipe need not close.