Commit graph

131 commits

Author SHA1 Message Date
Max
1fabd4a246 training: validator, feature/tensor extractors, 6 supervised models, schema-hashed checkpoints, eval suite, dashboard producers
The model layer of the project, built honestly:

  - tools/dataset_validate.py — full-sweep validator over the receiver
    store (sha256, schema, monotonic labels, telemetry-row gate). On the
    current corpus: 64,798 accepted + 8,154 degraded + 3,701 rejected +
    7 errored across 76,660 shipped episodes. data/processed/validation_v1.parquet
    is committed as the per-episode acceptance index.

  - training/_features.py — channel registry (46 channels across
    proc/guest/qmp/netflow), summary-stat windowing AND channel×time
    tensor extraction at 10s/5s windowing. Time alignment uses t_wall_ns
    (Unix ns) — tested fix for a real netflow-vs-host clock-base
    inconsistency that was silently dropping every netflow channel.

  - training/_split.py — three held-out recipes (host / sample / time)
    with profile-stratification assertions. held_out_host carries
    untested_profiles for cases like scan-and-dial absent from the test
    host (5 of 6 profiles tested cross-device, never silently averaged).

  - training/models/ — 6 architectures behind a common BaseModel
    interface: gbt (XGBoost), mlp, cnn, gru, lstm, transformer. Each
    trained twice (realistic / oracle) per the deployment threat model.
    Schema-hashed checkpoints refuse to load if _features.py changed
    since training (silent-input-drift protection, tested).

  - training/trainer/ — unified training loop: class-weighted CE, LR
    warmup + cosine, gradient clipping, mixed precision when CUDA,
    early stopping on val macro F1, best-on-val checkpoint. Same loop
    runs MLP/CNN/GRU/LSTM/Transformer; GBT uses XGBoost
    early_stopping_rounds on val mlogloss.

  - training/eval_/ — bootstrap 95% CIs on macro F1, per-class F1,
    per-profile and per-host breakdown, paired-bootstrap significance
    for model-vs-model gap. Confusion matrix uses union of seen labels.

  - training/dashboard/producers/ — replay/metrics/perf/profiles
    emitting the six event types the dashboard's awaiting scenes
    consume; on-demand tensor extraction so the Pi can run live
    inference without 65 GB of shards.

  - 17 unit tests (split coverage, features round-trip, schema mismatch,
    determinism, time-base alignment regression).

End-to-end smoke-trained all six on a 567-episode subset; held-out
test macro F1 reported with paired-bootstrap significance. The
methodology now reports honest cross-device generalization, not
in-distribution validation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 01:19:00 -05:00
Max Gorog
a04bba6281 training/dashboard: click a db row → render the episode envelope
New endpoint GET /api/episode/<host_id>/<episode_id> in app.py.
Stream-decompresses the tarball (zstd -dc piped into tarfile),
extracts telemetry-proc.jsonl, labels.jsonl, and meta.json,
returns the parsed contents. Synchronous extract runs in
asyncio.to_thread so the event loop isn't blocked.

Frontend: clicking a row in the database explorer now fetches
the episode and draws an SVG chart matching the README's Real
Alpine VM envelope shape:
  - per-interval CPU jiffies delta (user + sys)
  - per-interval IO bytes delta (read + write)
  - colored phase bands (clean/armed/infecting/infected_running/
    dormant) overlaid by labels.jsonl
  - axis ticks for 0-peak on Y, 0-totalDuration in seconds on X
  - legend below the chart with palette-driven swatches

The detail panel that previously showed the row JSON now shows
metadata + the chart + the legend. Validated end-to-end against
a real episode (863 samples, 8 labels) extracted from
/var/lib/cis490/episodes/elliott-thinkpad/.
2026-05-08 01:16:54 -05:00
Max Gorog
2aa33d19c1 training/dashboard: reduce metric-stack left padding to shift interactables left 2026-05-08 01:02:27 -05:00
Max Gorog
1160244dfa training/dashboard: tighten stage-view padding-right to full prose-w (no overlap) 2026-05-08 01:00:46 -05:00
Max Gorog
0175882ed6 training/dashboard: shift prose right, tighten metric-stack reserved width
Prose's feathered left edge was overlapping with interactive
widgets in the metric stack and blocking clicks (the prose has
pointer-events: auto so its left half — even when visually
mostly transparent — still captured input).

Two changes:
- .scene right padding: 2rem → 0.5rem. Pushes the prose card
  ~1.5rem further right.
- .stage-view padding-right: was calc(prose-w - 1.5em), now just
  prose-w. The metric-stack now ends exactly where the prose
  column starts instead of being pushed 1.5em into prose
  territory.

Result: roughly 3em of overlap reduction. The prose's left
feather and the metric-stack's right feather now meet over bg
rather than over each other's content.
2026-05-08 01:00:28 -05:00
Max Gorog
698a3c96bc training/dashboard: bilateral fade on the metric-stack backdrop
Backdrop card was a sharp rectangle whose right edge butted hard
against the prose column's left feather, producing a visible
seam where the two layers met. Replaced the solid background
with a horizontal linear-gradient that fades at both edges:

  0%   → transparent       (left edge dissolves into bg)
  8%   → full backdrop     (card body begins)
  78%  → full backdrop     (card body ends)
  100% → transparent       (right edge dissolves into bg)

The right fade is wider than the left because the right edge
overlaps the prose column's feathered start; double-feathering
that interface gives a continuous metric-card → bg → prose-card
transition with no rectangles meeting.

Border-radius removed — was hidden by the feather anyway.
2026-05-08 00:30:49 -05:00
Max Gorog
b41bd75209 training/dashboard(theme): add the content-backdrop slider markup that the JS expects 2026-05-08 00:25:12 -05:00
Max Gorog
6d3f8f1ef8 training/dashboard(theme): fadeable content backdrop behind prose & metrics
New 'content backdrop' slider (0..1, default 0.30) in the
animation section of the theme panel. Drives a single CSS
variable --content-backdrop that controls a uniform dark layer
behind both:

- .metric-stack — solid background with that opacity, plus a
  rounded corner so the metric content reads as a card sitting
  over the bg.
- .scene .prose — added as a SECOND background layer underneath
  the existing left-feathering gradient. The gradient stays;
  where it's transparent (left edge), the new uniform layer
  shows through. At backdrop=0 the prose looks identical to
  before; at backdrop>0 the feathered edge reveals a partly-
  opaque dark instead of fully transparent bg.

So when the bg is busy (vaporwave, drift, lava) the user can
crank backdrop up for legibility; when it's the still black
theme they can drop it to 0 for the cleanest look.
2026-05-08 00:24:55 -05:00
Max Gorog
fd5a0fba09 training/dashboard(vaporwave): re-enable scanlines with all safety measures 2026-05-08 00:20:26 -05:00
Max Gorog
91a3aceb68 training/dashboard: stage-view opacity-transition removal is the fix
Confirmed by user that snapping scenes in/out instead of opacity-
transitioning fixed the grid-shape artifact that had been appearing
over metric content during scene changes.

Root cause: while stage-view's opacity animated between 0 and 1
(over 600ms), the compositor was rendering stage-view to its own
intermediate bitmap and sampling whatever was painted underneath
— including the bg-canvas's animated perspective grid. That
sampled grid leaked into the metric content area for the duration
of the transition. Removing the transition removes the compositor
work entirely; scenes change with a snap, no resampling.

Trade-off accepted: no fade between scenes. If a smoother
transition is wanted later, options that DON'T trigger the same
sampling are clip-path wipes, transform-based slides, or animating
opacity at <100ms (short enough that the sampled bitmap doesn't
have time to register visually).
2026-05-08 00:19:59 -05:00
Max Gorog
1fd2adf376 training/dashboard: diagnostic — remove stage-view opacity transition 2026-05-08 00:18:48 -05:00
Max Gorog
d99a8861f3 training/dashboard: diagnostic — hide intro .bg-grid unconditionally to test source 2026-05-08 00:16:49 -05:00
Max Gorog
09960812fa training/dashboard: will-change: opacity on stage-view to pre-promote layer
The grid-shaped artifact that appeared over metric content
during scene transitions was Chromium promoting the stage-view
to its own compositor layer mid-transition (when opacity left
exactly 0). At that promotion moment the new layer samples
whatever's painted underneath as its initial bitmap — which is
the moving perspective grid in the bg — and that snapshot stays
visible for the duration of the 600ms opacity transition,
reading as a phantom grid pattern over the metric content.

will-change: opacity tells the browser to promote the layer
before the transition starts. The transition is then a pure
compositor opacity interpolation: no resampling of bg, no stale
snapshots. The hint is on the actual transitioning element
(stage-view), not on canvas-wrapper, which avoids the
cutout-mask issue from the previous over-aggressive layer
isolation attempts.
2026-05-08 00:14:57 -05:00
Max Gorog
cdb8d46954 training/dashboard(vaporwave): hide scanlines entirely — repeated moiré source 2026-05-08 00:09:34 -05:00
Max Gorog
34e579587c training/dashboard(vaporwave): move scanlines below sun so blinds don't moiré 2026-05-08 00:08:15 -05:00
Max Gorog
dc340b6462 training/dashboard(vaporwave): revert diagnostic bisect, restore floor/horizon/scanlines 2026-05-08 00:06:08 -05:00
Max Gorog
c55931f30a training/dashboard(vaporwave): diagnostic bisect — hide floor/horizon/scanlines 2026-05-08 00:05:36 -05:00
Max Gorog
cba10e27a5 training/dashboard: strip every stacking-context-creating prop from bg-canvas
The 'rendering over presentation elements' artifacts were from
piling stacking-context-creating properties on bg-canvas:
  - filter: blur(0px) — even at 0px creates a stacking context
    AND a 3D-flattening grouping property
  - transform: translateZ(0) — stacking context + 3D context
  - earlier: isolation, contain — stacking context + flattening

Each of these on its own can cause neighboring element artifacts
in Chromium (cutout-shaped opacity transition leaks, or in extreme
cases content rendering at the wrong z-order).

Strip everything. The bg-canvas is now just position:fixed with
overflow:hidden — that's enough for the bg layers it contains,
and the browser will GPU-promote it automatically when there's
real animation inside. The filter is now conditional: JS sets
--bg-filter to blur(Npx) only when the slider is non-zero, and
removes the custom property at zero so the rule falls through to
filter: none and no stacking context is created.
2026-05-08 00:02:18 -05:00
Max Gorog
841dcead6a training/dashboard(themes): strip overzealous layer isolation
The 'cutout mask' artifacts on foreground stage views came from
piling contain:paint + will-change + transform:translateZ all
together on .canvas-wrapper. Paint containment plus a
GPU-promoted layer on the foreground container breaks compositor
ordering when children's opacity transitions fire (the
IntersectionObserver fades stage-views in/out as scenes activate),
producing rectangular cutout-shaped artifacts where the
transitioning element's layer hadn't fully composited yet.

Strip canvas-wrapper back to the bare minimum (just position,
overflow, z-index). Also drop isolation/contain from bg-canvas,
keeping only transform: translateZ(0) for layer promotion — that
alone is enough to give bg-canvas its own compositor layer
without fighting the foreground's painting.
2026-05-07 23:59:38 -05:00
Max Gorog
243c1d019c training/dashboard(vaporwave): restore scanlines, sky-only, off-resonance, no blend
The scanlines were genuinely the source of the orthogonal-in-3D
artifact, in two compounding ways:

1. mix-blend-mode: multiply on a fullscreen overlay forces an
   isolation group: the browser composites the 3D-rotated floor
   into a flat 2D bitmap underneath the blend, and that
   flattening interacts badly with how Chromium rasterizes
   perspective transforms.

2. The 4px stripe period beats against the perspective floor's
   per-row line spacing (which is dense near the horizon and
   sparse near the viewer). At the screen y where the floor's
   row-spacing crosses 4px, the patterns interfere — producing
   moiré bands that look like a phantom grid orthogonal to the
   floor plane.

Fix: confine scanlines to the sky region above the horizon
(they never touch the perspective grid), drop the multiply
blend (regular alpha compositing), and use a 5px period that
avoids resonance with anything else in the scene.
2026-05-07 23:57:08 -05:00
Max Gorog
775930d35d training/dashboard(vaporwave): diagnostic — hide .vw-scanlines to test moiré hypothesis 2026-05-07 23:55:12 -05:00
Max Gorog
1d6a079c3b training/dashboard(vaporwave): revert perspective-origin change per user 2026-05-07 23:53:35 -05:00
Max Gorog
dccd9c63b6 training/dashboard(themes): hide intro .bg-grid on non-black themes
The 'lines orthogonal in 3D space' artifact was the intro scene's
.bg-grid — a flat 2D grid pattern in canvas-wrapper (z=1), drawn
on screen with horizontal + vertical 1px lines. While the user
was on the intro scene with vaporwave active, this 2D grid
overlaid the rotated perspective floor, looking exactly like a
phantom wall of grid lines orthogonal to the floor — same kind of
pattern, similar palette, but on a plane perpendicular to it.

.bg-grid was added for visual texture under the black theme; the
animated themes (drift/lava/vaporwave/laser) all have their own
backgrounds and don't need it. Hide it on any non-black theme.

The audit-trail of changes I made trying to find this in .vw-floor
have all been valid (the perspective-origin at horizon, the
collapse to a single transform, dropping will-change) and all
should stay — they removed real layer-promotion concerns. But the
artifact the user was seeing the whole time was this overlay.
2026-05-07 23:52:10 -05:00
Max Gorog
f98fa0d6bf training/dashboard(vaporwave): perspective-origin at the horizon
Audit revealed the orthogonal-in-3D phantom was actually correct
rendering of an INCORRECT perspective setup. Default
perspective-origin is 50% 50% of the perspective container — but
.vw-floor extends from the horizon (top 55%) to bottom -10%, so its
midpoint sits ~82% from the viewport top, well below the horizon
line. Receding vertical grid lines were converging at that midpoint
instead of at the horizon, which visually reads as the grid lines
standing UP off the floor at varying angles — exactly the "lines
literally orthogonal in 3D space" artifact.

Setting perspective-origin: 50% 0 puts the vanishing point at the
top of the floor box, which is exactly the horizon line, so
verticals converge where they should and the floor stops looking
3D-broken.
2026-05-07 23:49:39 -05:00
Max Gorog
ea2a03a763 training/dashboard(vaporwave): drop now-unused .vw-floor-tilt wrapper from HTML (CSS already updated) 2026-05-07 23:46:13 -05:00
Max Gorog
547d43cf81 training/dashboard(vaporwave): collapse to single transform — no more phantom 3D post
Removing the .vw-floor-tilt wrapper. The previous nested structure
(perspective container → rotated tilt with preserve-3d → grid with
animated translate3d) had three transform layers that the
compositor had to keep in sync, and Chromium's optimizer kept
breaking the agreement — placing the grid's compositor layer flat
in 3D space, producing the phantom orthogonal post.

New structure: one perspective container, one animated grid. The
rotateX and translateY live on the SAME transform property on the
SAME element. Both keyframes carry the same rotateX so it doesn't
animate (only translateY interpolates), and the rotation is part
of the same transform list as the translate so the grid never
leaves its rotated plane.

No will-change, no transform-style: preserve-3d, no
separately-promoted compositor layer. Nothing for the compositor
to disagree about.
2026-05-07 23:45:53 -05:00
Max Gorog
a8aab937d7 training/dashboard(vaporwave): drop will-change on grid, the source of phantom 3D post
Audit traced the 'orthogonal in 3D space' artifact to
will-change: transform on .vw-floor-grid. Chromium's compositor
was rasterizing the grid's gradient into its own layer and
placing the resulting bitmap in 3D using only the element's
own translate3d, ignoring the parent's rotateX even though
the parent had transform-style: preserve-3d. The result was
a flat 2D billboard of the grid pattern appearing as an
upright post alongside the correctly-rotated floor grid.

Removing will-change lets the simpler layer-promotion path
fire (animation alone), which respects preserve-3d. Also
add transform-style: preserve-3d on the grid itself for
belt-and-suspenders.
2026-05-07 23:42:35 -05:00
Max Gorog
7167d3d661 training/dashboard(vaporwave): restore vertical lines + preserve-3d to fix phantom orthogonal grid
The 'orthogonal in 3D space' artifact is the animated grid layer being
rendered flat (its own 2D bitmap) and then composited back into the
rotated parent — visually appearing as a phantom grid orthogonal to
the actual floor plane. transform-style: preserve-3d on .vw-floor-tilt
tells the compositor to keep the child transforms in the parent's 3D
context so the grid stays glued to the floor instead of popping out.
2026-05-07 23:39:15 -05:00
Max Gorog
03afdc6bd8 training/dashboard(vaporwave): drop the orthogonal grid that was the artifact source 2026-05-07 23:37:38 -05:00
Max Gorog
247095a58b training/dashboard(vaporwave): HTML had stray canvas; restore .vw-floor div tree
The earlier canvas→divs revert edit silently dropped due to a
file-modified race; HTML still had <canvas class=vw-floor-canvas>
while the CSS had been rewritten to target .vw-floor/.vw-floor-tilt/
.vw-floor-grid. Result: rules matched nothing and the grid was
invisible. This commit restores the div tree the CSS expects.
2026-05-07 23:35:24 -05:00
Max Gorog
446add0df7 training/dashboard(vaporwave): strip overzealous contain/translateZ that hid the grid 2026-05-07 23:32:16 -05:00
Max Gorog
004ce5f50a training/dashboard(vaporwave): restore CSS .vw-floor* rules (orphan from prior revert) 2026-05-07 23:29:38 -05:00
Max Gorog
f2e3e982ef training/dashboard(vaporwave): revert to CSS grid + harder isolation
User prefers the CSS perspective look — restore the rotateX +
translate-only-animation grid. To finally kill the scroll-flicker,
push much harder on layer isolation:

- bg-canvas: isolation, contain: layout style paint, transform:
  translateZ(0), will-change: transform, overflow-anchor: none
- canvas-wrapper: same compositor-layer treatment so its
  data-active scene transitions can't dirty bg-canvas
- vw-floor: contain: strict + transform: translateZ(0)
- vw-floor-tilt: rotateX combined with translateZ(0) (hint
  to compositor that this is a 3D layer)
- vw-floor-grid: backface-visibility: hidden, will-change
- html/body/article: overflow-anchor: none (disables Chrome's
  scroll anchoring, which can nudge layout during the IO-driven
  scene toggle and trigger re-paints)
- Lines bumped 2px → 3px (less subpixel sensitivity)

The canvas-based floor is removed; vw-floor-canvas + the rAF
drawing loop are gone.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 23:28:53 -05:00
Max Gorog
c30b4c8948 training/dashboard(vaporwave): give canvas an explicit height
<canvas> is a replaced element — top/bottom positioning alone
doesn't size it; it keeps its intrinsic 300x150 dimensions until
you give it explicit CSS width AND height. The previous CSS only
set width:100% so the canvas rendered at default 300x150, ignoring
bottom:0 and producing a tiny mis-positioned grid.

Set both explicitly: width: 100%, height: calc(100% - --vw-horizon).
Add a ResizeObserver so the drawing buffer follows the box when
the user moves the horizon slider or the window resizes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 23:22:15 -05:00
Max Gorog
24a0816bd1 training/dashboard(vaporwave): floor as a canvas, kill the flicker
Two prior CSS attempts both flickered when the user scrolled the
page. The first animated background-position on a perspective-rotated
element; the second split rotate from translate onto separate
elements with isolation: isolate, will-change, contain: paint, etc.
The browser kept recomputing the perspective math in lockstep with
document scroll regardless.

Replace the floor with a <canvas> drawn frame-by-frame via rAF:
  - No CSS perspective transform; lines drawn at explicit pixel
    positions by JS, so subpixel sampling can't shift them between
    frames.
  - Half-pixel y offsets keep 2px lines crisp.
  - Perspective via 1/(1 + z*k) falloff formula; the slider
    (perspective 30..80) drives k.
  - Palette colors read from `var(--c1)` / `var(--c2)` via a hidden
    probe div (lets the browser do the OKLCH→rgb conversion).
    Cache refreshed every time the palette changes.
  - Rendering loop early-returns when the active theme isn't
    vaporwave, so it costs nothing on other themes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 23:18:52 -05:00
Max Gorog
f4fba1f3bd training/dashboard(vaporwave): isolate bg layer to fix scroll-flicker
Even though bg-canvas is position:fixed, the browser was
re-rasterizing the perspective-rotated grid as the document
scrolled, producing visible jumps in the line positions.

Force bg-canvas into its own compositor layer (isolation: isolate
+ will-change: transform), confine the floor's paint to its box
(contain: paint), and drop transform-style: preserve-3d from the
floor-tilt (it's not needed without nested 3D transforms and
seems to encourage extra rasterization on Chromium).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 23:14:35 -05:00
Max Gorog
0ab4ce76f3 training/dashboard(theme): right-half sidebar + graceful element retirement
Theme panel
- Was a small floating tooltip-style card near the topbar.
- Now a full-height sidebar pinned to the right half of the screen
  (50vw, 100vw on narrow viewports). Slides in/out from the right
  via a transform transition. Toggle via the `is-open` class
  instead of the hidden attribute so the animation works on close
  too. Sticky header keeps the title and close button in view as
  the panel scrolls.

No-despawn-while-on-screen
- Settings sliders that previously rebuilt the bg elements via
  innerHTML='' caused all visible bubbles/blobs/beams to vanish
  instantly. Now existing elements are retired gracefully:
  - drift / lava: retireOnIteration — wait for the element's next
    animationiteration event (which fires at the end of one rise
    cycle, when the element is offscreen) and remove then. The
    user sees old bubbles complete their journey while new ones
    spawn alongside.
  - laser: fadeOutAndRemove — opacity transitions to 0 over 800ms
    then the element is removed. Lasers have no offscreen moment
    in their continuous rotation so a fade is the only graceful
    option.
- 60s safety timeout on retireOnIteration so spamming a slider
  doesn't leak old nodes if animationiteration somehow doesn't
  fire.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 23:09:08 -05:00
Max Gorog
8ee77e14b5 training/dashboard(vaporwave): fix grid line flicker
Animating background-position on a perspective-rotated element
re-rasterizes the gradient every frame; with 1px lines under
3D transform, subpixel sampling jitter caused them to flip
on/off between frames.

Fix: split rotate (static) and translate (animated) onto separate
elements, animate transform: translate3d instead, GPU-promote the
grid layer (will-change + translateZ + backface-visibility), and
bump line width to 2px. The repeating-linear-gradient is drawn
once and the layer offset moves it; translating by exactly one
cell-period loops seamlessly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 23:02:11 -05:00
Max Gorog
a027be72e7 training/dashboard(theme): per-theme settings · vaporwave overhaul
Per-theme settings panel
- New section in the theme panel that auto-shows the active theme's
  knobs (and only the active theme). Built dynamically from a
  THEMES spec; new params drop in by adding one entry.
- drift: blob count (3-12), size, blur, opacity
- lava: bubble count (4-16), mean size, σ spread, goo merge
  strength, goo blur σ. Bubbles are now JS-generated with sizes
  drawn from N(mean, σ) so the spread slider produces real
  variance, not just one-size-fits-all.
- vaporwave: grid cell, horizon angle, sun size, horizon position,
  blind width.
- laser: beam count, thickness, blur, opacity. Beams JS-generated.

Vaporwave overhaul (was: a single perspective grid + radial sun)
- Layered scene: gradient sky → palette-blended sun with venetian-
  blind stripes on the lower half → glowing horizon line → palette
  perspective floor → CRT-style scanline overlay
- Sun box-shadow gives a halo; horizon has multi-stop glow

Live SVG goo filter
- The lava merge strength / blur sliders now mutate the inline
  <feGaussianBlur stdDeviation> and <feColorMatrix values>
  attributes via JS (CSS vars don't work inside SVG filter primitives).

Settings persist in localStorage; new params are merged in over
older snapshots so prior reloads don't break.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 22:57:26 -05:00
elliott
fe0ba239ed Add reference links to references folder 2026-05-07 21:56:22 -06:00
Max Gorog
1cce655a6a training/dashboard(theme): continuous harmony · count + spread sliders
Replaces the discrete harmony dropdown with two continuous sliders
that together cover every named scheme:

  - count   (1..6) — number of palette colors
  - spread  (0..300°) — total angular range across the palette

Offsets fan symmetrically from the primary, so n=3 spread=60 →
[0, +30, -30] and n=4 spread=270 → [0, +90, -90, +180]. Common
named harmonies fall out as specific (count, spread) values:

   mono                   count=1, any spread
   complementary          count=2, spread=180
   analogous              count=3-5, spread<=60
   split-complementary    count=3, spread~180-210
   triadic                count=3, spread=240
   tetradic / square      count=4, spread=270

A small hint line under the sliders shows the matched harmony name
when (count, spread) lands in a recognized neighborhood, or "custom"
otherwise. Per-marker drags continue to work on top of the
continuous setting; touching the count or spread slider regenerates
to the symmetric distribution (i.e. drags are reset on slider use,
which is the natural "clean me up" action).

Migration: any prior localStorage state with the old discrete
`harmony` field or multiplicative `spread` is silently dropped at
boot and replaced with the new defaults (count=3, spread=60).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 22:40:43 -05:00
Max Gorog
924ac9daac training/dashboard(theme): drag fix + advanced sliders + real lava
Fixes
- Wheel markers are now actually draggable. The previous version
  rebuilt the marker DOM during drag, which destroyed the captured
  element. apply() now only rebuilds when harmony changes; otherwise
  it updates marker positions in place.
- Background swap is verified end-to-end. body[data-theme] is set
  by JS and the CSS selectors gate per-theme bg layers correctly.

New theme controls (advanced details panes)
- spread (0.2-2x): scales harmony angular offsets uniformly
- L variance (0-40): per-color alternating lightness ladder
- C variance (0-0.15): per-color alternating chroma ladder
- animation speed (0.1-4x): drives every animation-duration via
  calc(N / var(--anim-speed))
- background blur (0-40px): filter: blur on the entire bg-canvas
- tint strength (0-0.6): palette-tinted vignette opacity, applied
  on EVERY theme so even "black" picks up palette character

Marker drag semantics
- Primary marker drag → rotates the entire palette (changes H)
- Non-primary marker drag → moves just that marker's offset
  (allows individual proportion adjustment on top of harmony preset)

Backgrounds
- Old "lava" is renamed "drift" (soft blurred-blob version)
- New "lava" is a proper lava lamp via SVG goo filter
  (Gaussian blur + alpha threshold) so bubbles merge as they
  approach. Eight palette-colored bubbles rising at staggered
  speeds.

State persists in localStorage; all sliders + offsets survive
reload. Panel resets all knobs back to defaults.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 22:37:35 -05:00
Max Gorog
cf724e8f26 training/dashboard: t-key theme panel · OKLCH palette · 4 backgrounds
Press `t` (or click the topbar button) to open a floating theme
panel. Choices persist in localStorage.

Color machinery
- All palette colors derived from one base OKLCH (L, C, H) plus a
  harmony rule that fixes angular offsets between siblings. Six
  rules: mono, analogous, complementary, triadic, tetradic,
  split-complement. Browsers parse CSS oklch() natively so no JS
  conversion math is needed.
- Sliders for L (20-95%), C (0-0.4), H (0-360°), plus an interactive
  color wheel where each marker is draggable. Dragging any marker
  rotates the entire palette around the wheel — proportions are
  locked by the harmony rule, so siblings move together.

Background themes (--c1..c5 from the palette drive every color)
- black: solid bg, no animation. Default.
- lava: 6 blurred radial blobs floating up the screen at different
  speeds, screen-blended.
- vaporwave: perspective grid crawling toward the viewer + a soft
  radial sun in palette colors.
- laser: 5 long beams sweeping from screen center, palette-colored.

Caveats
- oklch() needs Chrome 111+ / Firefox 113+ / Safari 15.4+. Older
  browsers will fall back to CSS init values (which match the
  previous palette), so the page still renders.
- Phase colors (clean/armed/etc) stay hardcoded — they're semantic.
- The keydown listener for `t` is registered alongside the other
  hotkeys and skips form fields like the rest.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 22:20:41 -05:00
elliott
5a65894442 Add references folder with course materials and notebooks 2026-05-07 21:14:07 -06:00
Max Gorog
c201fec61b training/dashboard: training-code scene + tighter prose-canvas gap
- New scene 10 between Sequence models and KNN: shows
  training/models/lstm.py shape (PyTorch LSTM trainer, ~25 lines).
  Placeholder content; the model session can swap in the real
  trainer string in dashboard.js when it lands.

- Sharpens the prose gradient (95% solid until 70% across instead
  of 92% solid until 55%) and bumps stage-view right-padding by
  2.5em, so the prose column has a clearer edge against the canvas
  content instead of fading smoothly into it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 22:03:42 -05:00
Max Gorog
9373e8c057 training/dashboard(stack): lead with 'live, not staged'; trim prose
Audience takeaway is supposed to be that everything they're seeing is
genuine live data from the devices. The library list moves to a
single supporting paragraph instead of dominating the slide.
2026-05-07 21:51:47 -05:00
Max Gorog
e682562c6e training/dashboard: fix scene-comment numbering after stack insert 2026-05-07 21:45:07 -05:00
Max Gorog
cd133a2dd8 training/dashboard: add 'stack' scene showing real imports
Slots in between intro and collect: a side-by-side display of
pyproject.toml's annotated dependency list and the import header of
receiver/app.py. The point is to surface the project's
stdlib-first / annotated-deps stance early in the deck without
making the audience open a terminal.

Lightweight syntax highlighting via a small char-by-char Python
tokenizer (no third-party highlight.js). The TOML case uses regex.
GitHub-dark color palette.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 21:44:44 -05:00
Max Gorog
e5931df8ad training/dashboard: PRODUCERS.md + client.py for the model session
Documents the event contract for producers that want to drive
widgets on the dashboard:
- /publish endpoint (loopback only; Caddy 404s externally)
- All six widget-driving event types and their shapes
- The reconnect gotcha (live events not replayed; only `snapshot` is)
- Two integration patterns (separate process vs in-process)
- Three options for browser-triggered demos
- systemd hardening that constrains producers running on the Pi

Adds a stdlib-only Publisher helper so producers don't need to
hand-roll urllib.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 21:34:54 -05:00
Max Gorog
a8157ed177 training/dashboard: live deck at dashboard.wg, fed by receiver
Starlette + WebSocket dashboard run on the Pi as cis490-dashboard.service
(127.0.0.1:8447, Caddy-fronted at dashboard.wg). Tails
/var/lib/cis490/index.jsonl for episode events, snapshots host counts
every 30s, broadcasts to every connected browser. New connections get a
warm snapshot (recent_episodes, total_bytes, host_counts) so reloads
don't see a cold dashboard.

Frontend is a 10-scene scrollytelling deck following the project
outline: intro, collect, hosts, db explorer, baseline, attacks,
chunking, models, knn, perf. Sticky full-bleed canvas with a
right-aligned prose column (matrix-explorable layout). Hotkeys (arrows,
space, j/k, c, Home/End), prev/next chevrons, FAB, and an opt-in
click-to-advance toggle. Demo toggle drives synthetic data for the
five scenes that have no real producer yet (attack envelopes,
chunking, model bars, knn scatter, perf scatter); when off, those
scenes show "awaiting <event_type> events" rather than fake data.

Producers wire in by POSTing typed JSON to 127.0.0.1:8447/publish
(loopback only; Caddy 404s it externally). Event types the widgets
subscribe to: model_metric {model, accuracy}, embedding {x, y, phase},
model_perf {model, latency_us, accuracy}, prediction {episode_id,
window_idx, predicted, actual}, attack_profile {name, shape, curve},
phase {phase}.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 21:26:07 -05:00