docs(simulator): design spec + BDDs for curator's X-ray experience simulator

Discovery session 0003. Web-based, Docker-on-localhost simulator to feel the
selection model: real hef.selection wired to a synthetic fixture catalog, with
a full-transparency X-ray (ranked pool + distances + brain/mood grid maps) and
live model knobs (weights, pool size, approved-only).

- docs/superpowers/specs/2026-06-04-experience-simulator-design.md
- features/{selection_xray,model_knobs,fixture_catalog,simulator_service}.feature
- one additive hef.selection.ranked_candidates() change planned (single source of truth)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ben Stull
2026-06-04 15:31:30 -07:00
parent 0e5c3a1a0f
commit 9cb4fb62d8
6 changed files with 326 additions and 0 deletions
+25
View File
@@ -0,0 +1,25 @@
Feature: Synthetic fixture catalog
As a curator with an empty real catalog
I want a dense, representative stand-in catalog generated on demand
So that I can feel nearest-match behavior everywhere before any media is ingested
Scenario: The fixture catalog spans the coordinate space and all content modes
When the fixture catalog is generated
Then it contains records covering the brain plane and the mood plane
And it contains records in each of the "audio", "video", and "av" modes
And it contains a mix of "proposed" and "approved" records
Scenario: The fixture catalog is valid
When the fixture catalog is generated
Then every record passes hef.catalog validation
And no record references real media
Scenario: Generation is deterministic
Given a fixed seed
When the fixture catalog is generated twice
Then both runs produce identical records
Scenario: The real catalog is used when it is populated
Given catalog/library.jsonl is non-empty
When the simulator starts pointed at the real catalog
Then it loads the real records through the same loader instead of the fixtures
+32
View File
@@ -0,0 +1,32 @@
Feature: Feeling the model's own parameters
As a curator tuning the algorithm, not just the curation
I want to adjust the selection model's parameters live
So that I can feel how weighting, pool size, and the approved gate reshape picks
Background:
Given the simulator is loaded with the synthetic fixture catalog
And the dials are set to a fixed knob position with mode "av"
Scenario: Reweighting the brain plane can change the winner
Given the brain and mood weights are equal
And the current pick is recorded
When I increase the brain weight relative to the mood weight
Then the ranked pool may reorder so that brain-plane distance dominates
And the picked piece can differ from the recorded pick
Scenario: Pool size controls how many candidates are shown and shuffled
When I set the pool size to 1
Then the pool shows exactly the single nearest candidate
When I set the pool size to 5
Then the pool shows up to the five nearest candidates
Scenario: Approved-only narrows the pool to human-blessed records
Given the fixture catalog contains both "proposed" and "approved" records
When I turn the approved-only toggle on
Then every candidate in the pool has review_status "approved"
When I turn the approved-only toggle off
Then "proposed" records may appear in the pool again
Scenario: Default model parameters reproduce the shipped selection behavior
Given the weights are equal, the pool size is the default, and approved-only is off
Then the picked piece matches what hef.selection.select would return for the same dials
+48
View File
@@ -0,0 +1,48 @@
Feature: Curator's X-ray of the selection model
As a curator
I want to turn the installation's dials against a representative catalog and see
the full ranked pool the nearest-match algorithm picks from
So that I can judge whether the tagging rubric and selection surface fitting pieces
Background:
Given the simulator is loaded with the synthetic fixture catalog
And the fixture catalog spans the brain plane and the mood plane across all content modes
Scenario: A pick is always accompanied by its candidate pool
When I set the mode to "av" and the dials to left=1 right=3 dark=4 light=1
Then a piece is picked
And the ranked pool is shown nearest-first with each candidate's distance
And the picked piece is the rank-1 candidate in the pool
Scenario: Turning Dark up while Light stays low pulls the pick toward somber pieces
Given the mode is "av" and the dials are left=2 right=2 dark=0 light=0
When I raise the Dark dial from 0 to 4
Then the picked piece's dark coordinate is higher than before
And the pool's nearest candidates cluster toward the high-dark / low-light region
Scenario: Turning a dial moves the knob point on the coordinate map
Given the mode is "av"
When I change any of the Left, Right, Dark, or Light dials
Then the knob point on the brain or mood grid map moves to the new coordinate
And the candidate dots reflect the new neighborhood
Scenario: None mode is the void / rest state
When I set the mode to "none"
Then no piece is picked
And the pool is empty
And the screen shows the void / rest state
Scenario: A+V mode falls back to audio and video when av records are thin
Given the fixture catalog has fewer than the pool size of native "av" records near the knob point
When I set the mode to "av"
Then the pool includes "audio" and "video" records so it is not starved
Scenario Outline: Each content mode restricts the pool to eligible records
When I set the mode to "<mode>"
Then every candidate in the pool is eligible for "<mode>"
Examples:
| mode |
| audio |
| video |
| av |
+27
View File
@@ -0,0 +1,27 @@
Feature: Simulator service and API
As an operator
I want to run the simulator locally in one container and drive it over a small API
So that the browser X-ray and its behavior are testable without the physical stack
Scenario: The service runs locally in a container
When I start the simulator container
Then the X-ray is reachable in a browser on localhost
And no external network access is required
Scenario: Selecting returns the pick and the ranked pool
When I POST a valid selection request with dials and mode "av"
Then the response contains a picked record
And the response contains the ranked pool with a distance and rank per candidate
Scenario: None mode returns the void over the API
When I POST a selection request with mode "none"
Then the response pick is null
And the response pool is empty
Scenario: Invalid dial values are rejected
When I POST a selection request with a dial value outside 0 to 4
Then the API responds with a validation error
Scenario: Catalog metadata is available for the header
When I GET the catalog metadata
Then the response reports record counts, a per-mode breakdown, and the proposed/approved split