openvisengine/src/lib/version.ts

651 lines
46 KiB
TypeScript
Executable file
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.

// OVE — app identity + version stamp.
//
// Single source of truth for the product name and version shown in the UI
// (header, footer, document title). Bump APP_VERSION whenever we ship a
// large change so the running build can always be identified at a glance —
// useful both for the z.ai Version History and for naming local backups
// (v6.0 = rename to Open Visualization Engine + version indicator).
//
// STANDING RULE (since v7.6): every APP_VERSION bump MUST come with a
// matching entry at the top of RELEASE_NOTES in src/lib/release-notes.ts —
// that list powers the header's "What's New!" dialog. A dev-mode guard
// warns in the console when the two drift apart.
//
// History:
// 7.19 — Automatic true-black (user request, "Texe-rings"): the v7.17
// trueblack tail is no longer opt-in. Root cause of the smear
// the user saw: the persistence fade multiplies the framebuffer
// by persist in 8-bit steps, and round-to-nearest leaves fixed
// points -- at persist 0.78 every premultiplied value 1..2
// satisfies round(v*0.78) == v, so ring trails settle into a
// faint permanently-stuck wash (colored, not gray, because the
// script's channels divide by different constants and each
// channel sticks at its own quantum). No multiply-only
// compositing can fix that (proven: unsticking v=1 needs a
// multiplier < 0.5, which halves the trail every frame), so the
// fix is a subtractive -1/255/frame tail -- and the reason it
// could become automatic is a NEW implementation: shader mode 22
// in the WebGL compositor (blend-gpu.ts) clamps
// max(premult-1/255, 0) on rgb AND alpha in one pass. That is
// exact in 8-bit (black stays a true 0, unlike the Canvas2D
// 'difference' fill's 0<->1 flicker) and alpha-PRESERVING, so
// behind/clear art keeps its holes (which now heal to truly
// empty too -- stuck alpha quanta sweep like everything else).
// Engine policy: the sweep runs by default exactly in the
// sticky zone (persist >= 0.5), so low-persist scripts pay
// nothing and nothing visual changes where the multiply already
// decayed fully. The old 'difference' fill survives only as the
// no-WebGL fallback, and it stands down once a script has drawn
// with behind/clear (the alphaArt latch -- pre-latched from the
// header blend, latched at draw time on a per-frame env.blend
// switch). The trueblack: header becomes a tri-state: auto
// (default, the new behavior), on (forced at any persist < 1,
// the v7.17 flag), off (never -- the wash look, for authors who
// want it). Texe-rings itself needs no edit: at 0.78 the auto
// sweep clears each channel's quanta in 2-3 frames after the
// multiply stalls, and the background returns to pure black.
// 7.18 — "Beautiful Spectrogram" (user request, twelfth Zai-Magic
// preset): a true scrolling spectrogram as a pure script -- the
// v7.12 bgx feedback slide moves the whole framebuffer left
// every frame (the time axis) while persist 1 (v7.14.1's
// zero-fade framebuffer) keeps the history locked, so the print
// only ever leaves by scrolling off the left edge. The write
// head is a 256-point vertical line at the right edge: sv (the
// per-point spectrum) drives a megabuf attack/slow-decay level
// per frequency bin, which indexes a six-anchor palette table
// (also megabuf) -- deep dark blue background, then pink -> red
// -> orange -> yellow -> white as bins get loud. Two tricks make
// it read clean: (1) the stroke is exactly four scroll-steps
// wide with its per-frame alpha pre-divided by the accumulation
// factor (core 0.9 + halo 0.22*3.2 passes, times four overlapping
// steps ~= 1.6*4), so overlapping columns sum to exactly full
// palette strength and the quiet field renders as SOLID navy
// rather than banded; (2) a half-second erase phase (per-frame
// persist override to 0.45) drains the previous preset's
// framebuffer before the zero-fade lock engages, and a 15x
// three-second fill sweep (plus a replay on stage resize) paints
// the navy field across the whole screen instead of making the
// visitor wait 25 s for the crawl to cover it.
// 7.17 — True-black trail fade (user request, "Orange Burst"): the
// persistence fade multiplies the framebuffer by persist in
// 8-bit steps, so any persist >= 0.5 leaves fixed points
// (round(v*p) == v -- at 0.88 every value 1..4 is stuck) and long
// trails settle into a gray wash that a bg* blow-out then smears
// into every corner; scripts were forced below 0.5, where the
// trail dies in ~5 frames and the blow-out echo becomes
// unreadable. New opt-in header trueblack: on adds a subtractive
// tail right after the alpha erase: one fullscreen 'difference'
// fill of rgb(1,1,1) = an exact -1/255 per pixel per frame, which
// sweeps the stuck quantum values to a true 0 while bright
// pixels never notice (a 0.93 multiply halves them every ~9
// frames anyway). Costs one extra fullscreen fill, only when the
// flag is on; runs inside the persist < 1 branch so persist 1
// keeps its zero-fade (permanent) semantics. The tail forces
// pixels opaque (alpha composites to 1 under 'difference'), so it
// must never be combined with 'behind'/'clear' art -- documented
// in the Help panel's file-format section, off by default. Also:
// the script parser's unknown-header error now lists all nine
// valid keys (ysign had gone missing from the list).
// 7.16 — Presets tab rearranged (user request): "instead of having the
// two sections side by side ... the 'Load Preset' button section
// is on top with full width use of the preset section, the height
// set to where there is no empty space being wasted (even when a
// preset is selected). The preset tree should be placed below
// that with its width expanded to use the whole width of the
// Presets section ... so the preset names won't be clipped due to
// a restriction of horizontal space." The old 58/42 side-by-side
// split is gone: the management panel (selection header, action
// buttons, club-mode randomizer) sits ON TOP at the section's
// full width and its natural height -- never stretched, so the
// dead space that used to sit under the buttons when a preset was
// selected is gone. Its buttons pack into one compact grid row on
// desktop (Load Preset | Rename | Delete for an own preset with
// the primary Load cell wider; Load | Copy to my presets for an
// embedded one; a 2x3 grid -- Create | Subfolder | Rename over
// Delete | Export | Import -- for a user folder), all cells one
// uniform h-9; phones keep the previous stacking (primary action
// full-width, pairs below). The tree follows BELOW at full width
// and fills every bit of height the tab has left (the tab body's
// h-full flex chain; capped at 420 px only below lg, where the
// page itself scrolls and the cap keeps nested scrolling sane) --
// full-width rows mean preset names never clip against the old
// 58% column's horizontal squeeze. The club-mode scope line and
// the active RANDOM/SEQUENCE pill now share one row (truncate +
// shrink-0) instead of stacking.
// 7.15.2 — Facebook page + theme switch relocation (user request): the
// project now has its own Facebook page, and the footer's
// right-hand group carries the link (facebook.com/openvisengine)
// beside the version stamp -- an icon-only button (lucide
// Facebook glyph) that opens in a new tab, same hover/focus
// treatment as the portfolio link. The light/dark tick switch
// (v6.3) moved out of the header into the Settings tab's
// Appearance section, as a "Light / dark mode" row ABOVE the
// base-theme cards -- the header is one control lighter and all
// appearance controls now live in one place. Mechanics are
// identical: checking = the white theme, unchecking returns to
// the visitor's stored dark base (kept fresh by the base cards),
// both choices persist per visitor, and the mount guard keeps
// the SSR render mismatch-free. The row states the current side
// ("Light theme" / "Dark theme") and explains the return-to-base
// behavior in its caption.
// 7.15.1 — the silence hold (user request): "if silence is detected,
// it needs to place an immediate override and hold on the beat
// detection / prediction ... if silence is no longer detected,
// release the hold ... (the waveforms effects should still come
// through but if there is no beat, the visual should stop acting
// like there is a beat)." Sustained near-digital quiet on the
// OVERALL level (< 0.006 for 300 ms — the 3-band mean the engine
// already computes; digital silence reads exactly 0, mastered
// music stays above ~0.05 even between hits) now places a HOLD:
// nothing fires — real or predicted — the tempo estimate and
// confidence freeze exactly where they are (the old path let the
// predictor fake beats for up to ~2 s into a silence while its
// confidence quietly eroded), and beatphase freezes for scripts
// instead of saturating at "beat imminent". The hold is STICKY
// (only a decisive level above 0.015 releases — noise-floor
// wobble never does) and the release is immediate, re-anchoring
// the grid from the phase captured the frame the audio went QUIET
// (before the debounce window, so a pause/resume keeps perfect
// time — the music continues from the exact position it stopped
// at); after a hard cut the comb + first real beats re-align
// within a couple of periods. The 300 ms debounce only has to
// beat the ~800 ms the old flux-domain path needed before it
// started faking beats
// (bassGone = 1.6 periods at 120 BPM), so inter-attack dips in
// sparse masters never trip it. "Set Beat" taps release the hold
// outright — the manual override. Band/waveform data is NOT
// gated: it still flows (and is simply ~0, because the audio is
// gone). Scripts get a silent variable (1 while held) and the
// beat indicator shows an amber HOLD badge while it is active.
// BeatFrame.silent carries the state; rust-agent/src/beat.rs
// ports the same gate (its bass level doubles as the silence
// signal) with the same thresholds.
// 7.15 — the complete blend mode collection (user request): the blend
// header grew from six keywords to a 41-mode registry
// (src/lib/ava/blend.ts) covering every compositing family in the
// request list -- the full Photoshop set, the GIMP grain pair,
// the After Effects stencil/silhouette masks, Apple/CSS plus-
// lighter/plus-darker and the Pegtop exotica (negation, phoenix,
// reflect, glow). Native keywords map straight to Canvas2D
// operators; the 21 modes Canvas2D lacks run through a new WebGL
// compositor (blend-gpu.ts) with per-pixel formulas from the W3C
// compositing spec, the glsl-blend collection and GIMP (phoenix =
// min - max + 1, verified against four independent sources; hard
// mix = vivid light thresholded at 0.5). 'subtract' is now a TRUE
// clamped subtract, replacing 7.14.1's difference approximation.
// The framebuffer went alpha-capable so behind and clear work
// honestly: the persistence fade is an alpha ERASE
// (destination-out) and the bg* wipe a clearRect -- over the
// black stage both read pixel-identically to the old opaque
// fills, so every existing preset renders as before. The blend
// variable can switch the mode per frame (same contract as
// persist; it carries the mode's 1-based registry number because
// the expression language is numeric-only -- the Help table lists
// every number). Names are case/separator-insensitive
// with aliases (color burn = colorburn = ColorBurn; plus-lighter
// = additive; plus-darker = linear burn). Help panel gained a
// grouped 41-row Blend modes table; parser errors point at it.
// WebGL-less browsers fall back to the nearest native operator
// per mode.
// 7.14.1 — Script engine: blend modes + zero-fade persistence (user
// request, Brainstorm preset session). The renderer's content
// pass could only composite additively ('lighter') or plain
// alpha; the persistence header was hard-capped at 0.98, so a
// feedback blow-out ALWAYS faded -- the user wanted colors that
// ride outward forever at full brightness, replaced at the center
// by new color. Blend header extended to six values: multiply,
// screen, subtract (canvas 'difference' |dst-src| -- Canvas2D has
// no clamped true-subtract op; difference is the classic
// approximation and can never climb to white) and replace
// (source-over with the CORE pass at FULL alpha -- fresh strokes
// overwrite the pixels under them instead of stacking, so a
// figure redrawn over its own accumulating trails keeps its color
// instead of saturating to white; halo pass 0.25, dots core
// alpha 1.0, solid fill alpha 1.0). persist header + env override
// widened 0..0.98 -> 0..1; at exactly 1 the fade fill is SKIPPED
// entirely (engine.ts). The pairing this was asked for: blend
// replace + persist 1 + the v7.12 bgzoom/bgrot feedback = a
// static-speed blow-out that never fades, the fresh drawing
// replacing the old color at the center -- the blow-out itself is
// the cleanup. Help panel + parser error strings updated; no
// library changes (existing presets keep additive/alpha and their
// behavior byte-for-byte).
// 7.14 — Arctic Rainbow re-imagined (user request): the old stand-in was
// plain white columns of rain dots sweeping upward. Rebuilt as a
// music-driven water spray, stateless projectile physics in the
// [point] program: each of up to 120 droplets is a deterministic
// parabola (own apex height 1.26..1.88, own flight time 1.9..3.4 s,
// phase-staggered clock; every splash cycle re-rolls its origin,
// drift and arch) -- launch from below the bottom edge, slow rise,
// hang, arch left or right, fall back off-screen. Drop count
// follows the music (vol_att/bass_att envelope, fast attack 6/s,
// slow release 1.2/s: silence = 0 drops, loud = all 120). Each
// drop owns a hue that shifts by its own per-drop amount on every
// detected beat, plus a subtle gradient along its tail. The tail
// is 12 explicit trail samples (0.014..0.44 s back along the arc)
// drawn as a tapered polyline in lines mode, gated by smoothstep
// on the fall speed: nothing while rising (crisp dot), fading in
// as the arch turns downward -- no persistence smear (persist
// 0.12). Slot map through cell centers (the v7.13.1 lesson), 14
// slots per drop (separator + head + 12 trail), n = 1680.
// EMBEDDED_SEED_VERSION 7.13.1 -> 7.14 re-seeds the library.
// 7.13.1 — Beat Matrix endpoint fix (user report): the rightmost column
// of dots sat one row short -- its bottom dot missing -- while a
// phantom dot spilled one column past the wall's right edge at
// the top. Root cause: the engine's point position i spans 0..1
// INCLUSIVE (classic AVS Superscope convention, engine.ts: i =
// k/(n-1)), but Beat Matrix's grid math assumed exclusive i = k/n:
// floor(i*cols) turned the last point into col 16 (one past the
// 16-column wall) at row 0, and column 15 was left with nine
// dots (rows 0-8, no bottom). Fix: the script now maps through
// cell centers -- cell = i*(cols*rows-1)+0.5, col =
// floor(cell/rows), row = floor(cell-col*rows) -- which puts
// every floor() half a cell away from an integer boundary, so
// the endpoint can never spill and float dust can never shuffle
// a dot. Full 16x10 bijection verified headlessly (all 160
// points in 160 distinct cells, col<=15, row<=9, bottom-right
// cell present). Sweep of the other 100 presets: every other
// floor(i*M) use is benign -- angular presets close their loop
// (bar=bars lands exactly on bar 0's angle), Aurora Veil's
// phantom lands off-screen (x=1.19), the megabuf particle
// presets draw their phantom slot at alpha 0, and the converted
// Winamp classics rendered identically under original AVS
// (which also uses inclusive i). EMBEDDED_SEED_VERSION 7.12 ->
// 7.13.1 re-seeds the library so every visitor gets the fixed
// Beat Matrix.
// 7.13 — one build, every feature: the parallel development lines are
// merged. This tree now carries BOTH the community OVE-Directory
// (v7.11 here) AND the parallel line's additions — the spectrum
// 16 kHz cap (its 7.10.1), the screen wake lock + respectful
// Demo button + one-click Save (its 7.11, folded into this
// history as 7.10.2 to keep one clean timeline), and the
// AVS-style background movement family (bgrot / bgx / bgy /
// bgzoom) with the eleventh Zai-Magic preset "osc-rotations"
// (its 7.12). Git three-way merge against the shared v7.10
// ancestor (identical tree hashes verified on both sides); the
// only hand-merged surface was the Script tab toolbar, which
// now carries the one-click Save and "Upload to OVE-Directory"
// side by side, and the two changelogs (here + release-notes).
// 7.12 — background movement + osc-rotations. Scripts can now move the
// whole framebuffer every frame, the way the classic Winamp AVS
// plugins did: bgrot (radians/frame, + clockwise on screen),
// bgx / bgy (per-frame drift in the -1..1 screen convention, y
// up), bgzoom (< 1 draw-in, > 1 blow-out) -- read after [frame]/
// [beat] so this frame's values apply this frame. The engine
// snapshots the canvas into an offscreen buffer, wipes, and
// draws the snapshot back through the transform, so the
// persistence fade blends the moving trails: "speed accelerates
// to the beat, a hard hit flips the spin, fade to black stays
// slow" is pure script (a beatage envelope drives the rate; a
// [beat] section flips a dir variable on hard hits via bass vs
// bass_att). Neutral readings (0/0/0/1) skip the pass entirely
// (zero cost for every preset that doesn't move); non-finite
// values fall back to neutral (a NaN in a canvas transform
// matrix would black the screen with no error); per-frame
// clamps: rot ±0.6 rad, drift ±0.25, zoom 0.5..2. New 11th
// Zai-Magic preset "osc-rotations" (user-requested): a
// stationary centered oscilloscope (time-domain v) whose color
// is mirror-symmetric -- points equidistant left/right of
// center shift together, the center points hold their own hue
// -- over a background rotating cw/ccw to the music that
// temporarily accelerates on the beat; hard hits
// (bass > 1.25 × bass_att) flip the spin; a whisper of draw-in
// (bgzoom ~0.999) spirals the trails inward; persist 0.965
// keeps the fade slow so colors blend. Help panel: bg* variable
// rows + three new example formulas. EMBEDDED_SEED_VERSION
// 7.10 -> 7.12 (library re-seed adds the preset; the share
// codec's dictionary stays frozen per its compatibility
// contract -- bg* identifiers ride the pair packing like any
// custom variable). Headless validation of the preset's
// compiled programs: quiet 0.004 rad/frame, hard beat 0.026
// (6.5×) + direction flip, soft beat no flip, mirrored points
// share hue / center distinct
// 7.11 — the community OVE-Directory. A sixth tab (Directory) lists every
// script published to the community directory — a separate VPS at
// s3374dir.openvisengine.com, reached ONLY through this server's
// backend proxy (/api/ove-directory/*), which holds the API key in
// env and re-validates every upload (the share string must decode
// AND compile). Publishing: the Script tab gains "Upload to
// OVE-Directory" (enabled only while the editor shows ✔ Compiled OK
// with no runtime error); the script travels as its Script⇢Hash
// string, so the directory stores a fraction of the source. The
// listing carries each entry's name, type and date, a 1–5 star
// widget (the voter id is an HttpOnly cookie minted by the proxy;
// one vote per script per visitor, enforced upstream), the live
// "Average: #.#" to the right of the stars, and sorting by Name /
// Type / Date / Rating (done upstream, both directions). "Get"
// fetches the full record, decodes the hash back into the exact
// script, files it in this browser's preset tree under
// root/user created/OVE-Directory (content-deduped, names numbered
// "(1) (2) (3)…" on collision) and offers "Open in editor".
// Duplicate uploads resolve as success (409 DUPLICATE_HASH carries
// the existing row); duplicate votes show the stored aggregates.
// 7.10.2 — wake lock, a respectful Demo button, one-click save. While any
// audio source is live the site holds a Screen Wake Lock so phone
// and tablet screens stay on (released the moment the engine goes
// idle so the normal inactivity dimming returns; browsers silently
// drop the sentinel on hide, so a visibilitychange watcher
// re-acquires it on return while music is still going;
// unsupported browsers degrade silently). The Demo Track button no
// longer force-engages the Sequence checkbox: a visitor who loaded
// a preset (tree double-click / Load / Copy to my presets / created
// one), brought their own script material into the editor (typed,
// pasted, Hash⇢Script, file), saved their own default startup
// script, or runs their own Random/Sequence mix keeps their
// selection -- Demo just plays the track (the showcase's tree
// re-scope, 20 s interval and retire-on-end all skip too; a new
// per-visit manualChoice flag + a boot-script snapshot decide, and
// ensureDefaultPreset gained the same guard so mic/file/link never
// clobber a custom draft either; the showcase now only starts for
// a visit that never picked or typed anything). Script tab: a valid
// script shows the Save button with NO save target needed -- one
// click creates a preset under "user created", auto-named from the
// script's name: header (a hash import of "Spectrum Bars:[▼…]"
// saves as "Spectrum Bars"), "Empty Name" when nameless, " (1)"/
// " (2)"… suffixes on conflicts, and whenever the name had to be
// invented a "Give This Preset a Name" dialog opens to rename the
// just-saved preset in one step (the preset is always saved FIRST,
// renamed second, so nothing is ever lost to a naming problem).
// Hash⇢Script imports clear the stale save target (the editor now
// holds foreign content) and pass the decoded label through as the
// save-name fallback when the script has no name: header
// 7.10.1 — the frozen right edge of spectrum visuals. getspec()/sv mapped
// the bar field across the whole FFT (bin 2 ... Nyquist, ~12 Hz to
// 24 kHz), but music carries almost nothing above ~16 kHz and every
// lossy source low-passes even lower (192 kbps MP3 at ~18.6 kHz,
// YouTube AAC at ~15.5 kHz; measured across the demo collection:
// 0.000% of total magnitude above 18.6 kHz), so the rightmost ~20%
// of every spectrum visual read a permanent zero -- bars that never
// moved. getSpectrum() now tops the field out at 16 kHz
// (SPEC_TOP_HZ, matching the spectral-flux cap in getBands()), so
// the right edge lands on the 12-16 kHz "air" band where cymbals,
// hats and synth sparkle live. rust-agent's spectrum_at() mirrors
// the change, and the help panel's sv entry now states the span.
// 7.10 — ten demo tracks + first-person changelog + declutter. The demo
// source is now a COLLECTION: ten mastered tracks in
// public/demo-tracks/ (demo-01.mp3 … demo-10.mp3, filename order
// 1→10, replacing the single Neural Mecha Pulse file). Each Demo
// click plays the visitor's PLACE in the collection once through;
// the place advances when a pass truly ends — natural finish or
// the Stop button ('ended'/'stop' teardown reasons, a new
// onDemoEnded signature) — and wraps 10→1; switching to another
// source keeps the place. The place persists per visitor in
// localStorage (ove.demoTrack.next), so returning picks up where
// they left off. The engine caches one decoded buffer at a time,
// keyed by track index (~50–60 MB PCM each — all ten would be half
// a gigabyte). The header chip + footer SRC label show the current
// track's name (Demo Track N). RELEASE_NOTES rewritten in the
// operator's first-person voice (no more third-person marketing
// copy or addressing the visitor), and every em dash in the
// interface replaced with a plain double minus ( -- ) — including
// the embedded preset library's descriptions and script comments
// (EMBEDDED_SEED_VERSION 7.01 → 7.10 re-seed). Declutter: the
// landing menu's source list line, the presets panel's two
// read-only paragraphs, the Random/Sequence hint line, the empty
// user-folder note reworded, the Settings theme/highlight/Native
// paragraphs, the link dialog's privacy footnote and the footer's
// engine-ok dot are all gone — compile errors still toast, so the
// always-green badge was noise
// 7.9 — footer credit + What's New polish. The footer's blank middle now
// carries the maker's signature: "© {year} · Site Created &
// Deployed by Sum'ting White" (year generated from the clock,
// hydration-guarded) with a "View my portfolio at zcreations.dev"
// link that opens zcreations.dev in a new tab — centered in the
// blank area from xl (mx-auto between the stats and the version
// stamp), dropping to its own centered row on narrower screens so
// nothing ever crowds. The What's New dialog got a typographic
// tidy-up: a hairline divider above every release box
// (border-zinc-800 — a ramp step, so it matches every base theme
// through the globals.css remaps), and each change row became an
// invisible two-column grid — the kind tag top-right justified
// against the column boundary, the description top-left with word
// wrap — so all descriptions start on the same pixel column
// 7.8 — share strings 2.0: ~72% shorter than the script (was ~49%). The
// base-62 idea, cranked to 11,172… 32,164: after the symbol
// dictionary (which grew ~170 data-mined entries — every function
// WITH its open paren, every engine variable, the AVS idioms
// "hue = hue + dt * " and "t = t + dt * ", " * 0.", ") * ",
// blank-line+section skeletons, "linewidth: " / "blend: additive"
// headers, ~70 number literals, and the library's common
// variable names incl. chroma) the whole body is read as ONE BIG
// NUMBER and rewritten in base 32,164 — one digit per character
// from the CJK + Hangul blocks, ~15 bits a character vs base-62's
// 5.95 (so "1400" costs ~0.85 of a character). The decoder gets
// the body's alphabet back from a ~32-character bitmap header
// instead of ~80 literal symbols. Short scripts where the header
// wouldn't pay keep the v7.7 symbol form (encoder picks whichever
// is shorter); ▲ strings from v7.7 still decode; still lossless
// for ANY text, still copy-safe, still self-checking. Corpus
// measure: median share string is 27% of the script's length
// (best 22%), e.g. Bass Bloom 1341 → 326 characters
// 7.7 — script sharing + settings declutter. Script⇢Hash / Hash⇢Script:
// two buttons in the Script tab turn the editor's script into ONE
// short shareable Unicode string ("Bass Bloom:[▲…①①①①①①①①]") and
// paste one back — the script returns exactly as written (lossless
// for ANY text, compiling NOT required, so WIP scripts can be
// shared for debugging). The string is built from built-in symbol
// references (♤ = line return, ♡ = beats, ♠ = beatage, ♦ = space,
// ♩♪♫♬ = the four sections, one symbol per function/variable, plus
// a pair-packing layer that folds leftover identifier/number text
// two chars per CJK code point) — ~50% shorter than the script on
// real presets. Copy-safe by construction (no raw whitespace or
// control chars; platform-injected variation selectors / zero-
// widths stripped on paste) and self-checking: 8 check digits (an
// FNV-1a digest rendered as ①–⑯) reject strings that were cut off
// by a character limit or altered in transit, instead of decoding
// into garbage. Also: the Music Services log in / "log out of all"
// section in Settings is parked (MUSIC_SERVICES_UI flag) — the
// built-in house account already powers Stream-a-Link for
// everyone; the section returns reworked in a later update
// 7.6.1 — header declutter: the Set Beat button is gone from the header
// (tap-tempo lives in the Beat tab, and the Space shortcut works
// anywhere — both unchanged). The beat indicator dot stays.
// 7.6 — "What's New!" dialog: a Sparkles button in the header (left of
// the theme switch) opens the visitor-facing changelog — every
// release from v4.0 on, newest first, each with one-line Added /
// Improved / Fixed / Removed notes (src/lib/release-notes.ts,
// color-coded tags, the running version badged "current",
// scrollable with the thin scrollbar). A small emerald dot on the
// button marks updates the visitor hasn't seen yet — per visitor
// in localStorage, cleared on open (mount-guarded, no hydration
// mismatch). Standing rule added: version bumps ship with their
// release note; a dev-mode guard warns when they drift
// 7.5.1 — Link dialog width fix: text no longer escapes the dialog box.
// Root cause: Radix dialogs lay their children in an implicit CSS
// grid whose single column sizes to the widest child's min-content;
// the RECENT LINKS rows carry truncate spans (white-space: nowrap),
// and Chromium measures a nowrap flex item's full text width for
// that measurement — so a long saved title (e.g. "Bobby Foxx — This
// Is Why We Can't Have Nice Things (Official Music Video)") blew
// the column ~40 px past the 448 px dialog, and EVERY child (title,
// description, input + Load, rows, footer) laid out beyond the
// right border. Fix: the shared Dialog/AlertDialog content now pins
// the column to minmax(0,1fr) — capped at the available width, so
// no nowrap content can ever stretch a dialog again (covers every
// dialog app-wide) — the Link dialog widened to max-w-lg for
// comfier wrapping, and min-w-0 + break-words added on the
// recents/playlist containers and error text as belt-and-braces
// 7.5 — yt-dlp compatibility: single tracks resolve by DOWNLOAD + CONVERT
// instead of proxying the extractor's stream URL. The old stream
// URLs were IP-locked (googlevideo bakes in the resolver's IP),
// expired within hours, and arrived as webm/opus — a container
// Safari & co. refuse ("The media resource … was not suitable").
// The server now runs the operator's pipeline per track:
// yt-dlp -f bestaudio/best -x --audio-format mp3
// --audio-quality 0 -o "<dir>/%(title)s.%(ext)s" URL
// — audio-only download, universal MP3 at VBR-0 (~245 kbps), the
// video's real title preserved for the Playing: label. The
// finished file streams same-origin with full Range support from
// a per-track workspace dir (30-min sliding TTL + orphan sweeper);
// /api/audio/stream gained a file-serving mode. Hardening around
// it: shared failure classifier for both yt-dlp passes (metadata +
// download), 180 s download budget, 300 MB cap, bot-wall/stale-
// extractor answers unchanged, ffmpeg-missing degrades to the old
// proxy path (logged once), queue look-ahead lead 15 s → 60 s so
// playlist transitions don't stall behind conversions
// 7.4 — Sequence mode: a second mix checkbox next to Random. Ticked, it
// cycles the scope folder's presets IN ORDER (the tree's exact
// top-to-bottom order) instead of shuffling — and when it moves
// past the last preset it wraps back to the first, so the folder
// loops forever. Random and Sequence are mutually exclusive
// (ticking either one unticks the other; both share the same
// delay and folder scope), Random now never loads the same preset
// twice in a row, and Sequence holds its breath through silence
// exactly like Random does. The Demo showcase now runs SEQUENCE —
// the one play-through walks every Zai-Magic preset in order
// (20 s each) rather than repeating random picks, and when the
// demo stops, whichever mode it checked is unchecked again
// 7.3 — demo-flow refinements: the Demo Track is a single pass — it plays
// "Neural Mecha Pulse" once through and stops (a demo, not a radio
// station). However it ends (natural finish, Stop, or switching to
// another source) the showcase unwinds: the randomizer the Demo
// flow force-checked is unchecked again, and the visitor lands
// back on the "Select an audio source" overlay. Preset-change
// popups are gone ("Preset loaded" / "Random preset" / "Default
// preset running") — the footer's SCRIPT: label already echoes
// every change, so the toasts were pure intrusion; only compile
// errors still toast. Club-mode randomizer now respects silence:
// when no music is playing it never re-shuffles the selected
// preset, even with the checkbox on — it waits for the next track
// 7.2.1 — Supernova (Zai-Magic) preset fix: the particle kill condition
// (|px|+|py| > 2.4) was physically unreachable — drag caps a
// particle's total travel at vel/1.6 ≈ 1.0, so nothing ever
// crossed 1.4, nothing ever died, and since respawning requires a
// dead particle, the per-beat "budget = n" detonation could never
// fire after the initial one — the preset exploded once on load,
// then faded to permanent black. Now every beat force-clears the
// whole 620-particle field in [beat] (guaranteed full re-detonation
// on the very same frame), fully-cooled particles die by fade so
// long beat gaps leave a gentle ember drizzle instead of black,
// and each blast re-randomizes its geometry (no two explosions
// alike). Embedded library re-seeded (seed marker 7.01)
// 7.2 — new-visitor showcase flow. Defaults for first-time visitors:
// the Black + Purple Outline theme with the Blue highlight
// (previously royal purple + yellow), and Zai-Magic "Bass Bloom"
// as the startup preset (previously the first Synth-C classic).
// The idle overlay's Demo Track button now carries an animated
// reflective shimmer (skewed light band sweeping left→right every
// 3.2 s, reduced-motion safe) to draw the click — and that one
// click starts the full showcase: the demo track plays while the
// preset tree selects + expands Zai-Magic, randomize switches on
// scoped to that folder with a 20-second change interval. Music
// started with NO preset active (own draft running / selection
// cleared) auto-loads Bass Bloom. Preset changes — manual load,
// the random mix, the Demo flow — no longer hard-cut: the engine
// snapshots the outgoing frame and blur-dissolves it into the new
// preset over 700 ms (progressive blur + easeOutCubic fade + a
// slight scale-up so the softening edges never gap; plain
// crossfade where ctx.filter is unsupported)
// 7.1 — real demo track + a beat detector that survives real music.
// The Demo button now plays "Neural Mecha Pulse" (the operator's
// mastered industrial-EDM banger, public/demo-track.mp3, loops
// gaplessly) instead of the synthetic techno pattern — a real
// recording is the honest test: deep sub for the bass bands,
// driving mids, crisp highs, and a kick buried in sustained
// sub-bass. That last part exposed the 6.x detector as fundamentally
// broken on mastered music: a LEVEL threshold can never see the
// kick through the sub (peak-to-mean caps at ~1.2× → sporadic
// fires, phantom 120-BPM locks from the minInterval floor).
// v7.1 rewrites detection the way real beat trackers work
// (aubio/librosa-style): a full-band spectral-flux ONSET envelope
// → autocorrelation over 300–1000 ms lags with a log-BPM prior →
// comb-filter phase with grid hysteresis → beats fired on an
// absolute grid kept glued by a PLL pull. Headlessly validated
// against the actual track PCM (AnalyserNode chain replicated
// frame-for-frame): locks the true ~133 BPM (the song drifts
// 130.7→133.1 — the grid TRACKS it) in 1.9 s, 92% beat-gap
// regularity, confidence 0.99 through the body; techno/hip-hop
// patterns lock exactly; silence yields zero beats. Known limit:
// sparse kick-vs-snare backbeats (dnb) may lock half-tempo —
// stable, and two taps of Set Beat correct it. Visitor-stored
// beat configs migrate (floorLevel changed units, level → flux).
// The demo BPM slider is gone (a real track's tempo is detected,
// not dialed in) — replaced by a track-name chip in the header
// 7.00 — Zai-Magic: ten hand-authored presets in root/embedded/Zai-Magic
// built around BEAT-SYNCED COLOR — the color wheel turns on the
// kick, palettes swap per beat / per 4-beat bar, megabuf particles
// remember the hue of the beat that spawned them + new script
// tools: hsvr/hsvg/hsvb (HSV→RGB channels), smoothstep and the
// beatage kick-envelope variable — all documented in the help
// panel. (Numbered 7.00 rather than 6.10 — a new preset family
// plus new script-language features is a major release; 6.9.3 was
// the last patch of the 6.x line.) Beat engine fixes underneath so
// the Demo track actually
// locks tempo (was sporadic): demo synth remixed for detection
// (hot kick owning the 20–100 Hz band, off-beat bassline voiced
// C3+ with a sidechain pump — chronological envelope events!),
// and the detector's default threshold 1.4 → 1.2 (the 170 ms FFT
// window caps a clean kick's peak-to-mean at ~1.5×, so 1.4 fired
// only sporadically and the BPM never locked)
// 6.9.3 — canonical domain fix: metadataBase/og:url/og:image pointed at
// openvisualengine.com (a DEAD domain baked in by 6.9.1) while the
// real site is openvisengine.com — Facebook's "corrupted image"
// error was its crawler fetching the image from a host that serves
// nothing; all share URLs now use the correct short domain
// 6.9.2 — share-image fix: og:image re-encoded as BASELINE JPEG (the
// 6.9.1 progressive encoding rendered fine in every browser but
// Facebook's Sharing Debugger rejected it as "corrupted"), swapped
// in the title-card artwork (gradient wordmark over three
// visualizations) and added a ?v=2 cache-bust so crawlers fetch it
// 6.9.1 — social share preview: Open Graph + Twitter Card metadata with
// a 1200×630 screenshot of the app (public/og-image.jpg) — sharing
// openvisualengine.com on Facebook/X/WhatsApp/Discord now shows the
// big image card instead of a bare link
// 6.9 — house account: anonymous visitors play YouTube links through
// the operator's shared disposable-account cookie (server-side
// fallback under secrets/, never exposed) — YouTube bot walls stop
// applying to guests; visitor's own login always wins. Engine fix
// underneath: Deno runtime shipped in bin/ (YouTube requires a JS
// runtime for signature/n-challenge solving on authenticated
// sessions — without it, logged-in extractions return no formats)
// 6.8.2 — login friction cut to ~4 clicks: the exported cookies.txt /
// Cookie-Editor JSON file can be dragged straight onto the
// login box (or picked via “Import the exported file…”) — no
// opening the file, no copy/paste; “Log out” / sign-out
// revocation note added (the official Google OAuth flow can't
// power the site: Google's APIs never hand out stream URLs and
// restricted-scope verification would reject an audio player)
// 6.8.1 — cookie login made foolproof: the login dialog recognizes raw
// Cookie headers, cookies.txt exports AND Cookie-Editor JSON,
// validates the paste LIVE through one shared parser (a single
// cookie's value — like a bare LOGIN_INFO string — gets an honest
// "this will not log you in" warning), and the how-to steps lead
// with the one-click extension export for Chrome AND Firefox
// 6.8 — self-healing audio engine: when YouTube changes their mechanics
// the server detects the stale extractor, updates yt-dlp in the
// background (verified GitHub release download, atomic swap) and the
// player retries seamlessly behind an "Updating in Background"
// notice; bot walls answer "Google login required…" with an Open
// Settings action, and unfixable failures get the honest
// "A back-end problem has occurred. Please try again later."
// 6.7 — lazy playlist queue: a playlist link becomes a queue of URLs that
// streams ONE song at a time (each track is resolved by yt-dlp only
// right before it plays — next-track look-ahead at ≤15 s to go),
// queue bar with skip/jump/replay + server-side caps (max 2 parallel
// extractions); music-service framework: Spotify + Apple Music are
// registered providers whose playback ships in a later update
// (logins already save, links answer with an honest notice)
// 6.6 — music service logins (Settings tab): YouTube / YouTube Music,
// SoundCloud, Spotify, Apple Music cookie login, encrypted on the
// visitor's computer; Link button gains playlist picking + uses the
// login to bypass bot walls
// 6.5 — Settings tab (replaces the parked Native tab): theme picker with
// base chrome (Royal Purple / Matte Black / Black + Purple Outline /
// Light) × selectable highlight color (Purple, Yellow, Blue, Green,
// Red, Orange), all per-visitor; simple OVE logo as the site favicon
// 6.4 — beat detection suspends when no music is playing/loaded (no ghost
// beats after Stop, BPM resets) + dark theme restyle: royal purple
// chrome with light-yellow highlights
// 6.3 — light/dark theme tick switch in the header (white theme via .light
// palette remap; per-visitor, persisted by next-themes)
// 6.2 — security hardening: read-only embedded library (server refuses all
// writes) + per-visitor "user created" presets stored in the browser
// 6.1 — preset library quality pass: every embedded stand-in is now a UNIQUE
// generated visual (16 families × seeded parameters), approx badge
// 6.0 — renamed to "Open Visualization Engine" + footer version stamp
// 5.0 — folder-tree Presets page (Task 9–11 state, restored in Task 12)
// 4.0 — link audio source (yt-dlp resolve + stream proxy)
export const APP_NAME = 'Open Visualization Engine'
export const APP_SHORT = 'OVE'
export const APP_VERSION = '7.19.0'