PURPOSE

Playground tab UI for the three-layer post-FX pipeline. Surfaces curated full-game art-style presets, optional layer FX (BG stars, color sparkles, shooting stars), and a final-pass polish stage (blur, HSL, anim speed). All controls are sliders/toggles that map directly to entries in the post-FX store; changes apply to every nebula-rendered pixel via the FS1/FS2 fragment shaders.

OWNS

  • Default export FxTab — the tab component rendered inside a Panel titled “POST-FX”.
  • Internal helpers SliderRow and CheckboxRow for the two control row variants.
  • SliderEntry interface plus the ART_STYLES and POLISH_KNOBS arrays — the curated tables that drive the Art Styles and Polish sections.
  • Local style constants (ROW_STYLE, LABEL_STYLE, VALUE_STYLE, DESC_STYLE, SECTION_HEADER_STYLE).
  • Local UI state layerOpen (the Layer FX foldout open/closed flag) via useState.
  • Per-slider visible-to-internal value mapping: visible range 0..100 integer, stored as 0..1 float.

READS FROM

  • usePostFxState hook — subscribes to the full PostFxState object from engine/rendering/post-fx-store. Each row reads the matching key off state (e.g. state.borderlands, state.polishBlur, state.layerBgStarsOn, state.layerShootingStarsAngle).
  • getPostFxIdentity — fetches the per-key identity/reset value used for polish knobs and the shooting-stars angle reset target.
  • PostFxState type — constrains SliderEntry.key so only valid store keys appear in the curated tables.
  • Panel from ./PlaygroundShared for the outer collapsible container shell.

PUSHES TO

  • setPostFxValue(key, value) — single mutation entry point for every slider, toggle, and reset button. Used to:
    • Drive art-style slider values (0..1) and reset them to 0.
    • Toggle layerBgStarsOn / layerColorSparklesOn / layerShootingStarsOn between 0 and 1 using a >= 0.5 threshold.
    • Drive each layer’s intensity/density/angle/variance sliders and reset them (intensity-style knobs reset to 0.5, variance to 0, angle to its identity).
    • Drive each polish knob and reset it to its getPostFxIdentity value.

DOES NOT

  • Does not own or mutate post-FX defaults — the store owns identity values and the schema.
  • Does not render any nebula, sparkle, or shader output itself; it only edits store state that downstream shaders read.
  • Does not persist preset state outside the store (no localStorage, no URL params, no telemetry).
  • Does not gate on layer toggles in any way other than visually — toggling a layer just writes 0/1 to the store.
  • Does not handle keyboard input, drag interactions, or touch-specific affordances beyond the native <input type="range"> element.
  • Does not import or read any other tabs; rendering of the FX tab elsewhere is the playground shell’s responsibility.

Signals

  • Layer foldout caret swaps between (open) and (closed) based on layerOpen.
  • Layer toggle buttons render ☑ ON (amber #fbbf24 background, dark text) when active, ☐ OFF (translucent white background, dim text) when inactive. Active state is read as state.layer<X>On >= 0.5.
  • Each slider displays a right-aligned 0..100 integer readout in amber monospace.
  • Reset button () appears whenever a row passes an onReset callback; its title attribute shows the integer reset target derived from identityValue.
  • Section dividers come from SECTION_HEADER_STYLE (uppercase letterspaced label with a top border): “Art Styles”, “Layer FX”, “Polish”.
  • Per-style descriptions render in a dim monospace block immediately under the slider for ART_STYLES entries and POLISH_KNOBS entries that supply desc.
  • Shooting Stars block adds an extra footnote describing how Variance interpolates between parallel rain (0) and fully random per-streak angles (1).

Entry points

  • Default export FxTab — the only entry point; mounted by the Playground screen as one of its tabs.
  • SliderRow / CheckboxRow / SliderEntry are file-local; they are not exported and have no callers outside this module.

Pattern notes

  • Curated-table pattern: art styles and polish knobs are declared as SliderEntry[] constants and rendered with .map(...); adding a new preset is a one-row edit plus a new key on PostFxState.
  • Three-section layout corresponds 1:1 to the pipeline stages described in the file header: Art Styles (preset full looks), Layer FX (additive extras, foldout), Polish (final pass).
  • Visible-vs-internal split: the UI consistently shows 0..100 while the store stores 0..1. Conversion happens at the SliderRow boundary (pct = Math.round(value * 100) for display, parseInt / 100 for write).
  • Boolean-as-float convention: layer toggles are stored as numeric 0/1 values and interpreted via >= 0.5. This keeps every store entry uniformly typed as a number even when the semantic is binary.
  • Reset semantics differ by knob type. Art-style sliders reset to 0 (no preset applied). Layer intensity/density sliders reset to 0.5. Layer variance resets to 0. Polish knobs and shooting-stars angle reset to getPostFxIdentity(key) so the store decides identity.
  • Typed cast as never is used at every setPostFxValue call site because key is a wide union over PostFxState; the call site has already constrained the value type via the slider range.
  • The outer Panel is rendered with open={true} and a no-op onToggle, i.e. the FX panel is non-collapsible within the tab — the shell-level tab switcher gates visibility instead.
  • Styling is inline-only via React.CSSProperties constants; no CSS module, no Tailwind. Accent color #fbbf24 (amber) is used consistently for active state, readout, and slider thumb.