# Retro effects: choose the cause, not a pile of filters

This is a portable illustration experiment guide. Its recipes are deliberately approximate, not a standards-compliant VHS, NTSC or CRT simulator. Parameter ranges below are proposed art-direction limits for a small Canvas2D lab, not measured hardware characteristics or upstream defaults. Read [style-selection.md](style-selection.md) before choosing an effect; brand-specific adoption belongs in an adapter.

## Three different mechanisms

| Family | What the reference actually describes | Useful visual cue | Common false equivalence |
|---|---|---|---|
| Tape/playback | Head switching, RF dropout, tracking and timing errors during tape playback.[12][13][14] | Local horizontal disturbance; chroma softer than structure; occasional missing line segments | VHS is not a magenta tint, film scratches or a CRT mask. A clean tape need not look destroyed. |
| Display | CRT-Royale separates beam profile, phosphor-mask type/pitch, convergence, curvature, bloom and interlace options.[5] | Structured raster brightness and restrained light spread | Scanlines are not tape damage. RGB grille, slot mask and shadow mask are alternatives, not three overlays to stack. |
| Digital processing/failure | AVAA distinguishes block noise and DV block-boundary discontinuities from analog errors.[26][27] | Grid-aligned discontinuities, held/repeated blocks, selective quantization | Moving RGB copies are a graphic glitch convention, not a general model of codec corruption. |

A horizontal tear is not enough to identify its cause. AVAA documents timing errors as flagwaving, skew and horizontal tearing; establish a plausible source/playback chain before naming a defect.[13] The inspected AVAA diagram depicts top-edge bending, bottom-edge skew and separated horizontal strips; that static drawing does not prove temporal behavior. The inspected butterfly still has a narrow disturbance along the bottom, not rainbow noise across the whole picture.[12][13]

## Signal chain and coordinate ownership

Use this **conceptual ordering**, synthesized from the preservation references and implementation structure, rather than treating it as a circuit schematic:

1. **Clean source:** original drawing, camera picture or game frame. Author its silhouette, light and palette first. Preserve an untouched master.
2. **Recording/channel:** if a tape story is selected, introduce bandwidth loss and recording/playback defects here. A source timestamp recorded into the picture belongs before those defects.
3. **Playback/decode:** recover the picture, including any timing correction and chroma/luma separation. A player's newly generated on-screen display can be added after tape defects. It is not automatically the same as a camera timestamp.
4. **Optional digital stage:** a digitized capture may acquire compression artifacts. A digital-native source may start here and skip tape entirely. The exact position depends on the stated story.
5. **Display:** apply the chosen display's beam/mask/optical approximation to the resulting picture. A CRT's screen pattern should not be dragged sideways by the simulated tape rows.
6. **Host UI:** captions, real controls, focus rings and page chrome stay outside all these stages.

ntsc-rs is an implementation reference, not this lab's dependency. Its inspected `apply_effect_to_yiq_field` filters input luma/chroma, combines chroma into luma, applies composite disturbances including head switching/tracking, demodulates, then performs additional luma/chroma/VHS filtering. It exposes separate horizontal/vertical chroma delay, noise and edge-wave controls.[19][29] Do not advertise a short Canvas filter chain as equivalent. Its own source notes that one VHS filtering path is more smeary than real VHS.[19]

AVAA explains that a time-base corrector stores and retrieves lines against corrected timing; it also notes that head-switching noise is often hidden by overscan but visible in full-raster captures.[12][13] Therefore:

- **VHS → CRT** can be coherent. A raw full-raster capture with visible bottom switching noise is a different presentation from an overscanned television.
- **Digital illustration → clean CRT** needs neither VHS snow nor a tape date stamp.
- **CRT → tape** means a camera recorded a physical screen; that is a different scene, potentially including camera/screen interactions. Do not obtain it just by reversing two generic overlays.
- A recorder/player OSD is model-specific; ntsc-rs explicitly declines to bundle one universal OSD.[11] Prefer an original labeled annotation outside the image over a counterfeit historical UI.

### Color and scale discipline

Keep three coordinate spaces explicit: `sourcePx` for authored pixels, `effectPx` for the processing raster, and `outputPx` for exported/displayed pixels. A useful first lab uses a 320 × 180 artwork/effect raster and a 640 × 360 still export. This is a convenient 16:9 illustration, **not an NTSC sampling specification**. Do not label it 480i, interlaced or authentic VHS.

Do not confuse gamma-coded luma used in video processing with linear-light luminance used for physical light mixing. If separating color for the approximation, use one documented forward/inverse color transform as a pair; a YIQ-like split alone does not implement composite modulation/demodulation. Without that transform, call a horizontal RGB blur “color smear approximation,” not “NTSC chroma decoding.”

Filtering creates intermediate colors. Keep the strict native-grid/palette export separate from the effect-preview PNG; do not assert both have the original palette count. Apply nearest-neighbor scaling to a clean pixel-art comparison. The analog approximation may intentionally soften edges, but must say so. Handle transparent input deliberately: composite onto the chosen artwork background before signal processing, or use premultiplied-alpha-aware filtering to avoid dark halos.

## Parameter roles and bounded starting ranges

All ranges are **lab proposals**. Start at zero, compare one parameter, then commit a preset. Store units and seed with every export.

| Parameter | Proposed range | Role / implementation hint | Failure to watch |
|---|---:|---|---|
| `chromaRadius` | 0–3 effectPx | Horizontal color-only low-pass; retain more luma edge detail | Blurring all channels equally loses the intended distinction. |
| `chromaDelayX` | −2–2 effectPx | Offset color relative to luma, not whole RGB copies | Large cyan/red outlines read as graphic glitch. |
| `lumaNoise` | 0–0.015 normalized amplitude | Low-contrast seeded variation, not additive white dust everywhere | New random noise each repaint destroys reproducibility and creates flicker. |
| `rowShiftMax` | 0–2 effectPx | Smoothly correlated displacement along rows; one field shared by color planes | Independent random shift on every row gives shredded stripes, not mild timing drift. |
| `headBandHeight` | 0–4 effectPx | Bottom-local band; separate from general row waviness | A broad central band is not the ordinary bottom-switching example. |
| `headBandShift` | −4–4 effectPx | Shared displacement within that band | Do not affect caption/UI or wrap the opposite edge into the picture. |
| `dropoutSegments` | 0–3 per still | Sparse missing horizontal segments, one effectPx high | Uniform confetti is not line dropout. No automatic flashing series. |
| `scanlineDepth` | 0–0.12 multiplicative darkening | Static periodic brightness structure in the image | Black bars can overpower pixel clusters and small details. |
| `scanlinePeriod` | 3–4 outputPx in the proposed export | Demonstration period, **not** a real tube pitch | Fixed CSS spacing changes meaning with DPR/scaling; export must use the same raster logic. |
| `maskStrength` | 0–0.06 | Optional single chosen mask, only at adequate output sampling | Moiré, colored fringes and brightness loss; leave off in first lab. |
| `bloomMix` | 0–0.06 | Mix a small blurred bright-pass with the image | Do not brighten every dark area or white-clip the signal. |
| `blockSize` | 8 or 16 effectPx | Grid for an authored digital displacement/hold study | Not a claim that all codecs use these blocks. |
| `blockCount` | 0–4 per still | A few rectangular disruptions with unchanged surroundings | Uniform tiling becomes a decorative texture rather than a localized failure cue. |
| `channelOffset` | −1–1 effectPx | Optional graphic-only RGB separation | Keep disabled in the tape and clean-CRT presets. |

Upstream parameter names help explain roles, not numeric equivalence: ntsc-rs documents head-switch height/offset/horizontal shift, separate chroma low-pass, per-plane noise, and edge-wave intensity in pixels.[29] CRT-Royale's richer beam/mask/bloom controls and explicit “fake bloom” option show why one scanline overlay is only an approximation; its author also qualifies the realism of its phosphor bloom.[5]

## Four small recipes

### A. Clean first

No post-effect. Export the authored native grid and an integer nearest-neighbor enlargement. This is the comparison baseline and no-JS/unavailable-renderer fallback. If silhouette, value grouping or tiny highlights fail here, effects are not the fix.

### B. Tape softness, not ruined footage

**Purpose:** suggest a copied memory or degraded recording inside one illustration.

- Color-only horizontal blur radius 1 effectPx; chroma delay +1 effectPx. If the implementation only has RGB row copies, omit these controls until color separation exists rather than relabeling channel offsets.
- Seeded luma noise amplitude 0.008; correlated row shift capped at 1 effectPx.
- Bottom band 2 effectPx high, shift 2 effectPx; zero dropouts at first. Add one short dropout only for a separate damaged-tape comparison.
- No mask, no scanlines, no page glow and no animation by default.
- Processing shorthand: clean raster → color bandwidth/delay approximation → shared row displacement → local band/dropout → low-level noise → clamp/recombine → export. This inexpensive order is a perceptual model, not ntsc-rs's pipeline or a VCR circuit.

The cue should remain visible at normal viewing size without concealing the subject. Export a clean/effected pair with identical crop. If it reads as a broken website rather than a recording, reduce damage or change the art direction.

### C. Clean display study

**Purpose:** compare authored pixels with a restrained display treatment.

- No tape band, no row displacement, no dropout, no RGB split.
- First pass: static scanline depth 0.08, period 4 outputPx, phase fixed to the export raster. Bright-pass bloom mix 0.03 is optional.
- Leave mask, curvature, rolling bars, interlace simulation and persistence off. A “clean CRT approximation” is a more honest label than pretending these omissions reproduce a named monitor.
- If adding a phosphor mask later, choose one geometry and evaluate native output pixels at multiple DPRs. Do not stretch a tiny RGB texture arbitrarily.

### D. Digital interruption, held still

**Purpose:** explain an interrupted digital image or create one controlled graphic rupture.

- Choose 8-effectPx blocks, two affected blocks, horizontal displacement bounded to 4 effectPx. Read from an immutable source so moving one block does not contaminate subsequent reads.
- A repeated/held region must come from a real previous illustration frame if described as temporal hold; otherwise label it a same-frame copy approximation.
- Channel offset zero initially; allow 1 effectPx only as an explicitly graphic variant.
- No tape band, scanline roll or global brightness inversion. Offer “New seed” as an explicit still operation, not a timer.
- A procedural block-displacement lab is **not datamoshing**. Predictive-frame corruption requires a codec/temporal model that this lab does not implement.

## Small Canvas2D lab handoff

Keep style and signal effects independent. A synthwave scene can be clean; a functional interface can be static; neither requires tape damage.

**First implementation slice:** one authored illustration, four mutually exclusive presets (`clean`, `tape-approx`, `crt-approx`, `digital-approx`), deterministic seed, labeled intensity control, clean/effected compare, Reset, and PNG export. Default to clean and still. Do not expose every parameter before the preset demonstrates a useful difference. If implementation time is short, ship the still lab rather than an incomplete animation loop.

**Input contract:** immutable source raster; bounded finite parameters; effect mode; integer seed; output dimensions; fixed color/alpha policy. Reset restores a documented mode, seed and parameters. Invalid values are clamped or rejected visibly. Zero intensity must bypass processing and preserve pixel bytes, not merely look similar.

**Rendering contract:** keep a source buffer and separate working/output buffers; cache immutable layers. Process only the image bounds. Use one seeded generator rather than `Math.random()` per redraw. Keep controls/captions in semantic HTML. Never sample or distort the page. Export from the same completed raster shown in the preview, not from a separately reconstructed CSS overlay. A comparison seam belongs outside the exported image unless explicitly requested.

**Budget proposal:** start with the 320 × 180 processing raster, reuse buffers, and redraw only on input. Bound imported-image dimensions before allocating buffers; reject unsupported/cross-origin assets with a readable error. Prefer original same-origin artwork so a tainted canvas cannot silently break export. Any animation or larger processing raster needs measured timing and memory evidence on target devices, not a universal performance claim.

**Acceptance checks for the parent implementation:**

- Clean/zero output equals its source; nonzero presets change actual pixels. Seed + source + parameters reproduce the same PNG pixels; changing seed affects only stochastic modes.
- Tape displacement affects imagery but not the later display mask or DOM shell. In the tape preset the bottom band stays at the bottom after resizing.
- CRT has no tape noise; digital has no tape band. The preview is not merely recolored under identical effect labels.
- Export dimensions and decoded pixels match the completed preview raster. Native-grid export retains its own palette/grid contract; effect export declares softened/intermediate colors.
- Keyboard controls, focus, label/value association, reset and export errors work. Verify no-JS/Canvas-unavailable meaning and 390/700/1440 layouts; the ordinary caption remains readable.
- If animation is later added: test explicit pause, live reduced-motion changes, tab hiding, offscreen pause, reset/cancel, stale image loads and destruction. A preference change must stop pixel updates, not only set a CSS class.

## No-strobe and reduced-motion boundary

For this lab, **no strobe is a design rule**, not a claim of medical safety or automatic WCAG conformance. Omit global brightness pumping, red/white alternation, rapid snow replacement, rolling sync bars and autonomous glitch bursts. A small image is not a blanket exemption: W3C evaluates flashes and thresholds, and a pause button cannot undo a flash already presented.[7][8]

WCAG 2.3.1 is Level A and permits no more than three flashes in any one-second period or flashes below the defined thresholds; do **not** treat that maximum as a creative target.[7] SC 2.2.2 has separate rules for automatically moving content lasting over five seconds alongside other content and auto-updating information; a short duration is not an exemption from flash requirements.[8] SC 2.3.3 is Level AAA and requires that nonessential interaction-triggered motion can be disabled.[9]

Default to one composed still. Reduced motion must retain the subject, settings and comparison, remove time progression, and respond to preference changes at runtime. Do not restart on visibility return after an explicit pause. If optional playback is ever introduced, make it deliberate, bounded and interruptible, with no automatic sound. A static patterned texture may still be uncomfortable: offer Clean/Effects off, and check moiré and contrast in the actual rendered/exported image.

## Rights and evidence limits

- ntsc-rs's core manifest declares `MIT OR ISC OR Apache-2.0`; its GUI manifest declares `GPL-3.0`. These are different scopes, not a blanket permissive license for everything in the repository.[21][30]
- The inspected CRT-Royale source header grants GPL version 2 or later; its directory includes GPL v2 license text. Do not infer shader licensing from the license of the Libretro documentation or API.[23][33]
- AVAA states CC BY-NC-SA 3.0 for its work and separately says its clips may include copyrighted material used for educational fair use. That is not a commercial stock-footage grant.[28]
- This guide links to sources and writes original recipes; it does not bundle their code, masks, screenshots or film footage. If importing implementation code later, pin the exact revision and review that file and its dependencies' notices.

Primary documentation and static examples were inspected. No physical CRT/VCR, codec corruption, ntsc-rs rendering, CRT shader execution, temporal clip analysis or perceptual/accessibility certification was performed for this research. The lab contract above is implementation guidance, not an executed capability claim. Source details and bounded evidence are in [retro-sources.json](retro-sources.json).

## Sources

[5] https://docs.libretro.com/shader/crt_royale — CRT-Royale documentation
[7] https://www.w3.org/WAI/WCAG22/Understanding/three-flashes-or-below-threshold.html — WCAG 2.3.1 — Three Flashes or Below Threshold
[8] https://www.w3.org/WAI/WCAG22/Understanding/pause-stop-hide.html — WCAG 2.2.2 — Pause, Stop, Hide
[9] https://www.w3.org/WAI/WCAG22/Understanding/animation-from-interactions.html — WCAG 2.3.3 — Animation from Interactions
[11] https://ntsc.rs/docs/faq — ntsc-rs FAQ
[12] https://www.avartifactatlas.com/artifacts/head_switching_noise.html — Head Switching Noise
[13] https://www.avartifactatlas.com/artifacts/time_base_error.html — Time Base Error
[14] https://www.avartifactatlas.com/artifacts/video_dropout.html — Video Dropout
[19] https://raw.githubusercontent.com/ntsc-rs/ntsc-rs/main/crates/ntscrs/src/ntsc.rs — ntsc-rs core processing source
[21] https://raw.githubusercontent.com/ntsc-rs/ntsc-rs/main/crates/ntscrs/Cargo.toml — ntsc-rs core Cargo.toml
[23] https://raw.githubusercontent.com/libretro/slang-shaders/master/crt/shaders/crt-royale/LICENSE.TXT — CRT-Royale LICENSE.TXT
[26] https://www.avartifactatlas.com/artifacts/block_noise.html — Block Noise
[27] https://www.avartifactatlas.com/artifacts/dv_quilting.html — DV Quilting
[28] https://www.avartifactatlas.com/copyrights.html — Copyrights
[29] https://raw.githubusercontent.com/ntsc-rs/ntsc-rs/main/crates/ntscrs/src/settings/standard.rs — ntsc-rs standard effect settings
[30] https://raw.githubusercontent.com/ntsc-rs/ntsc-rs/main/crates/gui/Cargo.toml — ntsc-rs GUI Cargo.toml
[33] https://raw.githubusercontent.com/libretro/slang-shaders/master/crt/shaders/crt-royale/src/crt-royale-scanlines-horizontal-apply-mask.h — CRT-Royale scanlines apply-mask source header
