feat: reconciled simulator-first alteration slice (sessions 0007+0008) #7
@@ -0,0 +1,242 @@
|
||||
# Human Experience Filter — Simulator Alteration Preview (Design)
|
||||
|
||||
**Date:** 2026-06-06
|
||||
**Status:** Approved design (pre-implementation)
|
||||
**Repo:** `human-experience-filter-art`
|
||||
**Builds on:** the alteration engine of
|
||||
[`2026-06-05-machine-altered-perception-design.md`](./2026-06-05-machine-altered-perception-design.md)
|
||||
(the "design" below) and the simulator scaffold of
|
||||
[`2026-06-04-experience-simulator-design.md`](./2026-06-04-experience-simulator-design.md).
|
||||
**Revises the design:** §4.3 and §10 — the Left analytical HUD is **no longer a
|
||||
runtime overlay**; it is **baked into authored variant videos** (see §8 below).
|
||||
This is a deliberate trade of near-free multilingual support for authorial
|
||||
precision over the HUD.
|
||||
**Retires:** the simulator's selection-era "curator's X-ray" view and its
|
||||
`/api/select` + `/api/catalog/meta` endpoints (the selection model they
|
||||
visualize was superseded by the alteration model).
|
||||
|
||||
> **Why this exists.** Operator directive (session 0007): *build and design only
|
||||
> things that run in the simulator, and get the whole experience working the way
|
||||
> we like in the simulator before moving to hardware.* The slice-1 alteration
|
||||
> engine (PR #5) is pure logic with **no simulator surface** — you cannot yet turn
|
||||
> the experience knobs and *see* the result. This design brings the alteration
|
||||
> into the simulator so the look can be tuned and liked before any Pi/serial work.
|
||||
|
||||
---
|
||||
|
||||
## 1. Scope
|
||||
|
||||
A browser **alteration preview**: turn the four experience knobs and the content
|
||||
dial, and see the neutral base footage altered toward the knob state, in real
|
||||
time, on a looping clip. The purpose is to **tune the look** of the filter and to
|
||||
**settle the knob→strength calibration by eye** (the open §3-vs-§4.2/§5 question
|
||||
from session 0006).
|
||||
|
||||
**In scope**
|
||||
- Live preview of the four experience knobs (Left/Right/Dark/Light) on a looping
|
||||
base clip.
|
||||
- **Dark/Light** rendered as a live, deterministic runtime color grade.
|
||||
- **Left/Right** rendered by selecting a **pre-baked authored variant clip** from
|
||||
a 5×5 grid and crossfading on change.
|
||||
- A **calibration panel** that adjusts the grade curve live; the chosen values are
|
||||
baked back into `player/alteration.py` defaults.
|
||||
- A **RenderPlan readout** (the project's "X-ray" honesty): always show the exact
|
||||
numbers the engine produced.
|
||||
- The content dial's **video on/off** behavior (so "Off" goes to black), driven by
|
||||
the existing `resolve_content`.
|
||||
- Placeholder variant generation so the mechanism is testable before the operator
|
||||
authors real videos.
|
||||
|
||||
**Out of scope (this slice)** — real generative v2v; serial input / the 3⇄4
|
||||
framing contract; audio playback (music / white-noise / audio-track); the Pi/mpv
|
||||
runtime renderer; the full transition/crossfade timing engine; multilingual label
|
||||
tables; catalog-model changes. These remain later roadmap slices.
|
||||
|
||||
---
|
||||
|
||||
## 2. Architecture — Python-canonical, thin browser renderer
|
||||
|
||||
`player/alteration.py` stays the **single source of truth** for the alteration
|
||||
math (handbook §4.2 — deterministic core, thin I/O). The browser owns only
|
||||
*rendering*. Data flow:
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph Browser
|
||||
K[4 experience knobs +<br/>content dial +<br/>calibration sliders]
|
||||
V["<video> variant + canvas grade"]
|
||||
RO[RenderPlan readout]
|
||||
end
|
||||
subgraph FastAPI [simulator/app.py]
|
||||
EP["POST /api/alteration"]
|
||||
CL["GET /api/clips"]
|
||||
end
|
||||
ENG["player.alteration.plan_alteration(coord, calibration)"]
|
||||
K -- "debounced POST {controls, calibration}" --> EP
|
||||
EP --> ENG --> EP -- "RenderPlan {variant, grade}" --> V
|
||||
EP --> RO
|
||||
CL -- "base clip + variant manifest" --> V
|
||||
```
|
||||
|
||||
- The browser sends control/calibration changes (debounced) and receives a
|
||||
`RenderPlan`. Video filtering itself runs continuously in the browser; only a
|
||||
*plan recompute* makes a round-trip. On localhost these JSON round-trips are
|
||||
imperceptible.
|
||||
- "Bake the calibration winner in" = change the `Calibration` defaults in Python.
|
||||
Nothing in the browser is canonical.
|
||||
|
||||
---
|
||||
|
||||
## 3. The four axes — how each is rendered
|
||||
|
||||
| Axis | Engine output | Browser rendering |
|
||||
|---|---|---|
|
||||
| **Dark / Light** | `ColorGrade.tone` ∈ [−1, 1], center = identity | live color grade on the `<video>`: Light → warm/yellow + negative space toward white; Dark → cool/blue + negative space toward black; 0 = raw. (Confirmed direction in brainstorm.) |
|
||||
| **Left / Right** | `VariantRef(left, right)` | select the authored variant clip for `(left, right)`; **crossfade** when it changes; `(0,0)` → raw base. The analytical HUD (Left) and dreamlike restyle (Right) are **baked into the clip** (§8). |
|
||||
|
||||
Dark/Light is the only continuously-tunable axis in this slice; Left/Right is a
|
||||
discrete selection by coordinate.
|
||||
|
||||
---
|
||||
|
||||
## 4. The variant grid (per base clip)
|
||||
|
||||
Each base clip carries a 5×5 grid keyed by the two baked axes:
|
||||
|
||||
- Rows = **Left** (analytical / HUD), 0–4. Columns = **Right** (artistic /
|
||||
dreamlike restyle), 0–4.
|
||||
- `(0,0)` = the raw base clip — no authored file needed.
|
||||
- The other **24 cells are authored videos** (4 analytical-edge + 4 dreamlike-edge
|
||||
+ 16 combined core). This is **per base clip**; authoring load multiplies by the
|
||||
number of base clips (and by language, since the HUD is baked — §8).
|
||||
|
||||
The grid is **data**, not code: a manifest the simulator reads (see §6).
|
||||
|
||||
---
|
||||
|
||||
## 5. Engine reconciliation (`player/alteration.py`)
|
||||
|
||||
The slice-1 engine modeled Left/Right as continuous runtime layers
|
||||
(`AnalyticalOverlay.intensity`, `Restyle.blend`). Baked variants make that
|
||||
obsolete. Changes:
|
||||
|
||||
- **`RenderPlan`** becomes `{ grade: ColorGrade, variant: VariantRef }`.
|
||||
- `ColorGrade` — unchanged.
|
||||
- `VariantRef(left, right)` — new; identity selection by coordinate.
|
||||
- **Remove** `AnalyticalOverlay` and `Restyle` (their content is now baked into the
|
||||
variant clip) rather than leaving dead fields.
|
||||
- **`Calibration`** — a new frozen dataclass parameterizing the grade curve (e.g.
|
||||
`mood_center`, per-axis curve), defaulting to **today's exact behavior**
|
||||
(behavior-preserving). `plan_alteration(coord, calibration=DEFAULT_CALIBRATION)`.
|
||||
- **`player/state.py`** — the `CROSSFADE` trigger switches from "restyle changed"
|
||||
to "variant changed"; `LIVE_UPDATE` still covers a grade-only change. Video
|
||||
on/off fades unchanged.
|
||||
|
||||
These are framework-code changes that keep the engine pure and unit-tested; they
|
||||
do **not** bake any deployment-shape decision into the engine.
|
||||
|
||||
---
|
||||
|
||||
## 6. Data & endpoints
|
||||
|
||||
- **`simulator/clips.py`** (replaces `simulator/fixtures.py`): reads a base-clip +
|
||||
variant manifest. Each base clip: `{ id, title, base_file, license, source,
|
||||
variants: { "L,R": { file, model?, hud_lang? } } }`. Missing cells fall back to
|
||||
the raw base (and are flagged in the readout as "raw / unauthored").
|
||||
- **`POST /api/alteration`**: body `{ controls, calibration }` → `RenderPlan`
|
||||
(plus the `ContentResolution` from `resolve_content`, so the dial's video-on/off
|
||||
is honored). The endpoint calls the real `plan_alteration`.
|
||||
- **`GET /api/clips`**: the base-clip list + variant manifest for the active base.
|
||||
- Video files served as static assets from a sample-media directory.
|
||||
- **Removed**: `POST /api/select`, `GET /api/catalog/meta`.
|
||||
- `hef.selection` (the library, incl. `Coordinate`) is **untouched**; only the
|
||||
simulator's selection view/endpoints retire. (`ranked_candidates`, added for the
|
||||
old X-ray, may become unused by the simulator — noted, not removed here.)
|
||||
|
||||
---
|
||||
|
||||
## 7. Calibration tuning
|
||||
|
||||
The calibration panel exposes the grade-curve parameters (the two conventions in
|
||||
tension: `(2,2,2,2)`-centered vs the `value/4` reading currently implemented, plus
|
||||
curve shape). Changing them re-requests the plan and the footage responds live.
|
||||
When the operator is happy, the chosen values are written into
|
||||
`DEFAULT_CALIBRATION` in `player/alteration.py` and locked in by a unit test.
|
||||
|
||||
---
|
||||
|
||||
## 8. Baked HUD — the §4.3/§10 revision (accepted trade)
|
||||
|
||||
The design's §4.3/§10 kept the Left HUD a **runtime overlay** specifically so the
|
||||
v2v substrate is language-agnostic and multilingual support is near-free. This
|
||||
design **reverses that for the Left/Right plane**: the operator authors the HUD
|
||||
**into** the variant videos to get pixel-precise control over the HUD and the
|
||||
analytical↔feeling balance.
|
||||
|
||||
**Consequences (accepted):**
|
||||
- Multilingual support is **no longer near-free**: baked HUD text means a new
|
||||
language re-renders every label-bearing variant (the §10 "budget landmine").
|
||||
The piece is English-first; broad i18n is deferred/expensive.
|
||||
- The live Dark/Light grade is applied **on top** of the baked cell, so it **tints
|
||||
the baked HUD** too (a "Dark" mood cools/darkens the HUD). With baked overlays
|
||||
the grade cannot skip the HUD. Escape hatch: author HUD colors that survive
|
||||
grading, or (future) reinstate a runtime HUD layer.
|
||||
|
||||
The parent design's §4.3/§10 must be updated to point at this revision (a task for
|
||||
the implementation plan).
|
||||
|
||||
---
|
||||
|
||||
## 9. Bootstrapping before authored videos exist
|
||||
|
||||
The operator will author the 24 variants per base clip; none exist yet. So the
|
||||
mechanism is testable immediately, the build includes a **placeholder generator**:
|
||||
for at least one base clip, produce the 24 cells by burning the cell's `L,R` (and
|
||||
a stub HUD caption) into the base loop via ffmpeg. Real authored clips drop into
|
||||
the manifest with **no code change**.
|
||||
|
||||
---
|
||||
|
||||
## 10. Testing
|
||||
|
||||
- **`player/` unit tests:** `DEFAULT_CALIBRATION` is behavior-preserving vs the
|
||||
current helpers; the two calibration conventions produce the expected plans;
|
||||
`VariantRef` selection (incl. `(0,0)` → raw) and the `state.py` crossfade
|
||||
trigger on variant change.
|
||||
- **Simulator API tests** (rewrite `tests/test_simulator_api.py`):
|
||||
`POST /api/alteration` returns the engine's plan for given controls/calibration;
|
||||
`GET /api/clips` returns the manifest; the removed endpoints are gone.
|
||||
- **No browser/E2E automation** this slice (manual visual tuning is the point);
|
||||
the rendering JS is kept thin and the engine logic stays in tested Python.
|
||||
|
||||
---
|
||||
|
||||
## 11. What ships
|
||||
|
||||
- `player/alteration.py` (parameterized `Calibration`, `VariantRef`, slimmed
|
||||
`RenderPlan`) + `player/state.py` crossfade-trigger update.
|
||||
- `simulator/clips.py` (variant manifest) replacing `fixtures.py`.
|
||||
- `simulator/app.py` new endpoints; selection endpoints removed.
|
||||
- `simulator/static/` rewritten as the Player preview (variant `<video>` +
|
||||
crossfade, live grade, calibration panel, RenderPlan readout, content dial).
|
||||
- Placeholder-variant generator + a sample base clip.
|
||||
- Tests above; `docs/USER_GUIDE.md` "Playing with the simulator" rewritten;
|
||||
parent design §4.3/§10 pointer updated; `docs/ROADMAP.md` §3 updated.
|
||||
|
||||
---
|
||||
|
||||
## 12. Open questions (for the plan, not blockers)
|
||||
|
||||
- **Grade vs baked HUD interaction** — accepted that the grade tints the HUD;
|
||||
revisit only if it reads badly once real authored clips exist.
|
||||
- **Sample base clip** — pick one CC0 nature loop for the placeholder grid.
|
||||
- **Crossfade timing in the browser** — a simple opacity crossfade is enough for
|
||||
tuning; the real timing engine is a later slice.
|
||||
|
||||
---
|
||||
|
||||
## 13. Out of scope (YAGNI)
|
||||
|
||||
Real generative v2v; serial / firmware; audio playback; the Pi renderer; the full
|
||||
transition engine; multilingual tables; catalog-model changes; retiring
|
||||
`hef.selection.ranked_candidates`. All remain later roadmap work.
|
||||
@@ -0,0 +1,285 @@
|
||||
# HEF — Reconciled Simulator-First Alteration Slice (Design)
|
||||
|
||||
**Date:** 2026-06-07
|
||||
**Status:** Approved design (pre-implementation) — reconciliation approved this session (0009)
|
||||
**Repo:** `human-experience-filter-art`
|
||||
**Reconciles:**
|
||||
[`2026-06-06-simulator-alteration-preview-design.md`](./2026-06-06-simulator-alteration-preview-design.md)
|
||||
(session 0007, the *unmerged* `feature/simulator-alteration-preview` branch) **with**
|
||||
[`2026-06-07-scales-library-and-right-axis-pipeline-design.md`](./2026-06-07-scales-library-and-right-axis-pipeline-design.md)
|
||||
(session 0008, merged to `main`).
|
||||
**Parents:**
|
||||
[`2026-06-05-machine-altered-perception-design.md`](./2026-06-05-machine-altered-perception-design.md)
|
||||
(the alteration engine) and
|
||||
[`2026-06-04-experience-simulator-design.md`](./2026-06-04-experience-simulator-design.md)
|
||||
(the simulator scaffold).
|
||||
**Supersedes:** the **baked-HUD / 5×5-grid** position of the 0007 design (§4, §5, §8 there).
|
||||
|
||||
> **Why this exists.** Sessions 0007 and 0008 left two unmerged design threads that
|
||||
> disagree on one load-bearing point — how the **Left** analytical HUD is rendered —
|
||||
> and, downstream of that, on the **shape of the pre-baked variant set**. 0007 baked
|
||||
> the Left HUD into a 5×5 grid of authored Left×Right variant clips (pixel-precise,
|
||||
> but i18n becomes expensive). 0008 (the later, merged design) kept the Left HUD a
|
||||
> **runtime overlay** driven by an authored annotation track + per-language string
|
||||
> tables (near-free i18n). This document picks the runtime-overlay position, follows
|
||||
> its consequences through the engine and the simulator, and scopes the first
|
||||
> **simulator-runnable** slice that realizes it. It then hands off to an
|
||||
> implementation plan.
|
||||
|
||||
---
|
||||
|
||||
## 1. The decision (operator-approved this session)
|
||||
|
||||
**Left is a runtime overlay, not baked pixels.** Concretely:
|
||||
|
||||
- **Left axis** → a **runtime `AnalyticalOverlay`** driven by an **offline-authored
|
||||
annotation track** (box positions, anchor points, and which label *keys* appear at
|
||||
each Left level 0–4) plus a per-language **string table** (key → translated text).
|
||||
Text is **shaped live** — natively by the browser in the simulator; by
|
||||
**Pango + HarfBuzz + Noto** on the Pi at runtime (the 0008 §1.2 requirement). This
|
||||
is the "authored box positions + runtime-shaped text" hybrid: authorial control
|
||||
over *layout*, near-free *i18n*.
|
||||
- **Right axis** → the **only pre-baked axis**: a small set of **flow-stabilized
|
||||
restyle-strength variants** (0008 §1), selected **discretely** by the Right knob
|
||||
(0–4), with a **crossfade** on change.
|
||||
- **Dark/Light** → a **live runtime `ColorGrade`** (mood center = identity, §5 of the
|
||||
parent).
|
||||
|
||||
**What this supersedes.** The 0007 design's 5×5 Left×Right **grid of 24 authored
|
||||
clips** and its **removal of `AnalyticalOverlay`/`Restyle` from `RenderPlan`** are
|
||||
dropped. The Left HUD is *not* baked; the variant set is **1-D over Right strength**,
|
||||
not a 2-D grid. (Accepted loss vs. fully-baked HUD: no pixel-painted HUD *artwork* —
|
||||
the HUD is shaped text + drawn boxes. Accepted gain: near-free i18n + far less
|
||||
authoring — N Right variants per clip, not 24.)
|
||||
|
||||
**What it preserves from 0007.** The valuable, non-conflicting parts: Python-canonical
|
||||
engine with a thin browser renderer; a parameterized **`Calibration`** tuned by eye in
|
||||
the sim; retiring the simulator's selection-era surface; placeholder-variant generation
|
||||
so the mechanism is testable before real authored media exists.
|
||||
|
||||
### 1.1 The merged engine is already most of the way there
|
||||
|
||||
The slice-1 engine on `main` (`player/alteration.py`) **already** models
|
||||
`RenderPlan = { grade, overlay, restyle }` with a runtime `AnalyticalOverlay` — only
|
||||
0007's *unmerged doc* proposed removing it. So this reconciliation is **surgical**, not
|
||||
a rewrite: keep `grade` and `overlay`; change only how the **Right** axis and the
|
||||
**calibration** are modeled.
|
||||
|
||||
---
|
||||
|
||||
## 2. Engine reconciliation (`player/alteration.py`, `player/state.py`)
|
||||
|
||||
### 2.1 `RenderPlan` layers
|
||||
|
||||
| Layer | Axis | Type | Change from `main` |
|
||||
|---|---|---|---|
|
||||
| `grade: ColorGrade` | Dark/Light | `tone ∈ [−1,1]`, 0 = identity | unchanged shape; curve now from `Calibration` |
|
||||
| `overlay: AnalyticalOverlay` | Left | gains discrete `level: int` (0–4) + keeps `intensity` | `level` added so the renderer selects which annotations show |
|
||||
| `restyle: Restyle` | Right | **discrete** `variant: int` (0–4); 0 = raw | replaces continuous `blend: float` |
|
||||
|
||||
- **`Restyle.variant`** is a discrete index selecting a **pre-baked** Right-strength
|
||||
clip. `variant == 0` means the raw base (no restyle). This matches "select a
|
||||
pre-baked variant and crossfade," and replaces the continuous `blend` that no longer
|
||||
has a runtime meaning (restyle is pre-baked, not blended live).
|
||||
- **`AnalyticalOverlay.level`** (0–4) is the Left knob value; the renderer uses it to
|
||||
choose which annotations from the authored track are active. `intensity` (0..1)
|
||||
stays for overlay opacity/strength and is derived from `level` via `Calibration`.
|
||||
- **`RenderPlan.is_identity`** holds when `grade.is_identity and overlay.level == 0
|
||||
and restyle.variant == 0`.
|
||||
|
||||
### 2.2 `Calibration` (new frozen dataclass)
|
||||
|
||||
A frozen `Calibration` parameterizes the knob→strength maps so they can be tuned **by
|
||||
eye in the sim** and then baked into a `DEFAULT_CALIBRATION` constant:
|
||||
|
||||
- `mood_center` and a per-axis curve for `_mood_tone` (Dark/Light).
|
||||
- the Left `level → intensity` curve.
|
||||
- the Right `knob → variant` map (which knob positions select which pre-baked
|
||||
strength; identity-preserving so knob 0 → variant 0).
|
||||
|
||||
`plan_alteration(coord, calibration: Calibration = DEFAULT_CALIBRATION) -> RenderPlan`.
|
||||
**`DEFAULT_CALIBRATION` reproduces today's exact behavior** (the three current helpers:
|
||||
`value/4` for Left intensity, `(light−dark)/4` for mood, `right` → `variant=right`), so
|
||||
the change is behavior-preserving until the operator tunes it. The session-0006
|
||||
knob→strength open decision is then settled **by eye** in the sim's calibration panel
|
||||
and locked into `DEFAULT_CALIBRATION` by a unit test.
|
||||
|
||||
### 2.3 `player/state.py`
|
||||
|
||||
`state.py` already classifies a change to `plan.restyle` as a `CROSSFADE` and a grade-/
|
||||
overlay-only change as `LIVE_UPDATE`. With `Restyle.variant` discrete, the same
|
||||
classifier works unchanged: a new Right variant → `CROSSFADE`; a grade or Left-overlay
|
||||
change → `LIVE_UPDATE`; clip swap or video on/off → crossfade / fade-to-black as today.
|
||||
The only edit is to the `_classify` comparison if the field name changes
|
||||
(`restyle.blend` → `restyle.variant`).
|
||||
|
||||
These are pure framework-code changes; no deployment-shape decision enters the engine.
|
||||
|
||||
---
|
||||
|
||||
## 3. Simulator (`simulator/`)
|
||||
|
||||
### 3.1 Retire the selection era
|
||||
|
||||
The simulator currently visualizes the **old selection model** (the "curator's X-ray").
|
||||
Remove:
|
||||
|
||||
- `POST /api/select`, `GET /api/catalog/meta`;
|
||||
- the X-ray static UI (`simulator/static/*` rewritten, see §3.4);
|
||||
- `simulator/fixtures.py` (the 625-record synthetic *catalog*) — replaced by
|
||||
`simulator/clips.py`.
|
||||
|
||||
`hef.selection` (the library, incl. `Coordinate`, `ranked_candidates`) is **untouched**;
|
||||
only the simulator's selection *surface* retires. `Coordinate` is still used by the
|
||||
alteration engine.
|
||||
|
||||
### 3.2 `simulator/clips.py` (replaces `fixtures.py`)
|
||||
|
||||
Reads a base-clip + variant manifest. Per base clip:
|
||||
|
||||
```
|
||||
{
|
||||
"id": "forest",
|
||||
"title": "...",
|
||||
"base_file": "forest/base.mp4",
|
||||
"license": "...", "source": "...",
|
||||
"right_variants": { "1": {"file": "forest/right1.mp4", "model": "..."},
|
||||
"...": {...}, "4": {"file": "forest/right4.mp4"} },
|
||||
"annotations": [ {"key": "detected.conifer", "box": [x,y,w,h], "min_level": 1}, ... ],
|
||||
"strings": { "en": { "detected.conifer": "conifer", ... } }
|
||||
}
|
||||
```
|
||||
|
||||
- `right_variants` is keyed by Right strength `1..4` (strength `0` = raw `base_file`).
|
||||
Missing strengths fall back to the raw base and are flagged "raw / unauthored" in the
|
||||
readout.
|
||||
- `annotations` is the **authored annotation track** (box + label key + the minimum
|
||||
Left level at which it appears). `strings` is the per-language table (English only
|
||||
this slice).
|
||||
|
||||
### 3.3 Endpoints
|
||||
|
||||
- **`POST /api/alteration`** — body `{ controls, calibration? }` → `RenderPlan`
|
||||
(serialized) **plus** the `ContentResolution` from `resolve_content` (so the content
|
||||
dial's video-on/off is honored — "Off" → black). Calls the real
|
||||
`plan_alteration(coord, calibration)`.
|
||||
- **`GET /api/clips`** — the base-clip list + the active clip's manifest (variants +
|
||||
annotation track + string table).
|
||||
- Video served as static assets from a sample-media directory.
|
||||
- **Removed:** `POST /api/select`, `GET /api/catalog/meta`.
|
||||
|
||||
### 3.4 Browser preview (`simulator/static/`)
|
||||
|
||||
Thin renderer; all alteration math stays in Python.
|
||||
|
||||
- **Right** — `<video>` showing the selected Right-variant file; **opacity crossfade**
|
||||
to the new file when `restyle.variant` changes; variant 0 → the raw base.
|
||||
- **Dark/Light** — **live color grade** over the video via CSS/canvas filters: Light →
|
||||
warm + lifted toward white; Dark → cool + crushed toward black; tone 0 → raw.
|
||||
- **Left** — overlay drawn **live** from the annotation track: for each annotation with
|
||||
`min_level ≤ overlay.level`, draw its box and the **shaped** string (browser-native
|
||||
shaping) for the active language; opacity from `overlay.intensity`. No baked HUD.
|
||||
- **Content dial** — drives `<video>` visibility; "Off"/audio-only → black walls.
|
||||
- **Calibration panel** — sliders for the `Calibration` params; changing them
|
||||
re-requests the plan and the footage responds live.
|
||||
- **RenderPlan readout** — always shows the exact engine numbers (grade tone, overlay
|
||||
level/intensity, restyle variant) — the project's honesty "X-ray," now over the
|
||||
alteration model.
|
||||
|
||||
The Left overlay being browser-drawn is the **simulator analogue** of the Pi's
|
||||
Pango/HarfBuzz path: both take the *same* annotation track + string table; the browser
|
||||
shapes natively, the Pi shapes with HarfBuzz. The manifest is the shared contract.
|
||||
|
||||
---
|
||||
|
||||
## 4. The sample clip + a real Right variant (this slice)
|
||||
|
||||
To make the look **evaluable now**, wire one real clip end-to-end using the session-0008
|
||||
POC artifacts (`~/hef-poc/out/`, outside the repo):
|
||||
|
||||
- **Base clip** = the POC's `neutral.mp4` (an 8 s nature loop) → copied to the
|
||||
sample-media dir as the one base clip.
|
||||
- **Right variant (top strength)** = the POC's **`right_flow.mp4`** — the real,
|
||||
operator-approved **flow-stabilized** restyle — wired as Right strength 4.
|
||||
- **Intermediate Right strengths (1–3)** = generated by a small **ffmpeg placeholder
|
||||
generator** (e.g. graded/blended stand-ins) so the crossfade mechanism is exercised
|
||||
across the full knob range; the real high-end look is present for tuning.
|
||||
- **Left annotation track** = a minimal authored track (a few boxes + English label
|
||||
keys) for that clip, so Left renders as real shaped text over drawn boxes.
|
||||
|
||||
> **Licensing note.** The simulator sample footage exists **only to tune the look**; it
|
||||
> is not shipped installation content. Strict-PD scale-library sourcing (NASA/NOAA/NPS
|
||||
> per 0008 §2.1) remains a later slice and is unaffected by this choice.
|
||||
|
||||
A real multi-strength SD re-bake (4 genuine flow-stabilized strengths) is **out of scope
|
||||
this slice** — one real strength + placeholders is enough to settle the mechanism and the
|
||||
calibration. The re-bake is a later offline-pipeline task.
|
||||
|
||||
---
|
||||
|
||||
## 5. Testing
|
||||
|
||||
- **`player/` unit tests** (`tests/test_player_alteration.py`, `test_player_state.py`):
|
||||
- `DEFAULT_CALIBRATION` reproduces the current helpers exactly (behavior-preserving).
|
||||
- `Restyle.variant` is discrete; knob 0 → variant 0 (identity); a non-default
|
||||
`Calibration` changes the plan as specified.
|
||||
- `AnalyticalOverlay.level` maps from the Left knob; `intensity` derives from it.
|
||||
- `state.py`: a Right-variant change → `CROSSFADE`; a grade-/overlay-only change →
|
||||
`LIVE_UPDATE`; video on/off unchanged.
|
||||
- **Simulator API tests** (rewrite `tests/test_simulator_api.py`):
|
||||
- `POST /api/alteration` returns the engine's plan (+ `ContentResolution`) for given
|
||||
controls/calibration.
|
||||
- `GET /api/clips` returns the manifest.
|
||||
- the removed endpoints (`/api/select`, `/api/catalog/meta`) are gone (404).
|
||||
- `test_fixtures.py` retired/rewritten for `clips.py`.
|
||||
- **No browser/E2E automation** this slice — manual visual tuning is the point; the JS
|
||||
stays thin and the tested logic stays in Python.
|
||||
|
||||
---
|
||||
|
||||
## 6. What ships
|
||||
|
||||
- `player/alteration.py` — `Calibration` + `DEFAULT_CALIBRATION`, discrete
|
||||
`Restyle.variant`, `AnalyticalOverlay.level`; `plan_alteration(coord, calibration)`.
|
||||
- `player/state.py` — crossfade-trigger field rename only.
|
||||
- `simulator/clips.py` (variant + annotation manifest) replacing `fixtures.py`.
|
||||
- `simulator/app.py` — `/api/alteration` + `/api/clips`; selection endpoints removed.
|
||||
- `simulator/static/` — rewritten as the alteration preview (variant `<video>` +
|
||||
crossfade, live grade, live Left overlay, content dial, calibration panel,
|
||||
RenderPlan readout).
|
||||
- Sample base clip + one real Right variant + placeholder generator + minimal Left
|
||||
annotation track / English strings.
|
||||
- Tests above; `docs/USER_GUIDE.md` "Playing with the simulator" rewritten; the parent
|
||||
design §4.3/§10 pointer updated to cite this reconciliation; `docs/ROADMAP.md` §3
|
||||
updated.
|
||||
|
||||
---
|
||||
|
||||
## 7. Out of scope (YAGNI) — later slices
|
||||
|
||||
- Serial input / the 3⇄4 framing contract; the Pi/mpv/GPU runtime renderer (deferred by
|
||||
`simulator-first-before-hardware`).
|
||||
- Audio playback (music / white-noise / audio-track).
|
||||
- The **endless rotary encoder** + **AI zoom/warp transitions** between scales
|
||||
(0008 §3) — a separate control + offline pipeline element.
|
||||
- A real multi-strength SD flow-stabilized re-bake; strict-PD scale-library sourcing
|
||||
(0008 §2.1).
|
||||
- Catalog-model changes (audio source / neutral-vs-variant flag); retiring
|
||||
`hef.selection.ranked_candidates`.
|
||||
- Broad multilingual string tables (English-first; the runtime path keeps i18n cheap,
|
||||
but authoring other languages is later).
|
||||
|
||||
---
|
||||
|
||||
## 8. Open questions (for the plan, not blockers)
|
||||
|
||||
- **Calibration curve shape** — the panel exposes the params; the final
|
||||
`DEFAULT_CALIBRATION` values are settled by eye, not fixed here.
|
||||
- **Grade vs. Left overlay interaction** — with a runtime overlay the grade need *not*
|
||||
tint the HUD (unlike the baked path); decide in the renderer whether the Left overlay
|
||||
sits above or below the grade. Default: **above** (HUD stays legible regardless of
|
||||
mood). Revisit by eye.
|
||||
- **Crossfade timing in the browser** — a simple opacity crossfade is enough for tuning;
|
||||
the real timing engine is a later slice.
|
||||
- **Placeholder fidelity** — how close the strength-1–3 placeholders should look to real
|
||||
restyle; cheap stand-ins are fine for mechanism + calibration.
|
||||
Reference in New Issue
Block a user