diff --git a/docs/guide_nerv_theme.md b/docs/guide_nerv_theme.md index 510967b8..05ddce99 100644 --- a/docs/guide_nerv_theme.md +++ b/docs/guide_nerv_theme.md @@ -35,20 +35,18 @@ The theme is **opt-in**, not the default. Most users will use the standard dark ### Color Palette -| Role | Hex | Usage | -|---|---|---| -| **Background** | `#000000` | Primary window background | -| **Panel** | `#0A0A0A` | Panel and group backgrounds | -| **Text Primary** | `#E0E0E0` | Body text, labels | -| **Text Secondary** | `#A0A0A0` | Subdued text, hints | -| **Accent (NERV Orange)** | `#FF6B00` | Primary action color, active state | -| **Accent (NERV Red)** | `#E50000` | Error, blocked, abort state | -| **Accent (NERV Green)** | `#00C800` | Success, completed state | -| **Accent (NERV Blue)** | `#0080FF` | Information, in-progress state | -| **Border** | `#1A1A1A` | Panel and window borders | -| **Border Active** | `#FF6B00` | Active focus indicator | +The palette constants in `src/theme_nerv.py:8-13` (computed from RGB triples via `_c()`): -The accent colors are the iconic NERV orange, red, green, and blue — visible against the black void without being saturated to the point of eye strain. +| Constant | RGB (0-255) | Hex | Usage | +|---|---|---|---| +| `BLACK` | (0, 0, 0) | `#000000` | Primary window, panel, popup, frame, title, menu, scrollbar, button, tab backgrounds | +| `STEEL` | (224, 224, 216) | `#E0E0D8` | Text, separator, scrollbar-grab-hovered, resize-grip-hovered | +| `NERV_ORANGE` | (255, 152, 48) | `#FF9830` | Border, scrollbar-grab, resize-grip, nav-cursor, border-shadow alpha=0 | +| `ALERT_RED` | (255, 72, 64) | `#FF4840` | Used as 1.0,0.0,0.0 with sin-pulsed alpha in `AlertPulsing.render` (not as a steady color in `NERV_PALETTE`) | +| `DATA_GREEN` | (80, 255, 80) | `#50FF50` | Check-mark, slider-grab-active, plot-histogram, drag-drop-target, nav-windowing-highlight | +| `WIRE_CYAN` | (32, 240, 255) | `#20F0FF` | Scrollbar-grab-active, slider-grab, plot-lines, separator-hovered, resize-grip-active | + +The full `NERV_PALETTE` dict (47 `imgui.Col_` entries, `src/theme_nerv.py:15-63`) maps each constant to the relevant imgui style color. The "header/header-hovered/header-active" colors are semi-transparent NERV orange (alphas 60, 100, 140) used for table-header and tree-node selection. ### Geometry @@ -70,118 +68,113 @@ This produces a "military hardware" feel that contrasts with consumer-app aesthe ### Status Flickering -The active tier indicator in the MMA Dashboard, and other "operational status" elements, flicker at a low frequency (default: 0.5 Hz, configurable). The flicker is implemented as a sine wave on the alpha channel: +The `StatusFlicker.get_alpha()` method in `src/theme_nerv_fx.py:67-73` returns a value in the range [0.7, 1.0] from a sine wave: ```python -alpha = 0.7 + 0.3 * math.sin(time.monotonic() * 2 * math.pi * 0.5) +return 0.85 + 0.15 * math.sin(time.time() * 20.0) ``` -This creates a subtle "tactical display" effect without being distracting. +The angular frequency is hard-coded (`20.0` rad/s ≈ **3.18 Hz**, not 0.5 Hz) and the method is currently a utility — there is no production caller wiring it into a tier indicator at the time of writing. ### CRT Scanlines -A repeating horizontal line overlay applied via the `custom_background` callback. The scanlines are drawn at 2-pixel intervals, with the scanline alpha at ~30% by default (configurable). +`CRTFilter.render(width, height)` in `src/theme_nerv_fx.py:8-65` is invoked once per frame from `theme_2.render_post_fx()` (at `src/theme_2.py:400-408`) when NERV is active. The render method: -**Implementation**: Uses the FBO pipeline described in [guide_shaders_and_window.md#1-shader-injection-strategy](guide_shaders_and_window.md#1-shader-injection-strategy). The scanline pass is a fragment shader that modulates output color based on the screen-space Y coordinate. +1. Draws 1.2px (major, every 4 lines) and 0.8px (minor, every 2 lines) horizontal black lines at alphas 0.08 / 0.04 +2. Draws 1.0px vertical black lines every 3px at alpha 0.05 (simulated aperture-grille shadow mask) +3. Draws 20 nested rounded-rectangle outlines with exponentially increasing alpha (max 0.25) for vignette/tube-curvature +4. Adds 40 small white noise pixels per frame with flickering alpha (0.01-0.04) modulated by a 60 Hz sin wave -**Performance cost**: ~1-2ms per frame on a 1920×1080 display with default settings. +The scanline alphas and noise density are **hard-coded** in the FX source — there is no config knob for them. + +**Performance cost**: ~1-2ms per frame on a 1920×1080 display with default settings (per the original 2026-05 design estimate; the new shader-less draw-list implementation in `src/theme_nerv_fx.py` may be faster). ### Alert Animations -When MMA state transitions to `"blocked"` or `"error"`, a red border pulse animation runs for ~1.5 seconds: +`AlertPulsing` in `src/theme_nerv_fx.py:75-97` is a stateful on/off border overlay: -1. At t=0, the border alpha spikes to 1.0. -2. Over 1.5 seconds, the alpha decays exponentially to 0.1. -3. The border color is the NERV red (`#E50000`). +1. `update(status: str)` at `:79-83` sets `self.active = status.lower().startswith("error")` — i.e. any AI status string that begins with `"error"` (case-insensitive) triggers the pulse. There is no MMA-specific state-machine check; the call site at `src/theme_2.py:405` passes the live `ai_status` from the controller. +2. `render(width, height)` at `:85-97` is a no-op while `not self.active`. When active, it draws a full-screen red rectangle with alpha pulsing at `0.05 + 0.15 * (sin(t·4) + 1) / 2` (so alpha oscillates 0.05 ↔ 0.20 at ~0.64 Hz, no decay). +3. The pulse persists for the entire duration that `ai_status` starts with `"error"`; it does not have a 1.5-second auto-decay. There is no `alert_pulse_duration_seconds` config. -This makes operational failures immediately visible without requiring the user to look at a specific panel. +The color is the constant `(1.0, 0.0, 0.0)` in `src/theme_nerv_fx.py:96` (pure red, not the `ALERT_RED` constant), and the thickness is 10px. --- ## Implementation -### `src/theme_nerv.py` — Base Theme +### `src/theme_nerv.py` — Base Theme (88 lines) -Defines the color palette, geometry overrides, and font selection. The application function applies these to the active ImGui style. +Defines the 6 color constants (lines 8-13), the `NERV_PALETTE` dict (lines 15-63, 47 `imgui.Col_` entries), and the `apply_nerv()` function (lines 65-87). The actual `apply_nerv()` is a tight loop over the palette dict, not a hand-written sequence of `style.colors[col] = ...` assignments: ```python def apply_nerv() -> None: style = imgui.get_style() - style.colors[imgui.Col_.window_bg] = (0.0, 0.0, 0.0, 1.0) - style.colors[imgui.Col_.text] = (0.88, 0.88, 0.88, 1.0) - # ... (all other colors per the palette table) - style.window_rounding = 0.0 - style.frame_rounding = 0.0 - style.child_rounding = 0.0 - # ... (geometry overrides) - # Apply fonts + for col_enum, rgba in NERV_PALETTE.items(): + style.set_color_(col_enum, imgui.ImVec4(*rgba)) + + # Hard Edges + style.window_rounding = 0.0 + style.child_rounding = 0.0 + style.frame_rounding = 0.0 + style.popup_rounding = 0.0 + style.scrollbar_rounding = 0.0 + style.grab_rounding = 0.0 + style.tab_rounding = 0.0 + + # Border sizes + style.window_border_size = 1.0 + style.frame_border_size = 1.0 + style.popup_border_size = 1.0 + style.child_border_size = 1.0 + style.tab_border_size = 1.0 ``` -### `src/theme_nerv_fx.py` — Visual Effects +### `src/theme_nerv_fx.py` — Visual Effects (97 lines) -Implements the per-frame effects that go beyond static styling: +Three classes, no module-level entry point: -- **Status flickering**: Sine-wave alpha modulation for active-state indicators. -- **CRT scanlines**: Custom background shader (delegated to `bg_shader.py`). -- **Alert animations**: Time-based border pulse on state transitions. +- `CRTFilter` (lines 8-65) — `__init__(self)` sets `self.enabled = True`; `render(self, width, height)` is the scanline/vignette/noise overlay. The `enabled` flag is the only runtime toggle; the GUI controls it via the `crt_enabled` parameter to `theme_2.render_post_fx()`. +- `StatusFlicker` (lines 67-73) — `get_alpha(self)` returns a hard-coded 3.18 Hz sine. Currently a utility; no production caller (see §"Status Flickering" above). +- `AlertPulsing` (lines 75-97) — `__init__(self)` sets `self.active = False`; `update(self, status)` enables the pulse if `status.lower().startswith("error")`; `render(self, width, height)` is the no-op-or-pulse draw. + +There is no `render_nerv_fx(fx_state: dict)` aggregator. The actual entry point that wires the FX layer into the GUI is `theme_2.render_post_fx(width, height, ai_status, crt_enabled)` at `src/theme_2.py:400-408`: ```python -def render_nerv_fx(fx_state: dict) -> None: - """Per-frame NERV FX. Called from the main render loop when NERV is active.""" - if not fx_state.get("fx_enabled", True): - return - render_scanlines(fx_state.get("scanline_alpha", 0.3)) - render_status_flicker(fx_state.get("flicker_rate_hz", 0.5)) - render_alert_pulse(fx_state.get("alert_state")) +def render_post_fx(width: float, height: float, ai_status: str, crt_enabled: bool) -> None: + """Updates and renders the alert and CRT filters.""" + theme_nerv_fx = _require_warmed("src.theme_nerv_fx") + alert_pulsing = theme_nerv_fx.AlertPulsing() + crt_filter = theme_nerv_fx.CRTFilter() + alert_pulsing.update(ai_status) + alert_pulsing.render(width, height) + crt_filter.enabled = crt_enabled + crt_filter.render(width, height) ``` -### `src/bg_shader.py` — Background Shader - -The custom background shader (used for scanlines) is in `src/bg_shader.py`. It uses the FBO pipeline described in the Shaders & Window guide. +Note: `theme_2.py:111` has a code comment saying "NERV FX objects (CRTFilter, AlertPulsing, StatusFlicker) are now created [in `render_post_fx`]" — this confirms the per-frame create-and-discard pattern. The previous design (FX as long-lived module-level singletons) is no longer in use. ### Activation -The NERV theme is activated via the GUI's theme picker: - -``` -User Menu → Theme → NERV -``` - -Or programmatically: +The NERV theme is activated via the GUI's theme picker (NERV is one of the 4 built-in syntax palettes selectable via the multi-theme TOML system). The `apply_nerv()` call is wired into the theme-switch path of `theme_2.py`. To switch programmatically: ```python from src.theme_nerv import apply_nerv apply_nerv() ``` -When activated, the active color set, geometry style, and font are swapped. The FX layer is enabled/disabled via the `[nerv].fx_enabled` config flag (default: enabled). +The FX layer's `CRTFilter.enabled` is set per-frame by the caller of `theme_2.render_post_fx(crt_enabled=...)` — there is no `[nerv].fx_enabled` config key (see §"Configuration" below for why). --- ## Configuration -`config.toml`: +**There is no `[nerv]` config section in `config.toml`.** As of 2026-06-10, the NERV FX layer's parameters (scanline alpha, flicker frequency, alert pulse color, alert pulse duration) are all hard-coded in `src/theme_nerv_fx.py` and have no TOML or env-var override. The only runtime toggles are: -```toml -[nerv] -fx_enabled = true # Master toggle for all FX -scanline_alpha = 0.3 # 0.0 (none) to 1.0 (opaque) -flicker_rate_hz = 0.5 # Flicker frequency for active-state indicators -alert_pulse_duration_seconds = 1.5 # How long the red border pulse runs -alert_pulse_color = "#E50000" # Hex color for the alert pulse -``` +- **`CRTFilter.enabled`** — set per-frame by the caller of `theme_2.render_post_fx(crt_enabled=...)`; the GUI uses the NERV theme's "FX enabled" toggle to drive this. +- **`AlertPulsing.active`** — set automatically by `update(ai_status)` to `True` if `ai_status.lower().startswith("error")`. -| Key | Default | Description | -|---|---|---| -| `fx_enabled` | `true` | Master toggle. When `false`, no scanlines, no flickering, no alerts. | -| `scanline_alpha` | `0.3` | Alpha of the scanline overlay. `0.0` disables scanlines; `1.0` makes them solid (usually too aggressive). | -| `flicker_rate_hz` | `0.5` | Frequency of status flickering. `0.0` disables. Values > `2.0` become seizure-inducing. | -| `alert_pulse_duration_seconds` | `1.5` | How long the red border pulse runs on errors. `0.0` disables. | -| `alert_pulse_color` | `"#E50000"` | Hex color of the alert pulse. | - -### Per-Project Override - -Projects can override the NERV settings in `manual_slop.toml` under a `[theme_nerv]` section (with the same keys). The project override wins over the global `config.toml`. +**Earlier versions of this guide** (pre-2026-06-10) documented a `[nerv]` section with `fx_enabled`, `scanline_alpha`, `flicker_rate_hz`, `alert_pulse_duration_seconds`, and `alert_pulse_color` keys. None of those keys were ever wired into the source code — they were aspirational documentation, not configuration. This is the "stale doc" that the docs-sync track on 2026-06-10 surfaced and removed. ---