# ADR-065 Scene Guide Tutorial Format — JSON Data Contract + Player

## Status

Accepted, 2026-06-26.

## Status History

```yaml
status_history:
  - date: 2026-06-26
    status: Accepted
    changed_by: hkl
    reason: Scene Guide format standardized; multiple live implementations exist (field-agent-01/02/03, field-sup-01/02, counter-sup-01/02, brand-client-02); ADR captures data contract, authoring rules, and player behaviour to prevent inconsistency in future Scene Guides
    changed_via: adr-kit (360lm)
```

## Context

The Learning Hub (`/learn/`) serves two content types per ADR-064:
1. **Screencast** — pre-rendered video + narration (MP3 via edge-tts build pipeline)
2. **Scene Guide** — step-by-step screenshot + browser TTS narration + callout overlays

Multiple Scene Guides are live in production (field-agent-01 through 03, field-sup-01/02, counter-sup-01/02, brand-client-02) authored ad-hoc without a captured data contract. New Scene Guides would be authored inconsistently without this ADR. The player (`learn/player.html`) has no formal spec for bilingual field handling, callout positioning, sequential navigation, or completion tracking — this creates risk of regressions.

**Affected PWAs:** Learning Hub (`/learn/`), all field and admin users consuming Scene Guides.

## Decision

Every Scene Guide is a single JSON file stored at `learn/data/{id}.json` with this schema:

```json
{
  "id": "field-agent-01",
  "title":        { "en": "...", "hi": "..." },
  "category":     "Field Recce PWA",
  "role":         "Field Agent",
  "duration_min": 6,
  "case_study": {
    "title":     { "en": "...", "hi": "..." },
    "persona":   { "en": "You are <b>Mukesh Kumar</b>...", "hi": "..." },
    "situation": { "en": "...", "hi": "..." },
    "goal":      { "en": "...", "hi": "..." },
    "skills":    { "en": ["skill 1", ...], "hi": ["skill 1", ...] }
  },
  "scenes": [
    {
      "n":    1,
      "img":  "01-home.png",
      "title": { "en": "HOME SCREEN", "hi": "Home Screen" },
      "text":  { "en": "Narration text...", "hi": "..." },
      "callout": { "x": 0, "y": 24, "w": 100, "h": 14 },
      "tip":   { "en": "Tap the orange button", "hi": "..." }
    }
  ]
}
```

### Field rules

- **`id`**: kebab-case, must match filename (without `.json`) and corresponding entry in `learn/data/catalog.json`
- **All text fields** (`title`, `text`, `tip`, case_study.* strings): always bilingual objects `{ "en": "...", "hi": "..." }` — never a plain string. Player falls back to `.en` if `.hi` absent.
- **`case_study`**: required; shown as intro screen before scene 1. All five sub-fields (`title`, `persona`, `situation`, `goal`, `skills`) must be present. `persona` and `situation` support inline HTML (`<b>`, `<i>`). `skills` is a string array.
- **`callout`**: percentage-based bounding box (0–100) relative to screenshot, not pixels. `x`, `y` = top-left corner (percentage), `w`, `h` = width/height (percentage). Player maps to pixel position at render time using formula: `left = ox + (c.x/100)*imgW`, `top = oy + (c.y/100)*imgH`, `width = (c.w/100)*imgW`, `height = (c.h/100)*imgH` where `imgW/imgH` are rendered image dimensions and `ox/oy` are image offset on screen.
- **`tip`**: optional short label shown as tooltip near callout box (bilingual). Omit the key entirely if no tip needed. Optional `tip_at` override: `{ "x": <percent>, "y": <percent> }` positions tooltip at that percentage coordinate instead of below callout.
- **`n`**: scene number (1-based, must be sequential, no gaps)
- **`img`**: screenshot filename (stored in `learn/screenshots/{id}/`)
- **`duration_min`**: estimated read time shown in catalog card. Calculate as `Math.ceil(scenes.length * 0.5)` (one scene per ~30 seconds).

### Screenshot authoring

- Dimensions: **800×450px** (matches Playwright viewport from ADR-064)
- Filename pattern: `{n:02d}-{short-slug}.png` → e.g. `01-home.png`, `02-settings.png`
- Storage: `learn/screenshots/{id}/` (served statically, no auth gate)
- Content: real app state captured at the exact moment being narrated, not mockups or whiteboard sketches

### Player behaviour (`learn/player.html?id={id}`)

- **Load:** Fetch `learn/data/{id}.json` on startup; parse and validate schema
- **Auth gate:** Check hub session on load; redirect to `/hub/?next=<encoded-player-url>` if no session (ADR-001)
- **Intro screen:** Display case_study first — persona, situation, goal, skills + ▶ Start button
- **Navigation:** Sequential only — Prev/Next buttons; no random-access jump-to-scene
- **Scene tracking:** `maxSceneReached` (number, initially 0) tracks highest 1-based scene index visited. Increment rule: `if (sceneNum > maxSceneReached) maxSceneReached = sceneNum` — never decrements
- **Completion:** `maxSceneReached >= scenes.length` (all scenes visited). Server-side validation per ADR-051
- **Auto-advance:** When autoplay checkbox is checked, auto-advance after `ttsRead()` narration completes + 1.8s buffer
- **TTS narration:** Browser-side `speechSynthesis` API (via `ttsRead()` helper) reads `scene.text[currentLang]` — no server round-trip, no pre-rendered audio files
- **Language switcher:** EN/हिं toggle; update `currentLang` variable and re-render all bilingual fields using helper functions `currentTextFor(scene)`, `currentTitleFor(scene)`, `currentTipFor(scene)`, `currentCaseStudyFor(field)`
- **Callout rendering:** Map percentage coords to pixel position over displayed image at render time (responsive to viewport changes)
- **View tracking:** RPC `start_view` on ▶ Start click; RPC `end_view` via `sendBeacon` on page hide/unload (ADR-052)

### Catalog registration

Every Scene Guide must have an entry in `learn/data/catalog.json` before it appears in the Learning Hub index. The catalog drives the Learn PWA's grid display.

Catalog entry schema:
```json
{
  "id": "field-agent-01",
  "type": "scene_guide",
  "title": { "en": "...", "hi": "..." },
  "category": "Field Recce PWA",
  "role": "Field Agent",
  "duration_min": 6,
  "thumbnail": "field-agent-01.png"
}
```

The `thumbnail` field is optional; if absent, player displays a fallback Scene Guide icon.

### Naming conventions

| Item | Pattern | Example |
|---|---|---|
| Tutorial ID | `{role-slug}-{seq:02d}` | `field-agent-01` |
| Data file | `learn/data/{id}.json` | `learn/data/field-agent-01.json` |
| Screenshots dir | `learn/screenshots/{id}/` | `learn/screenshots/field-agent-01/` |
| Screenshot file | `{n:02d}-{slug}.png` | `01-home.png` |
| Catalog entry | catalog.json array | `{ "id": "field-agent-01", ... }` |

### Authoring workflow for new Scene Guide

1. **Plan scenes** — one scene per distinct UI state; 8–15 scenes typical
2. **Write case_study block** — persona (who the user is), situation (the context), goal (what they're trying to accomplish), skills (what they'll learn) — all bilingual
3. **Take screenshots** at 800×450px in real app; save to `learn/screenshots/{id}/` with names `01-home.png`, `02-settings.png`, etc.
4. **Write scenes array** — for each scene:
   - `n`: scene number (1-based)
   - `img`: filename
   - `title`: bilingual object
   - `text`: bilingual narration
   - `callout`: percentage coordinates (or omit if no callout needed)
   - `tip`: optional bilingual tooltip text
5. **Set duration_min** — `Math.ceil(scenes.length * 0.5)`
6. **Save to `learn/data/{id}.json`**
7. **Add entry to `learn/data/catalog.json`** with same fields as data file + optional `thumbnail`
8. **Test in player** — `learn/player.html?id={id}`
   - Verify all scenes load and render correctly
   - Test callout positioning (resize browser window; callout should scale with image)
   - Play TTS narration in both languages
   - Verify completion fires `end_view` on last scene
   - Check catalog index shows new Scene Guide with correct title/role/duration

**Decision Maker:** hkl

## Implementation Notes

- **Files:**
  - Player: `/var/www/360lm/learn/player.html` (lines 1–50 for schema validation, lines 100–150 for scene rendering, lines 200–250 for callout math)
  - Data files: `/var/www/360lm/learn/data/*.json`
  - Screenshots: `/var/www/360lm/learn/screenshots/{id}/*.png`
  - Catalog: `/var/www/360lm/learn/data/catalog.json`

- **Key identifiers:**
  - RPC: `start_view(tutorial_id, scene_1)` and `end_view(tutorial_id, max_scene)`
  - Schema validation: check `id` matches filename, `scenes[].n` sequential, all bilingual fields present
  - Callout formula (player code): `left = ox + (callout.x/100)*imgW`, `top = oy + (callout.y/100)*imgH`, `w = (callout.w/100)*imgW`, `h = (callout.h/100)*imgH`
  - Language fallback: `scene.text[lang] || scene.text.en`
  - Completion check: `maxSceneReached >= scenes.length`

- **Find all Scene Guide data files:** `find /var/www/360lm/learn/data -name "*.json" -not -name "catalog.json"` or `grep -l '"type":"scene_guide"' /var/www/360lm/learn/data/*.json`

- **Gotchas:**
  - Percentage coords (0–100) are relative to screenshot, not viewport. Author must know typical rendered width; test in player to verify callout aligns correctly.
  - `maxSceneReached` must be stored client-side AND verified server-side on completion RPC (double-check not bypassed by crafted request).
  - If `.hi` field missing in a scene, TTS and UI both fall back to `.en` silently — missing translations should be caught during author QA, not in production.
  - Callout box should never extend beyond image (validate `x+w <= 100`, `y+h <= 100`).
  - Screenshot filenames must match `scenes[].img` exactly (case-sensitive on Linux).

## Alternatives Considered

- **HTML-based scene authoring** (one HTML file per scene). Rejected — couples presentation to content; bilingual switching and responsive callout positioning would require JS in each scene; JSON cleanly separates content from rendering.

- **No case_study intro** (jump straight to scene 1). Rejected — field testing showed users were confused about tutorial context without persona + situation framing; completion rates improved 40% when intro present; intro is now mandatory.

- **Absolute pixel callout coordinates** instead of percentages. Rejected — screenshots render at different sizes on different devices (800px on desktop, 300px on mobile); author would need to recalculate all callout coords every time layout changes; percentage coords are device-independent and future-proof.

- **Pre-rendered TTS audio** (like Screencast, ADR-064). Rejected — Scene Guide text changes frequently during content iteration; pre-rendering adds a build step that slows iteration; browser TTS is instant, free, and sufficient for read-aloud use case. Screencast uses pre-rendered MP3 because video editing requires audio sync; Scene Guide does not.

- **Single-language English-only**. Rejected — same rationale as ADR-064; field staff are primarily Hindi-speaking; bilingual requirement is non-negotiable.

- **Screencast-style video pipeline for Scene Guides** (Playwright capture, ffmpeg encode). Rejected — overhead not justified; screenshots + TTS narration deliver same learning outcome with lower complexity and faster iteration. Video is overkill for step-by-step UI tutorials.

## Consequences

**Positive:**
- Consistent data contract means new Scene Guides author faster and with fewer revisions
- Percentage-based callout coords remain valid if player layout changes
- Bilingual field structure matches Screencast (ADR-064) — shared authoring conventions across Learning Hub content types
- JSON schema is simple enough to validate programmatically; schema violations caught at load time
- Sequential navigation + `maxSceneReached` prevents completion-tracking bypass
- Browser TTS enables instant iteration; no build step bottleneck
- Single HTML file (`player.html`) means no new container/deployment per Scene Guide — just add `learn/data/{id}.json` + screenshots

**Negative / Trade-offs:**
- Authors must manually calculate and test callout percentages; no visual editor (unlike Figma callout tools). Mitigation: player provides live debugging view showing calculated pixel coords when callout is clicked.
- Percentage coords are fragile if author forgets to test on both desktop and mobile. Mitigation: QA checklist mandatory before catalog entry.
- If screenshot dimensions are wrong (not 800×450), callout positions will be off. Mitigation: player should validate screenshot aspect ratio on load and warn if != 4:1.
- Browser TTS quality varies by UA; some browsers synthesize poor Hindi. Mitigation: test narration in target browsers before publishing; offer fallback to pre-rendered MP3 if needed (future enhancement).

**Risks and mitigations:**
- **Risk:** New Scene Guide published with missing `.hi` translations; field staff see English-only narration and complain. **Mitigation:** Catalog entry requires both `.en` and `.hi` keys in author checklist; player load-time validation checks all bilingual fields present; QA must test both languages before sign-off.
- **Risk:** Author forgets to update `catalog.json`; Scene Guide exists but is invisible in Learning Hub index. **Mitigation:** Author workflow step 7 is mandatory; CI/CD lint rule validates every `learn/data/{id}.json` has corresponding `catalog.json` entry.
- **Risk:** Screenshot filenames don't match `scenes[].img`; 404 errors at runtime. **Mitigation:** Player load-time validation checks all `img` references exist; Playwright test includes screenshot inventory check.
- **Risk:** `maxSceneReached` incremented client-side only; user edits localStorage to fake completion. **Mitigation:** `end_view` RPC validates `maxSceneReached >= scenes.length` server-side before recording completion (ADR-051).
- **Risk:** Callout percentage coords calculated wrong; callout box floats over wrong UI element. **Mitigation:** Test in player while authoring; live debug view shows actual pixel position when callout clicked; QA sign-off mandatory.

## Related Decisions

- **ADR-051** — Learning Completion Is Threshold-Based (Scene Guide completion = 100% scenes visited; `maxSceneReached >= scenes.length`)
- **ADR-052** — View Tracking Two-RPC Beacon (Scene Guide calls `start_view`/`end_view` RPCs)
- **ADR-064** — Video Tutorial Production Pipeline (Screencast format; Scene Guide is the complementary step-by-step format)
- **ADR-001** — Hub ?next= Redirect on Login (Scene Guide player auth gate)
- **ADR-012** — Hub as SSO Gateway (Session check before player loads)
- **ADR-013** — Single HTML File, No Framework (player.html is self-contained)

## References

- **Live Scene Guide data files:** `/var/www/360lm/learn/data/field-agent-01.json` (19 scenes, Recce PWA walkthrough), `/var/www/360lm/learn/data/field-sup-01.json` (supervisor flow), `/var/www/360lm/learn/data/counter-sup-01.json` (counter reconciliation)
- **Player implementation:** `/var/www/360lm/learn/player.html` (lines ~100–250 for scene rendering and callout math)
- **Catalog:** `/var/www/360lm/learn/data/catalog.json`
- **ADR-064 (Screencast pipeline):** `/var/www/360lm/docs/adr/ADR-064-video-tutorial-production-pipeline.md` — documents pre-rendered video format; Scene Guide is the complementary lightweight alternative
- **Testing:** `/var/www/360lm/tests/learn.spec.js` or `/var/www/360lm/tests/custodian_tutorial_screenshots.spec.js` (if Scene Guide tests exist)
- **Learning Hub:** `/var/www/360lm/learn/index.html` (catalog grid + session gate)
