# Adding Characters to the Orbie Cyberpunk Roster

This is the authoritative extension procedure for `https://orbie-cyberpunk-roster.pages.dev/`. It is written for coding agents operating from the roster project root. The pipeline is:

`character_roster.json → Orbie source GLB → structural/four-view gate → Tripo biped rig → Idle gate → Boast gate → optimized viewer → local/live matrix`

Do not skip gates or claim completion from provider task status alone.

## Non-negotiable rules

- Preserve all accepted characters. A new character is an additional roster row unless the user explicitly requests replacement.
- Treat provider calls as billable side effects. Add and validate the config first; submit only after scope is fixed.
- Never put API keys, signed URLs, STS credentials, or raw secret-bearing responses in source, manifests, guides, or chat.
- Use a new snake-case slug for a new identity. Do not reuse a slug to obtain a fresh generation.
- Require an actual GLB (`glTF` magic), not a filename or MIME claim.
- Inspect front, three-quarter, side, and rear. Reject missing body regions, pedestals, fused limbs, cutaways, floating equipment, or a silhouette that cannot rig.
- Idle is the first deformation gate. Do not spend on Boast until Idle passes.
- A successful Tripo task is not visual acceptance. Check bind, motion samples, full-body framing, and the browser.
- Do not deploy until the full character × motion matrix passes locally.

## Project contract

Only one file defines roster membership and order:

- `character_roster.json`

Each enabled row requires:

```json
{
  "slug": "new_character_slug",
  "name": "New Character Display Name",
  "role": "Short Roster Role",
  "prompt": "Specific identity, fitted materials, readable proportions and biped-safe silhouette.",
  "enabled": true
}
```

Start from `guides/character_spec.template.json`. The config also owns the shared T-pose suffix and verified Orbie topology controls (`quad`, `face_limit`, `smart_low_poly`). Do not add guessed aliases such as `quad_topology`.

This roster's 50k medium-high GLB profile uses `quad: false` and `smart_low_poly: false`. Live probes established the current compatibility matrix: `smart_low_poly: true` terminal-fails these character requests; `quad: true` without smart-low-poly succeeds but returns FBX bytes mislabeled as GLB; disabling both yields real `glTF` bytes. Do not change either switch for this pipeline without a one-job container-magic probe.

`generation_version` participates in the idempotency key for newly added rows. Keep it stable while resuming. Bump it only when intentionally ordering a fresh generation for the same scope after recording why the old attempt was rejected.

## Prerequisites

Required commands:

- Python 3.11+
- `curl`
- Node/npm and `npx`
- Blender at `/Applications/Blender.app/Contents/MacOS/Blender` for the existing QA scripts

Required Python packages for Tripo rigging:

- `requests`
- `boto3`

This checkout includes them in `.venv/`. Before a billable rig stage, run `.venv/bin/python -c 'import requests, boto3'`; use `.venv/bin/python` for `rig_and_animate.py` when the probe passes. Do not assume the system `python3` has the project dependencies, and do not discover that only after starting the stage.

Required environment variables:

- `ORBIE_API_KEY`
- `TRIPO_API_KEY` or `TRIPO3D_API_KEY`

The existing scripts can also read the current Hermes secret files, but portable agents should prefer environment variables. Never print their values.

## Procedure

### 1. Add one config row

Append the new character object to `character_roster.json` in the desired UI order. Keep the identity prompt separate from the shared production suffix; the script appends the suffix.

Run the non-billable contract check:

```bash
python3 guides/validate_character_extension.py --slug new_character_slug
python3 -m py_compile generate_orbie_roster.py rig_and_animate.py build_viewer.py guides/validate_character_extension.py
```

Completion criterion: both commands exit `0`; the slug is unique and enabled.

### 2. Generate the source GLB with Orbie

Run:

```bash
python3 generate_orbie_roster.py
```

For a bounded extension or a fresh retry, target only the reviewed rows:

```bash
python3 generate_orbie_roster.py --slugs new_character_slug,second_character_slug
```

The job runner is resumable. Existing downloaded rows are reused; only config rows absent from `generation_manifest.json` acquire new jobs. It spaces calls, honors `429`, persists job IDs, downloads bytes, and validates GLB magic.

Completion criterion for the new slug:

- `generation_manifest.json.characters.<slug>.status == "downloaded"`;
- `sources/<slug>.glb` exists and starts with `glTF`;
- prompt, job ID, asset ID, byte count, and SHA-256 are recorded without signed URLs.

### 3. Run source structural and visual QA

Structural pass:

```bash
python3 inspect_glbs.py --stage source
```

Four-view render pass:

```bash
/Applications/Blender.app/Contents/MacOS/Blender \
  --background --factory-startup --python-exit-code 1 \
  --python render_source_qa.py -- sources qa_source
python3 make_source_contact_sheet.py
```

Inspect the new source renders with an image-capable reviewer. Check exact identity, full body, T-pose separation, empty hands, rear completeness, real volume, no base, and no hanging geometry that will bridge the legs.

Completion criterion: the source row passes structurally and the four-view review is explicitly accepted. If it fails, keep the rejected evidence, use a new generation version/idempotency scope, and regenerate only that slug.

The runner archives a failed record under `rejected_attempts` when `generation_version` changes, then creates a fresh idempotency key. It refuses prompt drift under the same generation version.

### 4. Rig and produce Idle

Run only the accepted slug:

```bash
python3 rig_and_animate.py --stage idle --slugs new_character_slug
python3 inspect_glbs.py --stage idle
/Applications/Blender.app/Contents/MacOS/Blender \
  --background --factory-startup --python-exit-code 1 \
  --python render_animation_qa.py -- rigging_manifest.json idle qa_idle
python3 make_animation_contact_sheet.py --stage idle
```

The pre-rig result is advisory. The pipeline performs one bounded rig attempt; visual deformation remains authoritative.

Inspect bind plus 0%, 25%, 50%, 75%, and 95% Idle samples. Reject detached props, torso collapse, sheets bridging both legs, missing limbs, or persistent floor penetration. Minor hard-surface flex may be disclosed rather than hidden.

Completion criterion: `rig_status` and Idle `status` are `success`, expected animation `preset:biped:idle` is embedded, and visual Idle QA passes.

### 5. Produce Boast only after Idle passes

Run:

```bash
python3 rig_and_animate.py --stage boast --slugs new_character_slug
python3 inspect_glbs.py --stage boast
/Applications/Blender.app/Contents/MacOS/Blender \
  --background --factory-startup --python-exit-code 1 \
  --python render_animation_qa.py -- rigging_manifest.json boast qa_boast
python3 make_animation_contact_sheet.py --stage boast
```

Inspect both raised hands, shoulders, hip equipment, coat/pouch motion, feet, and full-body framing at the action apex.

Completion criterion: expected animation `preset:biped:victory_celebration` is embedded and the Boast deformation review passes.

### 6. Build the viewer

`build_viewer.py` reads roster order/name/role from `character_roster.json`; no Python roster table needs editing.

Run:

```bash
python3 build_viewer.py
python3 inspect_glbs.py --stage web
python3 guides/validate_character_extension.py --slug new_character_slug --all-stages
```

The builder produces Draco-compressed, WebP-textured, content-hashed GLBs. It fails if either clip is missing, the optimized GLB is invalid, or a hosted model exceeds 20 MiB. It also publishes this guide under `viewer/guides/`.

Completion criterion: the validator exits `0`, `viewer_build_manifest.json.characters` matches enabled config rows, and both hosted motion files are present.

### 7. Run local browser QA

Serve the viewer:

```bash
python3 -m http.server 8793 --directory viewer
```

Open:

- normal deep link: `http://127.0.0.1:8793/?character=new_character_slug&motion=idle`
- exhaustive matrix: `http://127.0.0.1:8793/?qa=1`

The matrix must pass every enabled character in Idle and Boast plus pause, restart, 1.5× speed, and layout. Also inspect the new character manually on desktop and exact `390×844` mobile.

Keep `<model-viewer loading="eager">` in the generated viewer, and scroll the stage into view before the first load in `?qa=1` and `?stage=1` modes. With a long mobile roster, the stage sits below the fold: lazy loading can defer the first model request forever, while an eager but offscreen viewer can load clips yet pause animation time. Either state deadlocks or falsely fails the playback matrix.

Completion criterion: matrix reports `PASS N/N`, browser errors are empty, full body remains visible, and there is no horizontal mobile overflow.

### 8. Deploy and verify

Deploy only after local acceptance:

```bash
npx --yes wrangler@latest pages deploy viewer \
  --project-name orbie-cyberpunk-roster \
  --branch main --commit-dirty=true
```

Record the immutable deployment URL returned by Wrangler. Then verify canonical and immutable bytes:

```bash
python3 verify_deployment.py \
  --base https://orbie-cyberpunk-roster.pages.dev \
  --base https://<deployment-prefix>.orbie-cyberpunk-roster.pages.dev
```

Run `?qa=1` again on the canonical site. Do not claim deployment success from Wrangler alone.

Completion criterion: exact hosted checks pass on both bases, canonical matrix passes, and `/guides/ADDING_CHARACTERS.md` returns the current guide.

## Acceptance checklist

- [ ] New unique enabled config row
- [ ] Source GLB structural pass
- [ ] Four-view source visual pass
- [ ] Tripo biped rig pass
- [ ] Idle structural and deformation pass
- [ ] Boast structural and deformation pass
- [ ] Optimized web GLBs validate and retain expected clips
- [ ] Deep link works
- [ ] Full local roster matrix passes
- [ ] Desktop and 390×844 mobile pass
- [ ] Canonical and immutable byte verification pass
- [ ] Canonical live matrix passes
- [ ] No secret or signed-URL leakage

## Common failure modes

- **New row does not appear:** rebuild after editing `character_roster.json`; do not hand-edit `viewer/index.html`.
- **Duplicate generation:** reuse the persisted idempotency key and manifest. Never delete a live job record just to retry transport.
- **Provider says GLB but bytes are not GLB:** inspect magic. Reject anything not beginning `glTF` for this character pipeline.
- **Rig check says questionable:** one bounded rig attempt is allowed, but visual QA decides.
- **Idle passes, Boast breaks:** keep Idle accepted, reject Boast, and retry only the Boast stage.
- **Viewer loads no animation:** inspect the optimized GLB; animation name must exactly match the preset recorded in `rigging_manifest.json`.
- **Guide disappears after build:** `guides/` must exist before `build_viewer.py`; the builder copies it into `viewer/guides/`.
- **Dirty parent repository:** deploy may be a manual dirty preview. Do not commit or reset unrelated parent-tree changes.
