"""Build the rotating-pool manifest for the 5 ring scales (content-pipeline §11).

This is the reproducible, hand-authored BASELINE for Increment 2's manifest:
each ring scale carries a POOL of vetted clips (docs/content-candidate-pool.md);
the player picks one member at random on landing (player.ring.pick_clip_id). The
authored label/affect content lives here in readable Python and is emitted to
`simulator/sample_media/manifest.json` — far less error-prone than hand-editing 19
clips of JSON, and a clean place for the simulator author mode to regenerate from.

Authoring model (design §11.3 / §11.4):
  - LEFT detail tiers: each label carries `salience` (1-4; first shows at Left
    level `5 - salience`) and a tiered string list (general -> specific ->
    scientific -> +fact) that escalates with the Left knob.
  - RIGHT emotion tiers: affect words are a tiered list per feeling (basic ->
    compound) escalating with the Right knob; still gated on min(Left, Right).
  - TIME-WINDOWED + TRACKED labels: a label may carry `appear`/`disappear`
    (loop-normalized) and a keyframed `track`; it shows only while on screen and
    its box follows the subject. The abyss + reef pools are the test material.

Strings: a value may be a LIST (indexed by tier-1) or a plain string (static,
back-compat). Affect appears only when both knobs are up.

Usage:
  python simulator/build_pool_manifest.py            # write manifest.json
  python simulator/build_pool_manifest.py --media     # + generate new transition placeholders
"""

from __future__ import annotations

import json
import shutil
import subprocess
import sys
from pathlib import Path

MEDIA = Path(__file__).parent / "sample_media"
MANIFEST = MEDIA / "manifest.json"

# --- Pools: scale id -> ordered clip ids (primary first) (content-candidate-pool.md) ---
POOLS: dict[str, list[str]] = {
    "cosmos": ["cosmos", "cosmos_galaxies", "cosmos_orion", "cosmos_tarantula",
               "cosmos_westerlund", "cosmos_southernring", "cosmos_carina_eso"],
    "orbit": ["orbit_planetearth", "orbit_bluemarble", "orbit_aurora2025",
              "orbit_citylights", "orbit_helene", "orbit_epic"],
    "sky": ["sky_grca_templesa", "sky_greenland_landice", "sky_greenland_suture",
            "sky_jungle_amazon", "sky_jungle_waterfall", "sky_coast_cliffspain",
            "sky_mtn_castlecrags", "sky_mtn_rocky"],
    "coast": ["coast_birdrock", "coast_surfgrass", "coast_kelp", "coast_otters",
              "coast_kalaloch", "coast_seals", "coast_mist"],
    "reef": ["reef_lionfish", "reef_spawning", "reef_hawkfish", "reef_coralspacific",
             "reef_redsea", "reef_flowergarden"],
    "abyss": ["abyss_wow", "abyss_midwaterexp", "abyss_hiding", "abyss_bigfin",
              "abyss_dandelion", "abyss_octopus", "abyss_seapig"],
}

# Ring order (large -> small, wraps): cosmos -> orbit -> sky -> coast -> reef -> abyss -> cosmos.
# `sky` (the airplane-window / low-flight layer) sits between orbit (space) and coast (ground).
RING_ORDER = ["cosmos", "orbit", "sky", "coast", "reef", "abyss"]

# Per-scale soundtrack (audio spec §5.1) — the production-pass output of
# simulator/build_audio_media.py, served at /media/audio/<path>.
SCALE_AUDIO: dict[str, str] = {
    "cosmos": "cosmos/pillars.loop.mp3",
    "orbit": "orbit/spaceamb.loop.mp3",
    # TODO(sky): wants its own wind/air ambience — reusing the orbit space-amb loop as a placeholder.
    "sky": "orbit/spaceamb.loop.mp3",
    "coast": "coast/waves.loop.mp3",
    "reef": "reef/soundscape.loop.mp3",
    "abyss": "abyss/whale.loop.mp3",
}

PD = "public-domain (US Gov, 17 U.S.C. §105)"
PD_NPS = "public-domain (NPS, no © — 17 U.S.C. §105)"
CCBY_STSCI = "CC-BY-class — credit NASA, ESA, STScI"
CCBY_WEBB = "CC-BY-class — credit NASA, ESA, CSA, STScI"

# --- Per-clip provenance: id -> (title, license, source) ---
META: dict[str, tuple[str, str, str]] = {
    # cosmos
    "cosmos": ("Cosmic Cliffs — Carina Nebula flythrough (NASA/ESA/CSA/STScI)", CCBY_WEBB,
               "NASA SVS 31348 Clifs-3d-STScI (Exploring the Cosmic Cliffs in 3D, Webb NIRCam); "
               "4K source, trim 10–34s, crossfade-loop. Text-free — replaces the Orion SVS 30957 flythrough."),
    "cosmos_miri": ("Cosmic Cliffs — Carina Nebula, mid-infrared (NASA/ESA/CSA/STScI)", CCBY_WEBB,
                    "ESA/Webb weic2205c (Pan of the Carina Nebula, combined NIRCam + MIRI); 4K source, "
                    "trim 4–26s, crossfade-loop. The MIRI mid-infrared composite — a muted gray/teal/gold "
                    "dust palette distinct from the NIRCam flythrough, same Cosmic Cliffs towers. Text-free."),
    "cosmos_galaxies": ("Flying Through Galaxies (NASA SVS)", PD,
                        "NASA SVS a014950 14950_Galaxies_FlyThrough_4k; trim 8–32s, crossfade-loop"),
    "cosmos_hudf": ("Hubble Ultra Deep Field zoom (NASA/ESA/STScI)", CCBY_STSCI,
                    "NASA SVS a030687 hudf-b-1920x1080p30; trim 20–44s, crossfade-loop"),
    "cosmos_xdf": ("eXtreme Deep Field flythrough (NASA/ESA/STScI)", CCBY_STSCI,
                   "NASA SVS a030681 hxdf_fly-b-1920x1080p30; trim 2–26s, crossfade-loop"),
    # orbit
    "orbit_planetearth": ("ISS — View of Planet Earth (NASA SVS)", PD,
                          "NASA SVS a030771 ISS_View_of_Planet_Earth_2160p; trim 10–34s, crossfade-loop"),
    "orbit_crewobs": ("ISS — Crew Earth Observations (NASA SVS)", PD,
                      "NASA SVS a030771 ISS_Crew_Earth_Observations_2160p; trim 9–33s, crossfade-loop"),
    "orbit_bluemarble": ("NPP “Blue Marble” rotating globe (NASA SVS)", PD,
                         "NASA SVS a004550 BlueMarble_starfield_4k_2160p30_v2; trim 10–34s, crossfade-loop"),
    # coast
    "coast_birdrock": ("Bird rock + ocean, aerial (NPS Channel Islands)", PD_NPS,
                       "NPS Channel Islands AV 663a2f13; trim 3–27s, crossfade-loop"),
    "coast_surfgrass": ("Surfgrass / coralline tidepool (NPS Cabrillo)", PD_NPS,
                        "NPS Cabrillo AV de7d1cf2; trim 2–26s, crossfade-loop"),
    "coast_elkbeach": ("Elk resting in coastal fog (NPS Redwood)", PD_NPS,
                       "NPS Redwood redw-elkbeach_1280x720; trim 20–38s, crossfade-loop (720p source)"),
    "coast_drakesbeach": ("Elephant seals on dark sand (NPS Point Reyes)", PD_NPS,
                          "NPS Point Reyes AV 1cfd8165; trim 20–35s, crossfade-loop"),
    # reef
    "reef_lionfish": ("Lionfish hovering over reef (NOAA Fisheries)", PD,
                      "NOAA Fisheries b-roll VIDEO_ID 4088881464001; trim 12–36s, crossfade-loop"),
    "reef_spawning": ("Snapper school over sunlit reef (NOAA Fisheries)", PD,
                      "NOAA Fisheries b-roll VIDEO_ID 1553921798001; trim 50–78s, crossfade-loop"),
    "reef_hawkfish": ("Humphead parrotfish over reef (NOAA Fisheries)", PD,
                      "NOAA Fisheries b-roll VIDEO_ID 6039896460001; trim 250–274s, crossfade-loop"),
    "reef_snapper": ("Red snapper schooling (NOAA Fisheries)", PD,
                     "NOAA Fisheries b-roll VIDEO_ID 5305427942001; trim 7–31s, crossfade-loop"),
    "reef_coralspacific": ("Pacific coral macro (NOAA Fisheries)", PD,
                           "NOAA Fisheries b-roll VIDEO_ID 5231464663001; trim 30–54s, crossfade-loop"),
    # abyss
    "abyss_wow": ("“World of Water” (NOAA Ocean Exploration)", PD,
                  "NOAA Ocean Exploration wow-1280x720-1; trim 10–34s, crossfade-loop"),
    "abyss_midwaterexp": ("“Midwater Exploration” (NOAA Ocean Exploration)", PD,
                          "NOAA Ocean Exploration midwater-exploration-1920x1080-1; trim 16–40s, crossfade-loop"),
    "abyss_hiding": ("“Hiding in the Dark” (NOAA Ocean Exploration)", PD,
                     "NOAA Ocean Exploration dark-1280x720-1; trim 20–44s, crossfade-loop"),
    # --- Round-4 curated additions (2026-06-28, branch feat/sky-scale-expanded-pools) ---
    # cosmos
    "cosmos_orion": ("Orion Nebula — visible/IR flythrough (NASA SVS)", PD,
                     "NASA SVS 30957 orion_vis_ir_zoom_xfade-1920x1080p30; trim 40–95s, crossfade-loop. Text-free."),
    "cosmos_tarantula": ("Tarantula Nebula (30 Doradus) zoom (NASA/ESA/CSA/STScI)", CCBY_WEBB,
                         "ESA/Webb weic2212b; 1080p, trim 1–59s, crossfade-loop."),
    "cosmos_westerlund": ("Flight through star cluster Westerlund 2 (ESA/Hubble)", "CC-BY 4.0 — credit ESA/Hubble",
                          "ESA/Hubble heic1509f; 1080p, trim 5–59s, crossfade-loop."),
    "cosmos_southernring": ("Southern Ring Nebula zoom (NASA/ESA/CSA/STScI)", CCBY_WEBB,
                            "ESA/Webb weic2207a; 1080p, trim 1–59s, crossfade-loop."),
    "cosmos_carina_eso": ("Carina Nebula zoom (ESO)", "CC-BY 4.0 — credit ESO",
                          "ESO eso1828c; 1080p, trim 0–48s, crossfade-loop."),
    # orbit
    "orbit_aurora2025": ("ISS aurora — Nov 2025 geomagnetic storm (NASA JSC)", PD,
                         "NASA JSC ESRS ISS_20251112_071350; 1080p, trim 5–60s, crossfade-loop."),
    "orbit_citylights": ("ISS night pass — West Coast city lights (NASA JSC)", PD,
                         "NASA SVS 30180 iss028_nighttime_20110819; 720p, trim 0–54s, crossfade-loop."),
    "orbit_helene": ("Hurricane Helene from the ISS — storm + atmospheric limb (NASA JSC)", PD,
                     "NASA JSC jsc2024m000168; trim 5–59s, crossfade-loop."),
    "orbit_epic": ("DSCOVR/EPIC full-disk Earth time-lapse (NASA GSFC)", PD,
                   "NASA SVS 12118 EPIC_timelapse; trim 0–60s, crossfade-loop."),
    "orbit_illuminated": ("“Earth Illuminated” — aurora + Milky Way over the limb (NASA JSC)", "CC0 / public domain",
                          "NASA JSC Earth_Illuminated_120808; trim 68–113s, crossfade-loop."),
    # sky (NEW scale — airplane-window / low-flight, between orbit and coast)
    "sky_grca_templesa": ("Grand Canyon — banking aerial over cliffs & temples (NPS)", PD_NPS,
                          "NPS Grand Canyon b-roll hd10 0121cbe5; 1080p, trim 0–24s, crossfade-loop."),
    "sky_greenland_landice": ("IceBridge — low flight over N. Greenland snowy land ice (NASA SVS)", PD,
                              "NASA SVS 13472 Panasonic 2016 Airborne LandIce; trim 20–60s, vidstab-stabilized, crossfade-loop."),
    "sky_greenland_suture": ("IceBridge — overhead N. Greenland sea-ice patterning (NASA SVS)", PD,
                             "NASA SVS 13472 4K SeaIce 2017 ‘Suture’; trim 10–50s, vidstab-stabilized, crossfade-loop."),
    "sky_jungle_amazon": ("Amazon rainforest canopy — amazon→canopy splice", "Pexels License",
                          "Pexels 31291128 (amazon canopy) → 29780524 (jungle canopy), 1s xfade splice; crossfade-loop."),
    "sky_jungle_waterfall": ("Waterfall plunging out of dense jungle canopy", "Pexels License",
                             "Pexels 8334944; 4K source, trim 6–46s, crossfade-loop."),
    "sky_coast_cliffspain": ("Dramatic Atlantic sea cliffs, Northern Spain — aerial", "Pexels License",
                             "Pexels 36668415; 4K source, trim 1–25s, crossfade-loop."),
    "sky_mtn_castlecrags": ("Castle Crags — gliding past jagged granite spires (US Forest Service)", PD,
                            "USFS/DVIDS 998726 DOD_111563303; trim 120–174s, crossfade-loop."),
    "sky_mtn_rocky": ("Rocky Mountain NP — aerial over alpine summits & tundra (NPS)", PD_NPS,
                      "NPS Rocky Mountain b-roll 188046ff; trim 60–110s, crossfade-loop."),
    # coast
    "coast_kelp": ("Giant kelp forest, underwater", "Pexels License",
                   "Pexels 30285973; 4K source, trim 0–46s, crossfade-loop."),
    "coast_otters": ("Wild sea otters, Elkhorn Slough CA (USFWS)", PD,
                     "USFWS Pacific Southwest b-roll (Wikimedia 28833083527); trim 0–48s, crossfade-loop."),
    "coast_kalaloch": ("Slow surf on rock at sunset, Kalaloch WA", "CC-BY-SA 4.0 — credit Joe Mabel",
                       "Wikimedia ‘Waves crashing on rocks off Beach 4, Kalaloch’; trim 0–46s, crossfade-loop."),
    "coast_seals": ("Harbor seals hauled out on a rock", "Pexels License",
                    "Pexels 6497564; 4K source, trim 8–54s, crossfade-loop."),
    "coast_mist": ("Sea mist over a rocky coastline", "Pexels License",
                   "Pexels 31036891; 4K source, trim 3–57s, crossfade-loop."),
    # reef
    "reef_redsea": ("Red Sea coral reef — clear blue, colorful", "CC-BY-SA 4.0 — credit Kora27",
                    "Wikimedia ‘Korallenriff im Roten Meer DSCF1654’; trim 62–86s, crossfade-loop."),
    "reef_flowergarden": ("Sunlit reef + sergeant majors + jack/scad schools (NOAA Fisheries)", PD,
                          "NOAA Fisheries b-roll VIDEO_ID 1553921798001; trim 8–68s, crossfade-loop."),
    # abyss
    "abyss_bigfin": ("Bigfin squid drifting in black water (NOAA Ocean Exploration)", PD,
                     "NOAA OER Windows to the Deep 2021 dive10-bigfin-squid; trim 34–76s, crossfade-loop."),
    "abyss_dandelion": ("Dandelion siphonophore — glowing orb + threads (NOAA Ocean Exploration)", PD,
                        "NOAA OER 2021 North Atlantic Stepping Stones dive13-dandelion; trim 12–70s, crossfade-loop."),
    "abyss_octopus": ("Deep-sea octopus (Graneledone) crawling (NOAA Ocean Exploration)", PD,
                      "NOAA OER 2023 EXPRESS West Coast; Wikimedia Graneledone_boreopacifica_close-up; trim 6–60s, crossfade-loop."),
    "abyss_seapig": ("Swimming sea cucumber (Enypniastes), CC0 (NOAA)", "CC0 / public domain",
                     "NOAA Okeanos Gulf of Mexico 2014 Ex1402-dive06; trim 0–44s, crossfade-loop."),
}

# --- Affect vocabulary per SCALE (shared by all the scale's pool members) -------
# Each: (key, [x, y], min_level, [tier1 basic .. tier4 compound]). The word shown
# escalates with the RIGHT knob; appears only when both knobs are up (§11.4).
AFFECT: dict[str, list[tuple]] = {
    "cosmos": [
        ("feel.wonder", [0.50, 0.44], 1, ["wow", "wonder", "awe", "transcendent awe"]),
        ("feel.vastness", [0.20, 0.70], 2, ["big", "vastness", "immensity", "a dizzying immensity"]),
        ("feel.insignificance", [0.66, 0.60], 3, ["small", "smallness", "insignificance", "a humbling insignificance"]),
        ("feel.longing", [0.42, 0.84], 4, ["want", "longing", "yearning", "an aching yearning"]),
    ],
    "orbit": [
        ("feel.serenity", [0.50, 0.44], 1, ["calm", "serenity", "peace", "a quiet, weightless peace"]),
        ("feel.fragility", [0.22, 0.68], 2, ["thin", "fragility", "vulnerability", "a tender fragility"]),
        ("feel.unity", [0.66, 0.58], 3, ["one", "unity", "belonging", "a borderless belonging"]),
        ("feel.distance", [0.42, 0.82], 4, ["far", "distance", "remoteness", "an exquisite remoteness"]),
    ],
    "sky": [
        ("feel.exhilaration", [0.50, 0.44], 1, ["whee", "exhilaration", "elation", "a soaring elation"]),
        ("feel.freedom", [0.22, 0.68], 2, ["free", "freedom", "release", "a boundless release"]),
        ("feel.lightness", [0.66, 0.58], 3, ["light", "lightness", "buoyancy", "a weightless buoyancy"]),
        ("feel.vertigo", [0.42, 0.82], 4, ["whoa", "vertigo", "giddiness", "a giddy, falling vertigo"]),
    ],
    "coast": [
        ("feel.ease", [0.50, 0.44], 1, ["nice", "ease", "calm", "an unhurried calm"]),
        ("feel.nostalgia", [0.22, 0.68], 2, ["miss", "nostalgia", "wistfulness", "a salt-air wistfulness"]),
        ("feel.belonging", [0.66, 0.58], 3, ["home", "belonging", "rootedness", "a tidal rootedness"]),
        ("feel.melancholy", [0.42, 0.82], 4, ["sad", "melancholy", "longing", "a soft seaward longing"]),
    ],
    "reef": [
        ("feel.delight", [0.50, 0.44], 1, ["fun", "delight", "joy", "a darting, bright joy"]),
        ("feel.abundance", [0.22, 0.68], 2, ["full", "abundance", "richness", "a teeming richness"]),
        ("feel.curiosity", [0.66, 0.58], 3, ["look", "curiosity", "fascination", "an absorbed fascination"]),
        ("feel.immersion", [0.42, 0.82], 4, ["in", "immersion", "absorption", "a held, breathless absorption"]),
    ],
    "abyss": [
        ("feel.unease", [0.50, 0.44], 1, ["uh", "unease", "disquiet", "a creeping disquiet"]),
        ("feel.fascination", [0.22, 0.68], 2, ["ooh", "fascination", "wonder", "a forbidden wonder"]),
        ("feel.isolation", [0.66, 0.58], 3, ["alone", "isolation", "solitude", "a crushing solitude"]),
        ("feel.dread", [0.42, 0.82], 4, ["fear", "dread", "foreboding", "a slow, cold foreboding"]),
    ],
}


def static_label(key, salience, tiers, box):
    """A fixed-box tiered label (always on-screen, salience-gated by Left)."""
    return {"key": key, "salience": salience, "box": box, "_tiers": tiers}


def tracked_label(key, salience, tiers, appear, disappear, track):
    """A time-windowed tracked label: shows only in [appear, disappear] and its
    box follows the subject (§11.2). `track` = [(t, [x,y,w,h]), ...]."""
    return {
        "key": key, "salience": salience, "appear": appear, "disappear": disappear,
        "track": [{"t": t, "box": b} for t, b in track], "_tiers": tiers,
    }


def measure(key, value, box, min_level):
    """A single-tier measurement readout (not a tiered object), gated by Left."""
    return {"key": key, "box": box, "min_level": min_level, "_tiers": value}


# --- Labels per clip. `_tiers` is lifted into the strings table by build(). ------
# Showcase tracked/windowed labels live on the abyss + reef pools (the creatures
# drift through frame). Other clips get a few salience-gated static tiered labels.
LABELS: dict[str, list[dict]] = {
    # ---------- cosmos ----------
    "cosmos": [
        static_label("detected.nebula", 4, ["cloud", "nebula", "emission nebula", "emission nebula · the Carina star-forming region"], [0.20, 0.42, 0.5, 0.42]),
        static_label("detected.star", 2, ["star", "young star", "protostar", "protostar · a newborn star, <1 Myr old"], [0.54, 0.12, 0.12, 0.14]),
        measure("measure.distance", "≈7,500 ly", [0.06, 0.06, 0.2, 0.08], 3),
    ],
    "cosmos_miri": [
        static_label("detected.nebula", 4, ["dust", "nebula", "dust ridge", "dust ridge · mid-infrared glow of the Carina cliffs"], [0.10, 0.45, 0.7, 0.45]),
        static_label("detected.star", 2, ["star", "young star", "protostar", "protostar · its dust laid bare in mid-infrared"], [0.48, 0.18, 0.14, 0.16]),
        measure("measure.distance", "≈7,500 ly", [0.06, 0.06, 0.2, 0.08], 3),
    ],
    "cosmos_galaxies": [
        static_label("detected.galaxy", 4, ["galaxy", "spiral galaxy", "barred spiral", "barred spiral · ~10¹¹ stars"], [0.34, 0.30, 0.30, 0.34]),
        measure("measure.distance", "~Mly", [0.06, 0.06, 0.18, 0.08], 3),
    ],
    "cosmos_hudf": [
        static_label("detected.galaxy", 4, ["smudge", "galaxy", "early galaxy", "early galaxy · z ≈ 1–6, light Gyr-old"], [0.40, 0.36, 0.20, 0.22]),
        measure("measure.field", "deep field", [0.06, 0.06, 0.2, 0.08], 3),
    ],
    "cosmos_xdf": [
        static_label("detected.galaxy", 4, ["dot", "galaxy", "faint galaxy", "faint galaxy · among the earliest seen"], [0.42, 0.38, 0.18, 0.2]),
    ],
    # ---------- orbit ----------
    "orbit_planetearth": [
        static_label("detected.cloud_band", 4, ["clouds", "cloud band", "cumulus field", "cumulus field · tropical convection"], [0.30, 0.30, 0.30, 0.20]),
        static_label("detected.limb", 2, ["edge", "Earth’s limb", "atmospheric limb", "atmospheric limb · ~100 km of air"], [0.05, 0.70, 0.9, 0.12]),
        measure("measure.altitude", "~408 km", [0.06, 0.06, 0.18, 0.08], 3),
    ],
    "orbit_crewobs": [
        static_label("detected.coastline", 4, ["land", "coastline", "continental margin", "continental margin · land meets sea"], [0.30, 0.34, 0.34, 0.26]),
        measure("measure.altitude", "~408 km", [0.06, 0.06, 0.18, 0.08], 3),
    ],
    "orbit_bluemarble": [
        static_label("detected.globe", 4, ["Earth", "the globe", "terrestrial planet", "terrestrial planet · 12,742 km across"], [0.28, 0.18, 0.44, 0.6]),
    ],
    # ---------- coast ----------
    "coast_birdrock": [
        static_label("detected.surf", 4, ["waves", "surf", "breaking swell", "breaking swell · wind-driven, ~10 s period"], [0.20, 0.55, 0.6, 0.3]),
        static_label("detected.searock", 2, ["rock", "sea stack", "coastal sea stack", "sea stack · wave-cut residual rock"], [0.30, 0.25, 0.22, 0.3]),
    ],
    "coast_surfgrass": [
        static_label("detected.surfgrass", 4, ["grass", "surfgrass", "Phyllospadix", "Phyllospadix · a marine seagrass, not algae"], [0.18, 0.40, 0.5, 0.4]),
        static_label("detected.coralline", 2, ["pink", "coralline algae", "crustose coralline", "crustose coralline · calcified red algae"], [0.6, 0.55, 0.2, 0.2]),
    ],
    "coast_elkbeach": [
        static_label("detected.elk", 4, ["elk", "Roosevelt elk", "Cervus canadensis roosevelti", "C. c. roosevelti · largest elk subspecies"], [0.34, 0.42, 0.3, 0.3]),
        static_label("detected.fog", 2, ["fog", "coastal fog", "advection fog", "advection fog · warm air over cold upwelling"], [0.05, 0.05, 0.9, 0.25]),
    ],
    "coast_drakesbeach": [
        static_label("detected.seal", 4, ["seals", "elephant seals", "Mirounga angustirostris", "M. angustirostris · males to 2,000 kg"], [0.25, 0.55, 0.5, 0.3]),
        static_label("detected.sand", 1, ["sand", "dark sand", "mineral-dark beach", "dark beach · eroded coastal sediment"], [0.05, 0.8, 0.9, 0.15]),
    ],
    # ---------- reef (showcase: tracked + windowed) ----------
    "reef_lionfish": [
        tracked_label(
            "detected.lionfish", 4,
            ["fish", "lionfish", "Pterois volitans", "Pterois volitans · venomous spines, invasive in the Atlantic"],
            0.05, 0.95,
            [(0.05, [0.15, 0.30, 0.16, 0.18]), (0.5, [0.50, 0.38, 0.16, 0.18]), (0.95, [0.72, 0.34, 0.16, 0.18])],
        ),
        tracked_label(
            "detected.snapper", 2,
            ["fish", "blue snapper", "Lutjanus", "Lutjanus · schools over reef structure"],
            0.0, 0.6,
            [(0.0, [0.05, 0.55, 0.12, 0.1]), (0.3, [0.22, 0.5, 0.12, 0.1]), (0.6, [0.40, 0.56, 0.12, 0.1])],
        ),
        measure("measure.depth", "−18 m", [0.06, 0.06, 0.16, 0.08], 3),
    ],
    "reef_spawning": [
        tracked_label(
            "detected.school", 4,
            ["fish", "fish school", "snapper aggregation", "snapper aggregation · synchronized spawning run"],
            0.0, 1.0,
            [(0.0, [0.20, 0.30, 0.2, 0.18]), (0.5, [0.55, 0.34, 0.2, 0.18]), (1.0, [0.20, 0.30, 0.2, 0.18])],
        ),
        static_label("detected.coral", 2, ["coral", "reef coral", "scleractinian", "scleractinian · reef-building stony coral"], [0.15, 0.6, 0.35, 0.3]),
        measure("measure.depth", "−22 m", [0.06, 0.06, 0.16, 0.08], 3),
    ],
    "reef_hawkfish": [
        tracked_label(
            "detected.parrotfish", 4,
            ["fish", "parrotfish", "Bolbometopon muricatum", "B. muricatum · humphead, grazes reef into sand"],
            0.1, 0.9,
            [(0.1, [0.55, 0.25, 0.2, 0.22]), (0.5, [0.35, 0.34, 0.2, 0.22]), (0.9, [0.12, 0.40, 0.2, 0.22])],
        ),
        measure("measure.depth", "−12 m", [0.06, 0.06, 0.16, 0.08], 3),
    ],
    "reef_snapper": [
        tracked_label(
            "detected.snapper", 4,
            ["fish", "red snapper", "Lutjanus campechanus", "L. campechanus · long-lived, can reach 50+ yr"],
            0.0, 0.7,
            [(0.0, [0.10, 0.3, 0.16, 0.14]), (0.35, [0.40, 0.36, 0.16, 0.14]), (0.7, [0.68, 0.30, 0.16, 0.14])],
        ),
    ],
    "reef_coralspacific": [
        static_label("detected.coral", 4, ["coral", "coral colony", "Pacific scleractinian", "Pacific scleractinian · a symbiosis with algae"], [0.2, 0.4, 0.4, 0.4]),
        tracked_label(
            "detected.spotfish", 2,
            ["fish", "spotted fish", "reef damselfish", "reef damselfish · territorial over its coral"],
            0.2, 0.8,
            [(0.2, [0.6, 0.25, 0.1, 0.09]), (0.5, [0.45, 0.3, 0.1, 0.09]), (0.8, [0.3, 0.26, 0.1, 0.09])],
        ),
    ],
    # ---------- abyss (showcase: tracked + windowed; creatures drift through) ----------
    "abyss_wow": [
        tracked_label(
            "detected.jelly", 4,
            ["jelly", "comb jelly", "Ctenophora", "Ctenophora · swims by beating rows of cilia"],
            0.0, 0.55,
            [(0.0, [0.05, 0.25, 0.14, 0.18]), (0.3, [0.30, 0.32, 0.14, 0.18]), (0.55, [0.52, 0.28, 0.14, 0.18])],
        ),
        tracked_label(
            "detected.siphonophore", 3,
            ["chain", "siphonophore", "Siphonophorae", "Siphonophorae · a colony of clones, can exceed 40 m"],
            0.45, 1.0,
            [(0.45, [0.80, 0.6, 0.1, 0.3]), (0.7, [0.6, 0.45, 0.1, 0.34]), (1.0, [0.45, 0.3, 0.1, 0.38])],
        ),
        measure("measure.depth", "−1,200 m", [0.06, 0.06, 0.16, 0.08], 2),
    ],
    "abyss_midwaterexp": [
        tracked_label(
            "detected.medusa", 4,
            ["jelly", "white medusa", "Scyphozoa", "Scyphozoa · the swimming medusa stage of a jellyfish"],
            0.0, 0.6,
            [(0.0, [0.55, 0.20, 0.16, 0.2]), (0.3, [0.40, 0.34, 0.16, 0.2]), (0.6, [0.22, 0.30, 0.16, 0.2])],
        ),
        tracked_label(
            "detected.worm", 2,
            ["worm", "red worm", "polychaete", "polychaete · a free-swimming bristle worm"],
            0.5, 1.0,
            [(0.5, [0.10, 0.7, 0.12, 0.1]), (0.75, [0.3, 0.6, 0.12, 0.1]), (1.0, [0.5, 0.66, 0.12, 0.1])],
        ),
        measure("measure.depth", "−1,600 m", [0.06, 0.06, 0.16, 0.08], 2),
    ],
    "abyss_hiding": [
        tracked_label(
            "detected.jelly", 4,
            ["jelly", "crimson jelly", "Scyphozoa", "Scyphozoa · red is invisible in the lightless deep"],
            0.0, 0.7,
            [(0.0, [0.15, 0.28, 0.16, 0.2]), (0.35, [0.42, 0.36, 0.16, 0.2]), (0.7, [0.66, 0.30, 0.16, 0.2])],
        ),
        measure("measure.depth", "−2,140 m", [0.06, 0.06, 0.16, 0.08], 2),
    ],
    # --- Round-5 left-brain fill (2026-06-30, session 0027): the pool members that
    # shipped with affect (right-brain) but no factual LEFT labels. Static tiered
    # labels (footage not eyeballed); the abyss/reef creature clips could later be
    # upgraded to tracked labels via author mode. ---
    # ---------- cosmos ----------
    "cosmos_orion": [
        static_label("detected.nebula", 4, ["cloud", "nebula", "emission nebula", "emission nebula · M42, the Orion star-forming region"], [0.18, 0.30, 0.6, 0.5]),
        static_label("detected.star", 2, ["star", "young star", "Trapezium star", "Trapezium · hot O-stars lighting the nebula"], [0.44, 0.40, 0.14, 0.16]),
        measure("measure.distance", "≈1,344 ly", [0.06, 0.06, 0.2, 0.08], 3),
    ],
    "cosmos_tarantula": [
        static_label("detected.nebula", 4, ["cloud", "nebula", "Tarantula Nebula", "30 Doradus · the Local Group’s brightest star-forming region"], [0.16, 0.28, 0.64, 0.5]),
        static_label("detected.cluster", 2, ["stars", "star cluster", "R136", "R136 · packs some of the most massive stars known"], [0.42, 0.40, 0.16, 0.18]),
        measure("measure.distance", "≈160,000 ly", [0.06, 0.06, 0.22, 0.08], 3),
    ],
    "cosmos_westerlund": [
        static_label("detected.cluster", 4, ["stars", "star cluster", "Westerlund 2", "Westerlund 2 · a young cluster, ~1–2 Myr old"], [0.30, 0.30, 0.4, 0.4]),
        static_label("detected.star", 2, ["star", "massive star", "O-type star", "O-type · blue-white, tens of solar masses"], [0.46, 0.42, 0.12, 0.14]),
        measure("measure.distance", "≈20,000 ly", [0.06, 0.06, 0.22, 0.08], 3),
    ],
    "cosmos_southernring": [
        static_label("detected.nebula", 4, ["ring", "nebula", "planetary nebula", "NGC 3132 · gas shed by a dying Sun-like star"], [0.24, 0.24, 0.5, 0.5]),
        static_label("detected.star", 2, ["star", "central star", "white dwarf", "white dwarf · the hot stellar core left behind"], [0.46, 0.44, 0.1, 0.12]),
        measure("measure.distance", "≈2,500 ly", [0.06, 0.06, 0.22, 0.08], 3),
    ],
    "cosmos_carina_eso": [
        static_label("detected.nebula", 4, ["cloud", "nebula", "emission nebula", "emission nebula · the Carina star-forming complex"], [0.16, 0.30, 0.64, 0.5]),
        static_label("detected.star", 2, ["star", "young star", "massive star", "massive star · the kind that sculpts the Carina cliffs"], [0.44, 0.40, 0.14, 0.16]),
        measure("measure.distance", "≈7,500 ly", [0.06, 0.06, 0.2, 0.08], 3),
    ],
    # ---------- orbit ----------
    "orbit_aurora2025": [
        static_label("detected.aurora", 4, ["glow", "aurora", "the aurora", "aurora · solar particles exciting upper-atmosphere oxygen"], [0.10, 0.45, 0.8, 0.35]),
        static_label("detected.limb", 2, ["edge", "Earth’s limb", "atmospheric limb", "atmospheric limb · ~100 km of air, glowing on edge"], [0.05, 0.66, 0.9, 0.12]),
        measure("measure.altitude", "~408 km", [0.06, 0.06, 0.18, 0.08], 3),
    ],
    "orbit_citylights": [
        static_label("detected.citylights", 4, ["lights", "city lights", "urban grid", "urban grid · sodium/LED glow tracing the coastline"], [0.20, 0.45, 0.6, 0.4]),
        static_label("detected.airglow", 2, ["band", "airglow", "atmospheric airglow", "airglow · the faint nighttime emission of the upper air"], [0.05, 0.30, 0.9, 0.1]),
        measure("measure.altitude", "~408 km", [0.06, 0.06, 0.18, 0.08], 3),
    ],
    "orbit_helene": [
        static_label("detected.hurricane", 4, ["storm", "hurricane", "tropical cyclone", "tropical cyclone · a warm-core spiral, eye at its center"], [0.22, 0.30, 0.5, 0.5]),
        static_label("detected.limb", 2, ["edge", "Earth’s limb", "atmospheric limb", "atmospheric limb · the thin shell weather lives in"], [0.05, 0.10, 0.9, 0.12]),
        measure("measure.altitude", "~408 km", [0.06, 0.06, 0.18, 0.08], 3),
    ],
    "orbit_epic": [
        static_label("detected.globe", 4, ["Earth", "the globe", "sunlit disk", "full disk · the whole daylit Earth, seen from L1"], [0.24, 0.14, 0.52, 0.66]),
        static_label("detected.cloud_band", 2, ["clouds", "cloud band", "weather systems", "weather systems · swirling across a turning planet"], [0.30, 0.34, 0.34, 0.26]),
        measure("measure.distance", "~1.5M km", [0.06, 0.06, 0.2, 0.08], 3),
    ],
    # ---------- sky ----------
    "sky_grca_templesa": [
        static_label("detected.canyon", 4, ["canyon", "gorge", "the Grand Canyon", "Grand Canyon · ~1.8 Gyr of rock, cut by the Colorado"], [0.10, 0.38, 0.8, 0.52]),
        static_label("detected.strata", 2, ["layers", "rock strata", "sedimentary beds", "strata · stacked epochs of deposition"], [0.15, 0.55, 0.6, 0.25]),
    ],
    "sky_greenland_landice": [
        static_label("detected.icesheet", 4, ["ice", "ice sheet", "the Greenland ice sheet", "ice sheet · up to ~3 km thick, flowing slowly seaward"], [0.08, 0.40, 0.84, 0.5]),
        static_label("detected.snow", 2, ["snow", "snowfield", "firn", "firn · old snow compacting toward glacial ice"], [0.20, 0.20, 0.6, 0.25]),
    ],
    "sky_greenland_suture": [
        static_label("detected.seaice", 4, ["ice", "sea ice", "drift ice", "drift ice · a frozen ocean skin, cracking and refreezing"], [0.08, 0.10, 0.84, 0.8]),
        static_label("detected.lead", 2, ["crack", "lead", "open lead", "lead · a fracture of open water between floes"], [0.30, 0.40, 0.4, 0.2]),
    ],
    "sky_jungle_amazon": [
        static_label("detected.canopy", 4, ["trees", "forest canopy", "the Amazon canopy", "rainforest canopy · among the densest biodiversity on Earth"], [0.06, 0.30, 0.88, 0.6]),
        static_label("detected.emergent", 2, ["tree", "tall tree", "emergent tree", "emergent · a giant breaking above the canopy roof"], [0.42, 0.40, 0.16, 0.2]),
    ],
    "sky_jungle_waterfall": [
        static_label("detected.waterfall", 4, ["falls", "waterfall", "cataract", "cataract · a river plunging off the canopy’s edge"], [0.34, 0.20, 0.3, 0.7]),
        static_label("detected.canopy", 2, ["trees", "jungle canopy", "rainforest canopy", "canopy · dense forest crowding the gorge"], [0.05, 0.30, 0.9, 0.5]),
    ],
    "sky_coast_cliffspain": [
        static_label("detected.seacliff", 4, ["cliff", "sea cliff", "coastal headland", "sea cliff · Atlantic rock cut back by the surf"], [0.10, 0.25, 0.8, 0.55]),
        static_label("detected.surf", 2, ["waves", "surf", "breaking swell", "breaking swell · ocean meeting stone"], [0.15, 0.70, 0.7, 0.25]),
    ],
    "sky_mtn_castlecrags": [
        static_label("detected.spire", 4, ["rock", "spire", "granite spire", "granite spire · a glacier-carved pluton, exhumed and weathered"], [0.30, 0.20, 0.4, 0.6]),
        static_label("detected.forest", 2, ["trees", "conifer forest", "montane forest", "montane forest · cloaking the slopes below the crags"], [0.05, 0.60, 0.9, 0.35]),
    ],
    "sky_mtn_rocky": [
        static_label("detected.summit", 4, ["peak", "summit", "alpine summit", "alpine summit · above tree line, snow-streaked granite"], [0.25, 0.20, 0.5, 0.5]),
        static_label("detected.tundra", 2, ["meadow", "alpine tundra", "alpine tundra", "alpine tundra · low cushion plants above the trees"], [0.10, 0.62, 0.8, 0.3]),
    ],
    # ---------- coast ----------
    "coast_kelp": [
        static_label("detected.kelp", 4, ["kelp", "giant kelp", "Macrocystis pyrifera", "Macrocystis · can grow ~0.5 m a day toward the light"], [0.20, 0.10, 0.6, 0.8]),
        static_label("detected.frond", 2, ["leaf", "blade", "kelp frond", "frond · gas-filled floats hold it upright"], [0.40, 0.30, 0.2, 0.4]),
    ],
    "coast_otters": [
        static_label("detected.otter", 4, ["otter", "sea otter", "Enhydra lutris", "Enhydra lutris · eats ~25% of its weight a day to stay warm"], [0.30, 0.35, 0.4, 0.35]),
        static_label("detected.water", 2, ["water", "estuary", "tidal slough", "tidal slough · sheltered nursery water"], [0.05, 0.05, 0.9, 0.25]),
    ],
    "coast_kalaloch": [
        static_label("detected.surf", 4, ["waves", "surf", "breaking swell", "breaking swell · long-period Pacific swell"], [0.15, 0.50, 0.7, 0.35]),
        static_label("detected.searock", 2, ["rock", "sea stack", "coastal sea stack", "sea stack · wave-cut residual rock"], [0.30, 0.25, 0.3, 0.3]),
    ],
    "coast_seals": [
        static_label("detected.seal", 4, ["seals", "harbor seals", "Phoca vitulina", "Phoca vitulina · hauls out on rock to rest and warm"], [0.20, 0.40, 0.6, 0.35]),
        static_label("detected.searock", 2, ["rock", "haul-out rock", "intertidal rock", "haul-out · a tide-washed resting ledge"], [0.05, 0.6, 0.9, 0.3]),
    ],
    "coast_mist": [
        static_label("detected.mist", 4, ["mist", "sea mist", "advection fog", "advection fog · warm air cooling over cold upwelling"], [0.05, 0.10, 0.9, 0.5]),
        static_label("detected.searock", 2, ["rock", "shore rock", "coastal rock", "coastal rock · the standing edge of the land"], [0.20, 0.55, 0.6, 0.35]),
    ],
    # ---------- reef ----------
    "reef_redsea": [
        static_label("detected.coral", 4, ["coral", "reef coral", "scleractinian", "scleractinian · stony reef-builder in symbiosis with algae"], [0.15, 0.35, 0.7, 0.5]),
        static_label("detected.reeffish", 2, ["fish", "reef fish", "anthias", "anthias · orange clouds of plankton-pickers over the reef"], [0.30, 0.20, 0.4, 0.3]),
        measure("measure.depth", "−10 m", [0.06, 0.06, 0.16, 0.08], 3),
    ],
    "reef_flowergarden": [
        static_label("detected.school", 4, ["fish", "fish school", "jack & scad school", "carangid school · jacks and scad sweeping the reef"], [0.20, 0.25, 0.55, 0.4]),
        static_label("detected.sergeant", 2, ["fish", "sergeant major", "Abudefduf saxatilis", "Abudefduf · the striped “sergeant major” damselfish"], [0.55, 0.45, 0.18, 0.2]),
        measure("measure.depth", "−20 m", [0.06, 0.06, 0.16, 0.08], 3),
    ],
    # ---------- abyss ----------
    "abyss_bigfin": [
        static_label("detected.squid", 4, ["squid", "bigfin squid", "Magnapinna", "Magnapinna · elbowed arms trailing meters into the dark"], [0.30, 0.20, 0.4, 0.65]),
        measure("measure.depth", "−2,000 m", [0.06, 0.06, 0.16, 0.08], 2),
    ],
    "abyss_dandelion": [
        static_label("detected.siphonophore", 4, ["orb", "siphonophore", "dandelion siphonophore", "Rhodaliidae · a colony of clones tethered to the seabed"], [0.32, 0.28, 0.36, 0.4]),
        measure("measure.depth", "−2,500 m", [0.06, 0.06, 0.16, 0.08], 2),
    ],
    "abyss_octopus": [
        static_label("detected.octopus", 4, ["octopus", "deep-sea octopus", "Graneledone", "Graneledone boreopacifica · broods its eggs for ~4.5 years"], [0.25, 0.30, 0.5, 0.45]),
        measure("measure.depth", "−2,500 m", [0.06, 0.06, 0.16, 0.08], 2),
    ],
    "abyss_seapig": [
        static_label("detected.seacucumber", 4, ["blob", "sea cucumber", "Enypniastes eximia", "Enypniastes · a swimming sea cucumber, the “headless chicken”"], [0.30, 0.30, 0.4, 0.4]),
        measure("measure.depth", "−2,700 m", [0.06, 0.06, 0.16, 0.08], 2),
    ],
}

# Human-readable strings for affect/measurement keys are produced from the tiered
# data above; this maps measurement keys to nothing extra (their value IS the string).


def _affect_for_clip(scale: str) -> tuple[list, dict]:
    """Build the affect list + its strings for a scale's pool member."""
    entries, strings = [], {}
    for key, at, min_level, tiers in AFFECT[scale]:
        entries.append({"key": key, "at": at, "min_level": min_level})
        strings[key] = tiers
    return entries, strings


def _labels_for_clip(clip_id: str) -> tuple[list, dict]:
    """Build the annotation list + its strings, lifting `_tiers` into the table."""
    anns, strings = [], {}
    for raw in LABELS.get(clip_id, []):
        a = {k: v for k, v in raw.items() if k != "_tiers"}
        anns.append(a)
        strings[a["key"]] = raw["_tiers"]
    return anns, strings


def _clip_entry(scale: str, clip_id: str) -> dict:
    title, license_, source = META[clip_id]
    anns, lab_strings = _labels_for_clip(clip_id)
    affect, aff_strings = _affect_for_clip(scale)
    return {
        "id": clip_id,
        "title": title,
        "base_file": f"{clip_id}/base.mp4",
        "license": license_,
        "source": source,
        "right_variants": {},
        "annotations": anns,
        "affect": affect,
        "strings": {"en": {**lab_strings, **aff_strings}},
    }


def build_manifest() -> dict:
    """Assemble the full rotating-pool manifest dict."""
    clips = []
    for scale in RING_ORDER:
        for clip_id in POOLS[scale]:
            clips.append(_clip_entry(scale, clip_id))
    scales = [
        {"id": s, "clip_id": POOLS[s][0], "pool": POOLS[s], "audio": SCALE_AUDIO[s]}
        for s in RING_ORDER
    ]
    transitions = []
    for H, L in _adjacent_edges():            # H higher altitude, L lower
        for h in POOLS[H]:
            for l in POOLS[L]:
                transitions.append({"from": h, "to": l,
                                    "file": f"transitions/{h}__{l}.mp4",       # zoom IN (descend)
                                    "model": "placeholder-zoom"})
                transitions.append({"from": l, "to": h,
                                    "file": f"transitions/{h}__{l}.rev.mp4",   # zoom OUT (ascend)
                                    "model": "placeholder-zoom"})
    return {"clips": clips, "ring": {"scales": scales, "transitions": transitions}}


# --- Generate the per-edge transition clips (zoom morph + reversed companion) ---

def _ffmpeg() -> str:
    if shutil.which("ffmpeg"):
        return "ffmpeg"
    import imageio_ffmpeg
    return imageio_ffmpeg.get_ffmpeg_exe()


def _adjacent_edges() -> list[tuple[str, str]]:
    """Ordered (higher, lower) altitude edges around the ring, including the wrap."""
    n = len(RING_ORDER)
    return [(RING_ORDER[i], RING_ORDER[(i + 1) % n]) for i in range(n)]


# All-intra H.264: every frame a keyframe, so the scrub interaction can seek to an
# arbitrary frame smoothly (a sparse GOP scrubs "steppy"). Files grow, but each clip
# stays well under the LFS/proxy ceiling. (Scrub-driven-transitions design §4.)
ALL_INTRA = ["-c:v", "libx264", "-g", "1", "-keyint_min", "1", "-sc_threshold", "0", "-pix_fmt", "yuv420p"]


def transition_cmd(ff: str, a: str, b: str, out: str) -> list[str]:
    """The ffmpeg argv for a forward (zoom-in) member-pair morph, baked all-intra."""
    norm = "trim=0:3,setpts=PTS-STARTPTS,scale=1280:720,fps=25,setsar=1,format=yuv420p"
    return [
        ff, "-y", "-i", str(a), "-i", str(b), "-filter_complex",
        f"[0:v]{norm}[a];[1:v]{norm}[b];"
        "[a][b]xfade=transition=zoomin:duration=1.5:offset=0.75,format=yuv420p[v]",
        "-map", "[v]", "-an", *ALL_INTRA, str(out),
    ]


def reverse_cmd(ff: str, forward: str, out: str) -> list[str]:
    """The ffmpeg argv for the zoom-OUT companion (forward played backward), all-intra."""
    return [ff, "-y", "-i", str(forward), "-vf", "reverse", "-an", *ALL_INTRA, str(out)]


def _make_transition(ff: str, src_clip: str, dst_clip: str) -> Path:
    """A zoom/warp morph between two CLIP MEMBERS' base footage (the well-liked
    orbit-coast recipe), keyed by clip id so every adjacent member pair gets its
    own morph. Forward = zoom IN (descend src->dst)."""
    a = MEDIA / src_clip / "base.mp4"
    b = MEDIA / dst_clip / "base.mp4"
    out = MEDIA / "transitions" / f"{src_clip}__{dst_clip}.mp4"
    out.parent.mkdir(parents=True, exist_ok=True)
    subprocess.run(transition_cmd(ff, str(a), str(b), str(out)), check=True, capture_output=True)
    return out


def _make_reverse(ff: str, forward: Path) -> Path:
    """The zoom-OUT companion of a forward (zoom-in) edge: the same clip played
    backward, written alongside it as `<edge>.rev.mp4`. An ascending ring move
    crosses its edge `reversed`; the renderer then plays this file, so zooming out
    recedes from the current scale back to the higher one instead of zooming in."""
    out = forward.with_suffix(".rev.mp4")
    subprocess.run(reverse_cmd(ff, str(forward), str(out)), check=True, capture_output=True)
    return out


def generate_media() -> None:
    """Bake EVERY adjacent member-pair morph from real bases — forward (zoom in,
    descend) plus its reversed companion (zoom out, ascend), for every pair of
    clips in adjacent altitudes."""
    ff = _ffmpeg()
    for H, L in _adjacent_edges():
        for h in POOLS[H]:
            for l in POOLS[L]:
                fwd = _make_transition(ff, h, l)
                _make_reverse(ff, fwd)
        print(f"generated {len(POOLS[H]) * len(POOLS[L])} {H}__{L} morphs (+ .rev) from real bases")


def main(argv: list[str]) -> None:
    manifest = build_manifest()
    MANIFEST.write_text(json.dumps(manifest, indent=2, ensure_ascii=False) + "\n")
    print(f"wrote {MANIFEST} — {len(manifest['clips'])} clips, "
          f"{len(manifest['ring']['scales'])} ring scales")
    if "--media" in argv:
        generate_media()


if __name__ == "__main__":
    main(sys.argv[1:])
