--- name: author-tutorial description: > Author an interactive tutorial, guided lesson, onboarding flow, coach-mark tour, or objectives checklist in LunCoSim. Use for work under `assets/tutorials/` and for requests involving `mission`, `objective`, `coach_step`, `hint`, or `spotlight`. --- # Author an authored lesson A lesson is a file-backed Rhai scenario with optional standard USD scene content. The application Rhai policy reads generic JSON asset snapshots, selects the tutorial catalog, and builds the workbench menu. The shared Rust asset and UI layers know only text assets, JSON values, and generic menu trees. The lesson does not need a Rust type, registry, lifecycle owner, or custom USD schema. Read [`author-scenario`](../author-scenario/SKILL.md) first, then use [`assets/tutorials/README.md`](../../assets/tutorials/README.md) and the examples under `assets/tutorials/`. ## Add a lesson 1. Add `assets/tutorials//.rhai`. 2. If it needs a world, reuse or add an authored scene under `assets/`. 3. Add an entry to `assets/tutorials/catalog.json`: ```json { "track": "Sandbox", "title": "First Drive", "blurb": "Take control of a rover and drive it to a lunar flag.", "difficulty": "beginner", "source_asset": "lunco://tutorials/sandbox/first_drive.rhai", "scene_asset": "lunco://tutorials/sandbox/first_drive.usda" } ``` The `track` value determines the submenu containing the lesson. Reuse an existing track when the lesson belongs to that learning path. The Rhai policy at `assets/scripting/policy/application_asset_lifecycle.rhai` discovers the unique marked catalog from generic asset-scope events and uses the shared `parse_json` function; do not add tutorial-specific Rust loading or menu code. The menu submits the generic `RunScenarioAsset` command. It uses `ScenarioReloadPolicy::Restart` for a predictable fresh start and lets the command resolve an omitted host target to the active `WorldRoot`. Other apps can reuse the command without importing tutorial code. ## Rhai contract Use the shared prelude: - `hint(...)`, `spotlight(anchor, caption)`, and `notify_kind(...)` for presentation; - `coach_step(steps, index)` with an `on_event` cursor for a guided tour; - `mission(me, ctx)` and `objective(...)` for an objective-driven exercise; - `input_binding(...)`/`input_hint(...)` for the controller-owned semantic labels. In LunCoSim, mission objectives are shown in View. Tutorial hints and actions remain visible in every perspective, as do spotlight and coach-step overlays; use those overlays to guide learners toward Builder or Editor panel anchors. Use `panel.center` for the viewport or central work area, a generic `panel.side_browser`, `panel.right_inspector`, or `panel.bottom` anchor for a whole dock, and `panel.` for a specific panel. Choose an anchor published by the active layout; `coach_step` focuses a named panel before drawing its spotlight. Progression must observe semantic commands or authoritative state. Never gate a lesson on a physical key name or a timer. A lesson must not open a USD layer directly; if it needs a world, the catalog's `scene_asset` is the request. Never branch on the Rust build profile (`is_debug()` or `debug_assertions`). The authored `lint.rhai` policy rejects that coupling; use `is_unattended()` when attended and automated execution need different behavior. For physical waypoint arrival, author an Avian sensor in USD and map its generic `SENSOR_ENTER` event to a lesson event in Rhai. Match `evt.source` to the exact sensor with `sensor_entered(evt, sensor_id)`, then validate that `evt.value` identifies the intended subject or one of its colliders before emitting the lesson event. Use `SensorOccupants` in `on_start` to handle an existing contact. Objectives and task waits consume the lesson event; distance thresholds do not define arrival. Do not add a tutorial-specific `TriggerZone` label to a shared sensor asset. Example objective: ```rhai fn mission(me, ctx) { [ objective("possess", #{ text: "Select the rover to take control", requires_event: "cmd:AcquireControl", }), objective("reach_flag", #{ text: "Drive to the glowing flag", requires: ["possess"], requires_event: "flag_reached", }), ] } ``` ## USD scene contract Scene ownership stays in the USD scene command layer. `RunScenarioAsset` submits a `SceneTransitionIntent`; the USD owner resolves and composes it, and the generic scenario driver waits for the completion/readiness edge. Use standard USD composition and schemas: `subLayers`, `references`, `payloads`, `UsdPhysics`, and `UsdLux`. For a repeatable celestial lesson, author a non-zero epoch on the scene root with the celestial payload. Otherwise the startup-installed scene-time policy selects current computer UTC converted to TDB after the scene and its queued projections settle. Time-dependent consumers wait for that result. Missing time with celestial sources is reported by runtime warning and lint. For a basic or UI lesson, author a fixed `DistantLight` or omit the world. Do not add tutorial-specific API schemas. ## Test without a Rust rebuild Put behavior assertions in `assets/scenarios/tests/.rhai` and use the production scene-test binary. Keep Rust tests limited to generic scripting, asset, USD, and lifecycle seams. ```bash "$LUNCOSIM_BIN" test \ --scene scenes/tests/tutorial_first_drive.usda --max-ticks 6000 ``` `--validate` proves preflight only. Inspect the authored verdict and process exit code for runtime evidence. Rhai, catalog, and authored USD edits should be replayed through the already-built production binary whenever possible.