268 lines
14 KiB
Markdown
268 lines
14 KiB
Markdown
# NERV Theme (Tactical Console Aesthetic)
|
||
|
||
[Top](../Readme.md) | [Shaders & Window](guide_shaders_and_window.md) | [Architecture](guide_architecture.md)
|
||
|
||
---
|
||
|
||
## Overview
|
||
|
||
The **NERV Theme** is a selectable high-density visual variant inspired by the NERV tactical consoles from the *Neon Genesis Evangelion* anime. It uses sharp geometry, a near-black palette, CRT-style scanlines, status flickering, and alert animations to produce a "tactical command center" aesthetic that emphasizes information density and operational urgency.
|
||
|
||
This guide covers:
|
||
|
||
1. **Design Philosophy** — Why NERV looks the way it does
|
||
2. **Visual Components** — Palette, geometry, typography, effects
|
||
3. **Implementation** — The `theme_nerv.py` and `theme_nerv_fx.py` modules
|
||
4. **Configuration** — `config.toml` keys
|
||
5. **Performance** — Cost of FX rendering
|
||
6. **Testing** — Visual verification
|
||
|
||
---
|
||
|
||
## Design Philosophy
|
||
|
||
The NERV theme is built around three principles:
|
||
|
||
1. **Black Void Background** — The dominant color is near-black (0,0,0 to 8,8,8 range). This reduces visual noise and lets accent colors pop.
|
||
2. **Zero-Rounding Geometry** — No rounded corners on panels, buttons, frames, or separators. Sharp rectangles convey "instrumentation," not "consumer app."
|
||
3. **Operational Urgency** — Status indicators flicker (subtle, low frequency). Errors trigger red border pulses. This is intentional: it makes operational state changes immediately visible to the operator.
|
||
|
||
The theme is **opt-in**, not the default. Most users will use the standard dark or light themes. NERV is for users who want maximum information density and a "tactical" feel.
|
||
|
||
---
|
||
|
||
## Visual Components
|
||
|
||
### Color Palette
|
||
|
||
The palette constants in `src/theme_nerv.py:8-13` (computed from RGB triples via `_c()`):
|
||
|
||
| 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
|
||
|
||
- **Window corners**: Square (0px rounding)
|
||
- **Panel corners**: Square
|
||
- **Button corners**: Square
|
||
- **Frame rounding**: Disabled (`ImGuiStyle.FrameRounding = 0`)
|
||
- **Window rounding**: Disabled (`ImGuiStyle.WindowRounding = 0`)
|
||
- **Child rounding**: Disabled
|
||
|
||
This produces a "military hardware" feel that contrasts with consumer-app aesthetics.
|
||
|
||
### Typography
|
||
|
||
- **Primary font**: `Inter` (already used by other themes)
|
||
- **Monospace font**: `Maple Mono` (for code, telemetry, logs)
|
||
- **Font sizes**: Slightly smaller than the default theme to maximize information density
|
||
- **Font weights**: Medium for body, Bold for headers
|
||
|
||
### Status Flickering
|
||
|
||
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
|
||
return 0.85 + 0.15 * math.sin(time.time() * 20.0)
|
||
```
|
||
|
||
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
|
||
|
||
`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:
|
||
|
||
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
|
||
|
||
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
|
||
|
||
`AlertPulsing` in `src/theme_nerv_fx.py:75-97` is a stateful on/off border overlay:
|
||
|
||
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.
|
||
|
||
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 (88 lines)
|
||
|
||
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()
|
||
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 (97 lines)
|
||
|
||
Three classes, no module-level entry point:
|
||
|
||
- `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_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)
|
||
```
|
||
|
||
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 (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()
|
||
```
|
||
|
||
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
|
||
|
||
**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:
|
||
|
||
- **`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")`.
|
||
|
||
**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.
|
||
|
||
---
|
||
|
||
## Performance
|
||
|
||
| Effect | Cost (1920×1080) | Cost (4K) | Notes |
|
||
|---|---|---|---|
|
||
| Scanlines (FBO) | 1-2ms/frame | 4-8ms/frame | Scales with resolution. |
|
||
| Status flicker | <0.1ms/frame | <0.1ms/frame | Per-element alpha calc, trivial. |
|
||
| Alert pulse | <0.1ms/frame | <0.1ms/frame | Per-frame interpolation, trivial. |
|
||
|
||
**Total NERV overhead**: ~1-2ms/frame on a 1920×1080 display with default settings. For a 60 FPS target (16.67ms/frame), this leaves 14+ ms for the rest of the GUI.
|
||
|
||
**Lower-end GPU**: Reduce `scanline_alpha` (cheaper blending) or disable FX entirely (`fx_enabled = false`). The base NERV theme (colors, geometry, fonts) has no measurable GPU cost.
|
||
|
||
**Latency impact**: None. The FX layer is purely visual; it doesn't block the main render thread.
|
||
|
||
---
|
||
|
||
## Interaction with Other Subsystems
|
||
|
||
### MMA Dashboard
|
||
|
||
The NERV theme enhances the MMA Dashboard with:
|
||
- Color-coded tier indicators (using the NERV accent colors)
|
||
- Status flickering on the active tier
|
||
- Red border pulse on "blocked" / "error" states
|
||
|
||
### Diagnostics Panel
|
||
|
||
The FPS / CPU / Input Lag display uses the NERV palette. Performance warnings (FPS < 30, CPU > 80%) flash in NERV red.
|
||
|
||
### Approval Modal
|
||
|
||
When the Execution Clutch suspends on a destructive action, the approval modal uses the NERV orange for the "Approve" button and the standard gray for "Reject". This makes the destructive action visually distinct.
|
||
|
||
---
|
||
|
||
## Testing
|
||
|
||
### Unit Tests
|
||
|
||
- `tests/test_theme_nerv.py` — `apply_nerv()` correctly sets all colors, geometry, and fonts.
|
||
- `tests/test_theme_nerv_fx.py` — FX rendering functions don't raise, produce expected output dimensions.
|
||
- `tests/test_theme_nerv_alert.py` — Alert pulse animation runs for the configured duration and decays correctly.
|
||
|
||
### Visual Verification
|
||
|
||
The NERV theme is primarily a visual feature; functional tests are limited. The `[nerv]` config keys are read at theme application time; a misconfigured key defaults gracefully.
|
||
|
||
For visual verification in `live_gui` tests:
|
||
|
||
```python
|
||
def test_nerv_theme_active(live_gui):
|
||
# Activate NERV theme
|
||
client.activate_theme("nerv")
|
||
|
||
# Verify the active theme
|
||
status = client.get_ui_performance() # Or a theme-specific endpoint
|
||
assert "theme" in status
|
||
assert status["theme"] == "nerv"
|
||
```
|
||
|
||
The visual appearance (colors, scanlines) is best verified manually by a human reviewer.
|
||
|
||
---
|
||
|
||
## Limitations
|
||
|
||
1. **Scanlines Are Static**: The current implementation uses a fixed scanline pattern. Animated scanlines (slow vertical scroll) are not implemented.
|
||
|
||
2. **No Color Customization in GUI**: The NERV palette is hardcoded. To customize colors, edit `src/theme_nerv.py` directly (no GUI editor for NERV-specific colors).
|
||
|
||
3. **No Theme Mixing**: The NERV theme is a single selectable theme. It cannot be mixed with other themes (e.g., "NERV colors but default rounding").
|
||
|
||
4. **Performance Cost on Low-End GPUs**: The FBO scanline pass may be too expensive on integrated graphics or older GPUs. The `fx_enabled` flag is the workaround.
|
||
|
||
5. **Accessibility**: The black void palette with high-contrast accents is **not** WCAG AA compliant for text contrast in all combinations. The active state color (NERV orange) on black meets AA, but secondary text (gray on black) is borderline. Users with low vision may need a different theme.
|
||
|
||
---
|
||
|
||
## Future Work
|
||
|
||
- **CRT Curvature**: A more advanced shader could apply screen-space curvature to mimic CRT monitors. Currently planned but not implemented.
|
||
- **Bloom Effect**: For high-saturation accent colors, a subtle bloom could make the NERV orange "glow." Adds GPU cost.
|
||
- **Animated Scanlines**: Slow vertical scroll of the scanline pattern for a more "active" CRT feel.
|
||
- **Custom Palette Editor**: Allow the user to override individual NERV colors via the GUI.
|
||
- **Hybrid Themes**: Allow partial NERV adoption (e.g., NERV colors but default rounding).
|
||
|
||
See [guide_shaders_and_window.md](guide_shaders_and_window.md) for the underlying shader and window frame infrastructure.
|