Skip to content

Releases: ZviBaratz/fresco

v1.3.0

Choose a tag to compare

@github-actions github-actions released this 21 Jul 16:58
50c76e2

Added

  • Prebuilt fresco binaries on every tagged release. A .goreleaser.yaml and
    a tag-triggered Release workflow build cmd/fresco for linux/darwin/windows ×
    amd64/arm64 on each vX.Y.Z push, publishing archives (each with LICENSE,
    README.md, and CHANGELOG.md), a checksums.txt, and a GitHub Release whose
    notes come from this file's matching section — so fresco is installable without a
    Go toolchain. The binary also gains a --version flag backed by an
    ldflags-injected main.version.

v1.2.0

Choose a tag to compare

@ZviBaratz ZviBaratz released this 21 Jul 12:26
95188b9

Added

  • A runnable fresco screensaver CLI (cmd/fresco). fresco is now directly
    runnable, not only importable: go install github.com/ZviBaratz/fresco/cmd/fresco@latest
    builds a full-screen terminal screensaver around the pure Render engine. It
    auto-sizes to the terminal and re-renders on resize (SIGWINCH on Unix, a size
    poll where there is none), animates in the alternate screen so scrollback is
    preserved, and takes single-key controls (q/Ctrl-C quit, space next variant,
    p pause). The terminal is restored on every exit path — normal end,
    Ctrl-C/SIGTERM, error, and panic — through a LIFO teardown stack behind a
    deferred restore, so the screensaver can't wedge the terminal. Flags cover the
    variant (a name or cycle/all), palette (presets or five custom hex anchors,
    validated through Palette.Validate), fps, seconds-per-variant, focal-row,
    lum-range, colour profile, duration, --size, --once, and --list;
    NO_COLOR/FORCE_COLOR are honoured, and a non-TTY stdout degrades to a single
    frame so a pipe or CI never hangs on a size query or spews control codes. The
    fresco package gains no new dependency and no impurity — all terminal I/O,
    the clock, and signals live under cmd/, and the logic is factored into a pure,
    TTY-free-testable core (flag/profile resolution, frame composition, the variant
    schedule) plus a thin impure driver exercised through a fake Console.

  • galaxy_figures_test.go makes the galaxy's shipped figures executable. Every
    quantitative "because" in the galaxy's comments is backed by a measurement, but
    those measurements lived only in prose, which rots — the #60/#61 work alone quoted
    three figures that had already gone stale (118.5 → 135.5, the ruler "odd → even",
    "~3% → ~0.7%"). The new test asserts the shipped figures the compiled renderer
    produces — whole-pane/arm clipping (1.36% / 0.92%), nucleus glyph contrast
    (0.430), core/arm/outskirts bead density (58.4 / 135.5 / 67.3), core/mid
    colour-stop density (11.46 / 6.13), and the lumRange 0.75 → 0.60 arm-annulus
    A/B (93.1 → 135.5) — each within ±5%, measured through the existing helpers and
    the public Options.LumRange override, with no twin of the renderer's
    internals
    . It passes on main and fails on a deliberate galBulgeAmp tweak;
    each assertion names the prose that quotes it. It does not assert the ridge/gas
    ratio (243.8 / 26.8), which is defined on the raw arm value the point function
    never returns and so cannot be measured without such a twin — that stays a
    tuning-probe finding in prose. SKILL.md §7 gains the tuning-probe (throwaway) vs
    figures-test (permanent) distinction. No rendered-byte change; an unparam
    nolint on two shared test helpers (now called from a second file) is the only
    edit to existing code.

  • rain_figures_test.go makes the rain's shipped figures executable, the second
    application of that convention — and the one it earns hardest, since rain is the
    field whose figure already rotted on screen: the layer cascade shipped pinned in
    raw field units, upstream of the smoothstep contrast curve, and rendered 3.9 L*
    apart against the 16.2 its comment claimed (fixed in [1.1.0]). The new test
    measures the cascade on the side of the curve the eye sees, two ways: it pins the
    layer head cascade (81.9 / 65.7 / 47.4 / 29.1, separations 16.2 / 18.3 / 18.3)
    and the per-layer head-outshines-tail steps (28.2 / 36.6 / 30.4 / 18.1) through
    rainScreenStopFor — the real smoothstep and stop quantization applied to each
    layer's shipped bright, read off the real ramp — and it anchors those with two
    rendered-byte figures: the lit fraction at rain's quoted 96×30 pane (27.0%,
    guarding rainDensity) and the rendered proof that the mid layer's heads land in
    the L* 60–69 band, which must dominate the 70–79 band they vacated. All within ±5%
    (reusing the galaxy file's requireFigure), passing on main and failing on the
    pre-fix rainLayers mid bright 0.64 → 0.72, which inverts the rendered bands
    exactly as [1.1.0] describes. It does not assert the one-off histogram counts
    (239 → 53, 50 → 193), which do not reproduce without the demonstration's
    unrecorded pane/frame span — their robust relation is the test, the counts stay
    prose — and there is no lumRange A/B, since lumRange is a dead lever for rain
    (pinned at 1). SKILL.md §7 gains the rain refinements (post-curve + rendered-byte
    anchor; demonstration-count-vs-relation; the unparam dodge). No rendered-byte
    change, and this time no edit to existing code at all — the shared rainStopGrid
    helper is called at a pane distinct from its other caller, so unparam stays quiet
    without a nolint.

  • tunnel_figures_test.go makes the tunnel's fog geometry executable, the third
    application of the convention. The tunnel already carries a thorough invariant
    suite, but its one quantitative geometry claim — how deep the fog's black core
    reaches — lived only in prose. The test pins it from both sides: the analytic
    half-lit radius the shipped fog constants imply (0.5·(tunFogA/tunRefD)/(tunFogGain − 0.5) = 18.4% of maxD, so it fails the moment tunFogA, tunRefD or tunFogGain
    drift under the quoted figure), and the rendered half-peak crossing of the
    radial luminance profile (≈15% of maxD), asserted to be the same curve across
    96×30 / 160×44 / 240×60 / 300×80
    — the tunnel's defining scale invariance — and to
    sit inside the analytic 18.4%. Within ±5% (reusing requireFigure), passing on
    main and failing on a tunFogGain tweak. Measuring it corrected a stale prose
    figure: the profile crosses at ≈15% (13.9–15.4%), not the 16% the comment
    claimed, so tunnel.go and the test now agree. It does not assert the
    ring-spacing product — tunnel.go quotes a good value of 400 against the shipped
    tunDepthK·tunFreqU = 70, a genuinely stale figure, but rendered ring spatial
    frequency
    has no single value to pin (it varies as r² by design), so the
    discrepancy is surfaced here rather than tested. One new file plus the two prose
    fixes; no rendered-byte change.

  • ripple_figures_test.go makes the ripple's packet-shape figures executable, the
    fourth application of the convention — and ripple is the field that proves why the
    gap matters: its row-pitch capture comment carried a stale 87.3% for three PRs
    after rippleCyc moved 1.5 → 1.8, because the figure was folded to a literal
    cos(0.15pi) that outlived the constant it came from, and the 75% floor guarding it
    never noticed. The new test pins the shipped packet shape on the compiled
    rippleDropWave: the worst row-pitch capture (82.8%, the anti-blink margin), the
    packet trough as a share of its crest (54% at the shipped cyc, only floored at 30%
    before) and where that trough sits (x = 0.48), the ring-open fraction
    (rippleHueOpen = 0.38) and a lone crest's amplitude (0.65 = rippleAmp, asserted
    under the clamp so two rings adding have headroom to read as constructive). Each
    within ±5% (reusing requireFigure), passing on main and failing on the pre-1.8
    rippleCyc 1.8 → 1.5, which drifts the capture back to exactly the stale 87.33%.
    It does not re-run the rippleCyc sweep (15/29/41/54/61%) — that is a tuning
    probe, so only the shipped point is pinned — nor the candidate-skip distribution
    (49.7 / 43.7 / 6.6%), an internal branch statistic the point function never
    exposes, which stays a prose measurement like galaxy's ridge/gas. One new file, no
    edit to existing code, no rendered-byte change.

  • aurora_figures_test.go makes the aurora's tuning figures executable, completing
    the roster (all five variants now guard their shipped figures). It pins the two
    numbers aurora is actually tuned around: the drift-trap Nyquist figure — the
    finest fBm octave sits at auroraFreqX·2^(auroraOct−1) = 0.40 cycles/cell, below the
    0.5 limit, so no octave crawls sideways as the curtains drift (a fourth would land
    at 0.80 and alias) — and the fBm window placement: measured on the compiled
    auroraFBM, its median is ~0.50 and its 90th percentile ~0.67, and the curtain
    window must straddle the upper third (auroraLo just above the median, auroraHi
    near the 90th percentile). Within ±5% (reusing requireFigure), passing on main
    and failing on auroraFreqX 0.10 → 0.15 (finest drifts to 0.60, past Nyquist) or
    auroraHi 0.70 → 0.80 (off the fitted p90). Measuring corrected two stale prose
    numbers: the fBm clusters around 0.50 (not 0.53) and tails to ~0.79 at the 99th
    percentile (not "near 0.82") — aurora.go now matches. One new file plus that prose
    fix; no rendered-byte change.

    With this the executable-figures convention is applied across the whole roster
    (galaxy, rain, tunnel, ripple, aurora), and SKILL.md §7 documents it as the standing
    practice for a shipped figure.

Changed

  • fresco.Tunnel's ring-spacing comment (tunDepthK) corrected to match the
    shipped product, resolving the discrepancy the tunnel figures test surfaced. The
    comment quoted a good product of 400 against a bad 72, but the shipped
    tunDepthK·tunFreqU is 70 — at the "bad" end. That framing predated the octave
    stack and the per-octave mip: 70 is the coarsest octave's ring spacing,
    deliberately coarse (tunFreqU is low for the dead-zone reason its own comment
    gives), and the five-octave stack plus the mip carry the rings out to the rim, not a
    single high product. Verified before rewriting — the wall's texture variance is flat
    at the mip-quieted centre and carries out into the pane (already pinned by
    TestSplashTunnelMipQuietsTheVanishingPoint), the opposite of the "coloured haze"
    the old comment feared. Comment only; no code or rendered-byte change.

Removed

  • *...
Read more

v1.1.0

Choose a tag to compare

@ZviBaratz ZviBaratz released this 20 Jul 20:15
93d815b

Fixed

  • fresco.Rain renders its four parallax depths as four, not three. The layer
    cascade was pinned in raw field units, upstream of the smoothstep contrast curve
    every value passes through before Pass 2. Smoothstep flattens approaching 1, so
    the near and mid layers — 1.00 and 0.72, comfortably apart in the field — both
    landed near the ramp's top and rendered 3.9 L* apart, against the 10-point
    separation the design calls for and the 16.2 its own comment claimed. The
    documented cascade 81.9 / 65.7 / 47.4 / 35.2 was never what reached the screen;
    that was 81.9 / 78.0 / 47.4 / 29.1.
    TestRainLayersSeparateInBrightness and TestRainHeadOutshinesItsTail now measure
    through the curve (rainScreenStopFor), which makes the guard fail on the shipped
    value, and the mid layer's bright moves 0.72 → 0.64 to satisfy it: the rendered
    cascade is now 81.9 / 65.7 / 47.4 / 29.1, separations 16.2 / 18.3 / 18.3. 0.64
    is the centre of the stop-10 plateau rather than an edge of it, so a later palette
    change cannot quietly tip it to a neighbouring stop. Measured on rendered output:
    the mid layer's heads move out of the L* 70–79 band (239 cells → 53) and into
    60–69 (50 → 193), opening the gap under the near layer that the parallax reads
    from. Rendered bytes change by design; determinism, bounds and rain's other
    invariants are untouched.

Changed

  • The README demo GIF re-recorded to tour all five variants — it previously
    showed only the pre-campaign four (rain, tunnel, ripple, galaxy) and now includes
    aurora, with every field at the re-art campaign's current look (including galaxy's
    #60 bulge). .github/vhs/demo.tape extends its tour by one variant accordingly.
    Docs/asset only; no code change.

  • fresco.Galaxy's bulge grades into the disk instead of saturating into a flat
    bright mass (#60). galBulgeAmp drops 1.0 → 0.60. At 1.0 the pedestal sat near 1.0
    across the inner disk, so the disk it rides on — floor + arm + knot, the knot term
    doubled to galKnotAmp 2.0 in #56 — stacked past 1 and clamp01 flattened the sum:
    2.84% of the pane clipped (val == 1.0, a field-level fact upstream of Pass 2) and
    the core rendered as a solid block of two glyphs. The clamp was discarding real
    structure, not a smooth saturated field — across the clipped cells the pre-clamp
    bulge + disk spans 1.02..2.75 — so dropping the pedestal is what lets it show,
    rather than making room for structure never computed. At 0.60 clipping falls to 1.36%
    (the residue is sparse knot peaks, not a block) and the nucleus's local glyph
    contrast — |centre − mean(8-ring)| in ramp steps, the flat-mass metric — rises
    0.13 → 0.43, while the core stays the field's brightest region (core colour-stop
    density 11.5 against the mid-disk's 6.1). A soft-knee was measured and rejected:
    removing the clamp without dropping the pedestal compresses the 1.02..2.75 spread
    into a band near 1.0 the glyph ramp cannot resolve, and the nucleus goes flatter
    (contrast 0.008). galBulgeAmp was settled by rendering {0.50, 0.55, 0.60, 0.65}
    in colour and mono and looking: 0.65 fills the nucleus back toward the old solid look,
    0.50 shrinks it to a dim dot that no longer reads as the bright core.
    TestSplashGalaxyRendersABrightCoreAndDimmingArms gains a core-structure floor
    (nucleus glyph contrast > 0.25, placed between the measured flat 0.13 and graded
    0.43): its coreLit > 0.9 and coreDens > midDens were both satisfied by the flat
    block they were meant to guard and so could not have caught this. Rendered bytes
    change by design; determinism, bounds and purity hold.

  • The galaxy band tests measure against the renderer's own length scale (#61). The
    radial ruler was computed two ways — renderField took the vertical term as
    max(cyFocal, h-1-cyFocal), galaxy_test.go took cyFocal — which differ by one on
    an even-height pane (29 vs 30 at 240×60; on odd heights the two are equal), so the
    test's rho ran ~0.7% large — the raw vertical term differs ~3%, diluted through the
    hypotenuse by the dominant half-width — and slid every band boundary inward. Both
    now call one splashMaxD(w, h, focalRow) helper (rain's and ripple's band tests
    too), so they cannot diverge again. The knot entry
    below quotes its arm-annulus bead figures on the pre-#61 ruler and the pre-#60 bulge
    (118.5 per 1000, ridge/gas 213.8 / 19.7, 10.9×); on the corrected ruler and the
    shipped field they are 135.5 per 1000, and — taking ridges as arm ≥ 0.75 and gas
    as arm ≤ 0.30 (the raised-cosine spiral phase, before the turbulence lift) across
    the 0.35 ≤ rho < 0.60 annulus, over frames 0/30/60 at 240×60 — 243.8 / 26.8, a
    9.1× ratio (the lower bulge lifts the inner-annulus gas cells, so gas beads rise a
    little more than ridge). No rendered-byte change.

  • fresco.Tunnel retuned for a warmer, textured corridor. Its lumRange moves
    from 1 to 0.75, so the ring texture takes the glyph-density ramp
    (o → O → 0 → @) and reads as a tactile, receding surface rather than a flat
    field of a single @; and the depth hue now relaxes to a warm base
    (tunHueBase) where the sampling-rate mip leaves the sweep unresolved — the near
    field and, on a real pane, most of the view — so the corridor reads as a warm
    interior receding into the cool cyan rings that survive out where the sweep
    resolves, instead of a uniform purple-blue wash. The field's brightness (the fog
    depth cue) and its mip/angular-seam guarantees are unchanged; only the Pass-2
    luminance split and the hue's unresolved fallback moved. Rendered bytes change by
    design; determinism, bounds, and the tunnel's invariants still hold.

  • fresco.Galaxy caught mid-turn. Its rigid pattern rotation galRotSpd doubles
    (1.0 → 2.0) so the spiral's turn — the roster's weakest motion — is plainly alive
    within a second or two of viewing while staying stately rather than spinning (phase
    is the field's only time term, so the per-frame step is far below any strobe). The
    arm turbulence is also raised (galTurbAmp 0.62 → 0.72, galKnotThr 0.68 → 0.63,
    galKnotAmp 0.70 → 0.85), which adds grain and lifts the brightest turbulence peaks
    into local highlights. The texture stays additive-on-peaks, so it opens no holes; the
    bright core still outshines the disk, the arm mip/anisotropy and core-finite
    guarantees are unchanged, and hue still moves without touching brightness. Rendered
    bytes change by design; determinism, bounds, and the galaxy's invariants still hold.

    An earlier draft of this entry claimed the knots "read as distinct bright beads
    strung along filamentary arms". Measured against galKnotAmp = 0, that overstates
    what the change does. The knot term is not inert — it produces 12× the local maxima
    (73 against 6 across three frames, at ≥8 L* over their neighbours) — but those
    maxima land in the bright core and the faint outskirts, not on the arms: per 1000
    lit cells the arm annulus carries 0.0 and 3.1 beads against 14.9 at the core
    and 12.3 further out, the lowest density in the field. In the glyph-density channel
    they do nothing measurable at any radius (mean glyph weight 9.24 against 9.22 in
    the arms). That draft went on to blame saturation — "the arms already sit at 8.4–9.2
    of 11 on the ramp, so an additive term there clips instead of studding" — and that
    mechanism is false too: re-measured at 240×60 over three frames, the arm annulus
    clips (val == 1.0) on 11 of 14,712 cells, 0.07%, and sits at a mean val of
    0.40 of 1.0, so there was headroom where the claim said there was none. Clipping
    in the field is real but lives at the bulge, not the arms (508 cells, 1.2% of the
    pane). Both the mean-glyph figure and the local-maxima distribution above are also
    single-channel readings taken before the instrument was sound. The
    actual cause is measured in the entry below. The rotation half of this entry is
    unaffected.

  • fresco.Galaxy's arms are actually studded now, and the reason the previous
    attempt could not have worked is a spatial-frequency one rather than a gain or a
    headroom one. The knots were gated on splashGalaxyTurbulence, an fBm carrying 47%
    of its energy at a period of 7.7 columns and 74% at 3.8 columns or wider, so the term
    brightened a region four to eight cells across — and a brightened region reads as a
    brighter arm, not as a knot. Measured against the term switched off, only 40% of
    the brightest decile of such brightenings survived subtracting its own eight
    neighbours (27.6% across every cell the term brightened at all): the blob's own skirt
    lifted the background it had to stand above.

    A bigger galKnotAmp could only buy that back by blowing the arms out. Swept on the
    old term, arm beads per 1000 lit go 13.1 → 32.2 → 50.8 → 70.4 → 84.7 at
    galKnotAmp 0.85 / 2 / 4 / 8 / 16, so gain alone does eventually clear this PR's own
    guard (TestSplashGalaxyArmsCarryKnots floors at 40) — the flat "no amount of gain
    could have fixed it" an earlier draft of this entry claimed is false. What it costs
    is the field: arm clipping rises 0.07% → 1.90% → 6.36% across that sweep, against
    118.5 beads at 1.50% clipping from the lattice below. Four times the gain for
    half the beads at worse clipping is the case for changing the term rather than
    tuning it.

    So the knots now ride their own high-frequency lattice (galKnotFreq 0.9,
    galKnotPeak 0.5, galKnotAmp 0.85 → 2.0), sampled in screen cells rather than
    in-plane ones — the in-plane axes are anisotropic by cellAspect/cos(galInc) = 2.17,
    so a frequency compact enough to read horizontally packs past Nyquist vertically. The
    turbulence now only gates them, softly (galKnotGas 0.35), because multiplying two
    sparse gates together starves the count faste...

Read more

v1.0.0

Choose a tag to compare

@ZviBaratz ZviBaratz released this 18 Jul 12:01
f1e5296

The 1.0 release. The public API is now stable and committed to under Semantic
Versioning: Render, AppendRender, Options, Palette (+ Validate), the
Variant set (+ Variants, ParseVariant, String), and the ColorProfile
enum. No exported identifier will be renamed, removed, or retyped before a
2.0.0; new variants, options, profiles, and methods may still be added, since
those are additive. The core contract is unchanged and now permanent: Render
and AppendRender are pure over their inputs and emit exactly h lines of
exactly w visible cells, never erroring or panicking on any Options — a
malformed Palette degrades to documented fallbacks, and Palette.Validate is
the opt-in check. The surface was validated against its real downstream consumer
(atrium bumped to v0.3.0 with zero code changes) and given a final last-look
review before freezing; see docs/api-review-v1.0.md.

Added

  • fresco.Aurora — a fifth splash variant: northern-lights curtains that drift
    slowly sideways over dark sky and snake as they go, the hue sliding warm→cool with
    altitude. An absolute field like Rain and Ripple (a bigger pane shows more
    sky), shaded at lumRange 0.75 so the filament cores keep the density ramp while
    the soft halo rides the colour. Registered in Variants(), ParseVariant, and
    the rotation.

v0.3.0

Choose a tag to compare

@ZviBaratz ZviBaratz released this 17 Jul 13:12
5b14ab3

The "Refine & prove" release. It settles the pre-1.0 API — a fresco-owned
ColorProfile enum, up-front Palette.Validate, and a buffer-reusing
AppendRender — then proves the hot path with an allocation pass and validates
the whole surface against its real downstream consumer (atrium).

Added

  • AppendRender(dst []byte, w, h, frame int, opts Options) []byte — a
    buffer-friendly render path that appends the frame to a caller-owned slice, so
    a per-frame loop can reuse one buffer (buf = AppendRender(buf[:0], …))
    instead of allocating a fresh string every tick (#17). Render becomes a thin,
    byte-identical wrapper over it. Purely additive: measured at 120×40 truecolor,
    reusing the buffer removes the per-frame output allocation (≈172 KB → 82 KB).
  • Palette.Validate() error — an advisory, opt-in check that reports any
    anchor that is not a canonical hex colour ("#rgb" or "#rrggbb"), naming
    every offending field (#18). Render is unchanged: it still never errors or
    panics on a malformed palette — each bad anchor degrades to a documented
    fallback, so the exactly-h×w-cells contract always holds. Validate is
    deliberately stricter than the renderer's parser, so it flags typos (a missing
    #, a wrong length, trailing garbage) the renderer would otherwise paint.

Changed

  • Options.Profile is now a fresco-owned ColorProfile enum instead of
    *termenv.Profile, so pinning colour depth no longer requires importing
    termenv (#15). The zero value, Auto, auto-detects the terminal exactly as
    an unset (nil) profile did before; pin TrueColor, ANSI256, ANSI16, or
    NoColor for a fixed depth. This also settles the options-ergonomics review
    (#16): Options stays a plain struct — functional options were rejected as
    per-frame allocation churn for a 60fps render call — and LumRange stays a
    *float64 because 0 is a meaningful value, so no sentinel can mean "unset".
    Breaking: Profile: &p becomes Profile: p (a ColorProfile value);
    callers pinning depth swap termenv.AsciiNoColor, termenv.ANSIANSI16,
    and drop the termenv import.

Performance

  • Hot-path allocation pass (#21) — the render loop no longer materialises the
    field into two per-frame []float64 buffers (the "Pass 1"/"Pass 2" split is
    fused: each cell is evaluated inline as it is emitted), and the per-palette LUT
    cache is keyed by a comparable struct instead of a freshly joined string on every
    call. Output is byte-identical (verified by a 16,896-frame SHA-256 sweep across
    variants × profiles × frames × sizes × luminance ranges). On a reused
    AppendRender buffer this takes the warm hot path from 4 allocations/frame
    (≈82 KB at 120×40) to 1 (Rain/Ripple: to zero); via Render, per-frame
    B/op roughly halves. Wall-clock gains are modest and variant-dependent (≈8–12%
    for the lighter variants, flat for tunnel and the shaded path) — the pass targets
    garbage, not latency. Baselines and method are recorded in
    docs/perf.md.

v0.2.0 — Open the doors

Choose a tag to compare

@ZviBaratz ZviBaratz released this 16 Jul 20:45
5b29aa1

fresco v0.2.0 — Open the doors

The first milestone of the open-source hardening effort. No changes to the
rendering engine or its public API
— this release is entirely about turning a
good repository into a well-run, contributable open-source project.

Highlights

  • Green CIbuild / vet / gofmt / tests across Go 1.25 & 1.26 on
    Ubuntu, macOS, and Windows, plus a race-detector job and golangci-lint.
  • 🤝 Community health files — Code of Conduct, security policy, issue & PR
    templates (including a "propose a new variant" form), and CODEOWNERS.
  • 📖 Contributor docsCONTRIBUTING.md, a variant-authoring guide
    (docs/variants.md), and a published roadmap (docs/ROADMAP.md).
  • 🎇 A demo you can see — an animated GIF at the top of the README, with its
    reproducible vhs .tape source committed.
  • 🧪 More tests — runnable Examples, fuzz targets, and a determinism guard.
  • 🏷️ README badges (CI + Codecov coverage), this changelog, and a pre-1.0
    versioning policy.

See the full changelog
for the complete, issue-linked list.

Full diff: v0.1.0...v0.2.0

v0.1.0

Choose a tag to compare

@ZviBaratz ZviBaratz released this 16 Jul 20:45
e35ab50

fresco v0.1.0

Initial release — the engine, extracted from
Atrium as a standalone module.

fresco is a pure, deterministic (width, height, frame, Options) → ANSI
rendering engine for the terminal: generative, free-running animated fields you
can drive from any render loop.

Included

  • Four variants: Rain, Tunnel, Ripple, and Galaxy.
  • The Options / Palette API.
  • Automatic terminal colour-profile detection, with Options.Profile to pin it
    (making Render pure over its inputs).
  • The cmd/fresco-demo runnable demo that cycles every variant.

This release page was added retroactively during the v0.2.0 milestone; see the
changelog for the
full history.