# EP001 Full Visual Pipeline — starsend-test

End-to-end pipeline run: 9 shots from plan → keyframes → videos.

**Project:** starsend-test
**Episode:** EP001 (9 shots, KIT, DOCKING_BAY_7)
**Working directory:** /Users/joeturnerlin/Dropbox/CLAUDE_PROJECTS/starsend
**Goal:** 9 keyframe images + 9 video files in `projects/starsend-test/output/`

## Phase 1: Fix Pipeline Bugs for Dry-Run

The pipeline has several bugs that prevent a clean dry-run. Fix all of them.

### Bug 1: Manifest init_shots — list vs dict

**File:** `orchestrator/manifest.py`
**Error:** `TypeError: list indices must be integers or slices, not str` at line 267

`init_shots()` does `self._data["shots"][shot_key] = {...}` but `self._data["shots"]` is a list (from JSON). The method expects a dict. Either:
- Initialize `shots` as a dict `{}` instead of a list `[]`
- Or convert list to dict on load

Fix so that `init_shots(["EP001_SH01", "EP001_SH02", ...])` works with string shot IDs.

Also check `update_shot()` and `get_shot()` methods for the same issue — they likely also assume dict access.

### Bug 2: SeedDance stub blocks dialogue shots

**File:** `orchestrator/scene_planner.py`, function `route_shot()`

The router sends dialogue shots (SH02, SH04) to SeedDance (`seeddance-2.0`), but `SeedDanceClient.submit()` raises `NotImplementedError`. These shots need to fall through to the next available video model.

Fix the routing logic so dialogue shots route to Kling V3 (or Veo 3.1) instead of SeedDance until SeedDanceClient is implemented. The cheapest fix: in the plan-shot branch of `route_shot()`, replace `seeddance-2.0` with `kling-v3` for dialogue shots. Or add a model-availability check.

### Bug 3: ENV shots (SH03, SH05) mis-routed to t2v

The dry-run shows SH03 and SH05 routing to `t2v → kling-v3` ("Default single-character shot") but these are ECU shots with 0 characters and `is_env_only: true`. They should route to `still`.

Check `route_shot()` in `orchestrator/scene_planner.py` — the plan-shot branch should detect `is_env_only` and return `still`. Read the full function and trace why SH03/SH05 bypass the ENV check.

### Bug 4: T2V prompt builder may not exist

**File:** `orchestrator/pipeline.py` line 769 imports `from lib.prompt_engine import build_kling_t2v_prompt`

Check if `build_kling_t2v_prompt` exists in `lib/prompt_engine.py`. If not, create it. It should build a text prompt suitable for Kling T2V from the shot's `prompt_data` fields:
- Camera/lens info from `prompt_data.shot_type` + `prompt_data.focal_length`
- Subject from `prompt_data.prompt_skeleton.subject_line`
- Action from `prompt_data.prompt_skeleton.action_line`
- Environment from `prompt_data.prompt_skeleton.environment_line`
- Emotion from `prompt_data.prompt_skeleton.emotion_line`
- Duration from `routing_data.target_editorial_duration_s`

### Bug 5: Video output directory

Verify that video output saves to `projects/starsend-test/output/video/ep_001/` (not `frames/ep_001/`). The I2V and T2V pipelines currently save to `context.output_dir` which is the frames directory. If videos save alongside keyframes, that's fine for now, but check and note the actual behavior.

### Validation

```bash
cd /Users/joeturnerlin/Dropbox/CLAUDE_PROJECTS/starsend && python3 -m orchestrator.pipeline --episode 1 --project starsend-test --dry-run 2>&1; echo "EXIT:$?"
```

Must exit with code 0 and show all 9 shots processed (status: dry_run).

## Phase 2: Generate 9 Production Keyframes

Run the pipeline in keyframe-only mode. Force all shots through the `still` pipeline to generate keyframes before attempting video.

### Instructions

1. First, clear any stale state from previous runs that might cause shot skipping:
   - Check the execution store at `projects/starsend-test/state/starsend/` — if shots are marked as "complete" or "dead" from old runs, they may be skipped. Reset or remove stale state if needed.

2. Run keyframe generation for all 9 shots:
   ```bash
   cd /Users/joeturnerlin/Dropbox/CLAUDE_PROJECTS/starsend && python3 -m orchestrator.pipeline --episode 1 --project starsend-test --tier simple 2>&1
   ```
   Using `--tier simple` forces all shots through the StillPipeline (direct Pro render, no grid planning). This generates a keyframe image for each shot.

3. If any shots fail:
   - Check the error in the log
   - If it's an API error (rate limit, timeout), retry just those shots
   - If it's a code bug, fix the code and retry
   - If it's a prompt issue, check the compiled prompt and fix

4. Verify that the keyframe images look reasonable (non-blank, correct aspect ratio).

### Validation

```bash
cd /Users/joeturnerlin/Dropbox/CLAUDE_PROJECTS/starsend && python3 -c "
from pathlib import Path
out = Path('projects/starsend-test/output/frames/ep_001')
# Count keyframe PNGs (not grids, not scene_refs)
kfs = [f for f in out.glob('*.png') if 'keyframe' in f.name or 'shot_' in f.name]
# We need at least one frame per shot (9 shots)
shot_ids = set()
for f in kfs:
    parts = f.stem.split('_')
    if len(parts) >= 2:
        shot_ids.add(f'{parts[0]}_{parts[1]}')
print(f'Keyframes: {len(kfs)} files, {len(shot_ids)} unique shots')
if len(shot_ids) >= 9:
    print('PASS: All 9 shots have keyframes')
else:
    missing = set(f'shot_{i:03d}' for i in range(1,10)) - shot_ids
    print(f'FAIL: Missing shots: {missing}')
    exit(1)
"
```

## Phase 3: Generate 9 Videos from Keyframes

Generate a video for each of the 9 keyframe images using I2V (image-to-video).

### Instructions

1. For each of the 9 shots, find the best keyframe image in `projects/starsend-test/output/frames/ep_001/`.

2. Generate videos using Veo 3.1 (cheapest video model at $0.05/sec, ~$0.20-0.40/video). Use Veo through the GoogleGenaiClient which is already implemented.

3. Write a standalone script (or use the pipeline's I2V/T2V paths if they work) to:
   - Load each keyframe image
   - Load the shot's prompt/action data from the plan
   - Submit to Veo 3.1 via GoogleGenaiClient for I2V generation
   - Save the resulting video to `projects/starsend-test/output/video/ep_001/`
   - Use the naming convention: `shot_NNN.mp4` (e.g., `shot_001.mp4`, `shot_002.mp4`)

4. If the pipeline's I2V/T2V code paths work after Phase 1 fixes, prefer using those. But if they're blocked by additional bugs, write a focused generation script that:
   ```python
   from lib.api_client import GoogleGenaiClient
   client = GoogleGenaiClient()
   for shot in plan["shots"]:
       payload = {
           "model": "veo-3.1-generate-preview",
           "prompt": shot["compiled_prompts"]["veo_t2v"],
           "start_frame": str(keyframe_path),
           "duration": shot["routing_data"]["target_editorial_duration_s"],
           "aspect_ratio": "9:16",
       }
       job = client.submit(payload)
       result = client.wait_for_job(job, timeout_s=600)
       if result.success and result.video_data:
           video_path.write_bytes(result.video_data)
   ```
   Note: Each shot already has pre-compiled `compiled_prompts.veo_t2v` in the plan JSON.

5. For shots where Veo fails, fall back to Kling V3 (both access key and secret key are available in env).

6. Cost target: ~$2-4 total for 9 videos via Veo 3.1.

### Validation

```bash
cd /Users/joeturnerlin/Dropbox/CLAUDE_PROJECTS/starsend && python3 -c "
from pathlib import Path
vid_dir = Path('projects/starsend-test/output/video/ep_001')
# Also check frames dir in case videos saved there
frame_dir = Path('projects/starsend-test/output/frames/ep_001')
vids = list(vid_dir.glob('*.mp4')) + list(frame_dir.glob('*.mp4'))
# Deduplicate by name
seen = set()
unique = []
for v in vids:
    if v.name not in seen:
        seen.add(v.name)
        unique.append(v)

print(f'Videos found: {len(unique)}')
for v in sorted(unique, key=lambda p: p.name):
    size_kb = v.stat().st_size / 1024
    print(f'  {v.name} ({size_kb:.0f} KB)')

if len(unique) >= 9:
    print('PASS: All 9 videos generated')
else:
    print(f'FAIL: Only {len(unique)} videos found, need 9')
    exit(1)
"
```

## Notes

- All API keys are available: GEMINI_API_KEY, KLING_ACCESS_KEY, KLING_SECRET_KEY, FAL_KEY
- The plan JSON at `projects/starsend-test/state/starsend/plans/ep_001_plan.json` has pre-compiled prompts for each model (previs_flash, keyframe_nbp, veo_t2v)
- Character refs: 2 casting images for KIT in `projects/starsend-test/output/refs/characters/kit/`
- Location refs: shot-aware wide + mid views in `projects/starsend-test/output/refs/locations/`
- Expression refs: 27-image universal matrix in `starsend/output/refs/expressions/`
- Cost estimate: ~$1.21 keyframes (NBP) + ~$2-4 videos (Veo) = ~$3-5 total
