236 lines
11 KiB
Markdown
236 lines
11 KiB
Markdown
# Architecture — HS_DollyCam
|
||
|
||
## System Overview
|
||
|
||
HS_DollyCam is a multi-script Second Life HUD system. The Controller resides in the ROOT prim of a single-prim HUD; all other scripts are linked children. Communication is exclusively via `llMessageLinked` with integer channel codes.
|
||
|
||
## Component Diagram
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ HS_CamController.lsl (ROOT) │
|
||
│ ┌───────────────────────────────────────────────────────┐ │
|
||
│ │ Chat parser (/88) │ Preset manager │ State mgmt │ │
|
||
│ └───────────────────────────────────────────────────────┘ │
|
||
│ │ │ │ │ │ │
|
||
│ ┌────┘ │ │ │ └────┐ │
|
||
│ ▼ ▼ ▼ ▼ ▼ │
|
||
│ ┌─────┐ ┌──────────┐ ┌────────┐ ┌────────┐ ┌──────┐ │
|
||
│ │ Menu │ │Playlist │ │Engine │ │Markers │ │ FOV │ │
|
||
│ │ │ │ │ │Tour │ │ │ │ │ │
|
||
│ └──┬───┘ └────┬─────┘ └───┬────┘ └───┬────┘ └──┬───┘ │
|
||
│ │ │ │ │ │ │
|
||
│ └ └ └ └ ┘ │
|
||
│ ▼ ▼ ▼ │
|
||
│ ┌─────────────────────────────────┐ │
|
||
│ │ HS_CamEngineCore.lsl │ │
|
||
│ │ Camera permissions, │ │
|
||
│ │ llSetCameraParams, │ │
|
||
│ │ Follow/Lock math │ │
|
||
│ └──────────────┬──────────────────┘ │
|
||
│ │ │
|
||
│ ┌──────┴──────┐ │
|
||
│ │ SL Viewer │ │
|
||
│ │ (Camera) │ │
|
||
│ └─────────────┘ │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
## Component Details
|
||
|
||
### 1. HS_CamController.lsl (ROOT — Main Orchestrator)
|
||
|
||
**Responsibility:** Central command router and state coordinator.
|
||
|
||
**Key responsibilities:**
|
||
- Chat command parsing on channel 88 (`/88 help`, `/88 save`, `/88 load`, `/88 moveto`, `/88 tour`, etc.)
|
||
- Preset management via Linkset Data (`P1`, `P2`, … keys)
|
||
- State persistence: Follow/Lock state, Marker visibility, Camera state
|
||
- Routing all high-level commands to specialized helper scripts
|
||
- Demo mode enforcement (`DEMO_MAX_SLOTS`)
|
||
|
||
**Key constants:**
|
||
- `CH = 88` — Chat channel
|
||
- `CE_CMD_*` — Engine command channels (1000–1060)
|
||
- `CE_EVT_*` — Engine event channels (2000–2060)
|
||
- `PH_CMD_*` — Playlist command channels (6100–6104)
|
||
- `MC_CMD = 5100` — Marker command channel
|
||
- `MN_CMD = 5200` — Menu command channel
|
||
|
||
**Notable patterns:**
|
||
- `loadPreset(idx)` — reads Linkset Data, extracts position/focus/FOV into temp globals
|
||
- `packPreset(pos, foc, rot, fovRad)` — serializes camera state to pipe-delimited string
|
||
- `engineMove(pos, foc, durMs)` — sends `CE_CMD_MOVE` to Engine, hides HUD during animated moves
|
||
- `phStop(reason)` — forwards `PH_CMD_STOP` to all helpers
|
||
|
||
### 2. HS_CamPlaylist.lsl (Notecard-Driven Playlist Engine)
|
||
|
||
**Responsibility:** Asynchronous notecard reading and playlist execution.
|
||
|
||
**Key responsibilities:**
|
||
- Reads playlist notecards line-by-line
|
||
- Supports `moveto`, `goto`, `load`, `wait`, `tour...endtour`, `dollyzoom`, `fov`, `lock`, `follow` commands
|
||
- Builds `CE_CMD_TOUR` payloads for tour blocks
|
||
- Uses `llGetNotecardLineSync` with `NAK` fallback
|
||
- Compact notecard tours avoid slow multi-line notecard reads
|
||
|
||
**Memory optimization:**
|
||
- `loadPreset()` uses targeted field parsing (not full `llParseString2List`)
|
||
- Tour blocks store indices first, load position/focus at `endtour`
|
||
- Token helpers instead of full `llParseString2List(line, [" "], [])`
|
||
|
||
### 3. HS_CamTourCommands.lsl (Secondary Tour Command Helper)
|
||
|
||
**Responsibility:** Chat/menu one-shot tour builders.
|
||
|
||
**Key responsibilities:**
|
||
- Handles `/88 tour ...`, `/88 dollyzoom ...`, menu `TOURRUN` payloads
|
||
- Builds `CE_CMD_TOUR` payloads without adding memory pressure to Playlist
|
||
- Targeted preset parsing for position/focus/FOV fields
|
||
|
||
**Design principle:** Keep heavy tour parsing out of Playlist; this script absorbs the memory cost.
|
||
|
||
### 4. HS_CamEngineTour.lsl (Continuous Tour Runtime)
|
||
|
||
**Responsibility:** High-frequency multi-waypoint camera tour playback.
|
||
|
||
**Key responsibilities:**
|
||
- Receives `CE_CMD_TOUR` from Playlist and TourCommands
|
||
- Requests current camera state from Core via `CE_CMD_GET_STATE`
|
||
- Builds runtime lists: positions, focuses, holds, weights, segment lengths, cumulative lengths, point times
|
||
- Drives camera via `CE_INT_SET_CAM` to EngineCore
|
||
- Implements Catmull-Rom spline interpolation and trapezoidal motion profiles
|
||
- Throttled camera-frame sending via `tourSendCam()` at ~30Hz
|
||
|
||
**Memory-critical decisions:**
|
||
- Active tour lists stay in script memory (never Linkset Data)
|
||
- Segment caching reduces repeated `llList2Vector` access
|
||
- `CE_CMD_TOUR` payload parsed with targeted pipe-field helpers
|
||
|
||
### 5. HS_CamEngineCore.lsl (Camera Engine Core)
|
||
|
||
**Responsibility:** Low-level camera interface to Second Life viewer.
|
||
|
||
**Key responsibilities:**
|
||
- Camera permissions (`PERMISSION_CONTROL_CAMERA`, `PERMISSION_TRACK_CAMERA`)
|
||
- Executes `llSetCameraParams` for all camera movement
|
||
- Follow mode math (World/Local/Yaw coordinate frames)
|
||
- Lock mode math (tracks target position)
|
||
- Configuration dump consumed by Playlist and TourEngine
|
||
|
||
**Hot path:** `CE_INT_SET_CAM` parsing uses direct separator lookup (no `llParseString2List`).
|
||
|
||
**Tour-related config keys:**
|
||
- `tour_max_points=20` — Max waypoints per segment
|
||
- `tour_cam_min_interval=0.033` — ~30Hz camera update cap
|
||
- `tour_pos_epsilon=0.005` — Position change threshold
|
||
- `tour_focus_epsilon=0.005` — Focus change threshold
|
||
|
||
### 6. HS_CamMenu.lsl (Dialog Menu / UI)
|
||
|
||
**Responsibility:** Touch-based dialog menus and UI command dispatch.
|
||
|
||
**Key responsibilities:**
|
||
- Dialog menus with page state
|
||
- Menu-driven tour building
|
||
- Nearby target lists (avatars/objects)
|
||
- UI command dispatch via `MN_CMD` to Controller
|
||
|
||
**Design note:** Menu paths are secondary to notecard playlist workflows.
|
||
|
||
### 7. HS_CamMarkers.lsl (Visual Marker Helper)
|
||
|
||
**Responsibility:** Rez marker pyramids at preset positions.
|
||
|
||
**Key responsibilities:**
|
||
- Rezzes `HS_CamMarker` objects at preset positions
|
||
- Uses `MARKER_CH = -880088` region channel
|
||
- Uses `llRegionSayTo` for marker communication when keys are known
|
||
- Mixed marker list (known to be less memory-efficient, but marker use is rare)
|
||
|
||
### 8. HS_CamFov.lsl (FOV Controller)
|
||
|
||
**Responsibility:** Field of View adjustments and FOV state synchronization.
|
||
|
||
**Key responsibilities:**
|
||
- FOV clamping (`RLV_FOV_MIN_DEG=10`, `RLV_FOV_MAX_DEG=179`)
|
||
- FOV state sync across scripts
|
||
- RLVa integration
|
||
|
||
## Data Flow
|
||
|
||
### Preset Save Flow
|
||
```
|
||
User: /88 save 3
|
||
→ Controller: requests camera state from Core (CE_CMD_GET_STATE)
|
||
← Core: returns current pos/focus/rot (CE_EVT_STATE)
|
||
→ Controller: packs preset, writes to Linkset Data (P3)
|
||
← Controller: confirms save to user
|
||
```
|
||
|
||
### Smooth Move Flow
|
||
```
|
||
User: /88 moveto 5 2500
|
||
→ Controller: phStop() (stops any active playlist/tour)
|
||
→ Controller: engineMove(<pos5>, <foc5>, 2500)
|
||
← EngineCore: camera permission (if needed)
|
||
→ EngineCore: llSetCameraParams (smooth interpolation)
|
||
← EngineCore: CE_EVT_MOVE_DONE when complete
|
||
→ Controller: hudShow()
|
||
```
|
||
|
||
### Continuous Tour Flow
|
||
```
|
||
User: /88 tour 10000 spline 1 3 5 7
|
||
→ Controller: phChatTour(msg)
|
||
→ TourCommands: builds CE_CMD_TOUR payload
|
||
← TourCommands: sends CE_CMD_TOUR to TourEngine
|
||
→ TourEngine: requests current state from Core
|
||
← TourEngine: builds runtime lists (positions, focuses, holds, segments)
|
||
→ TourEngine: CE_INT_SET_CAM frames @ ~30Hz
|
||
← EngineCore: llSetCameraParams (continuous drive)
|
||
← TourEngine: CE_INT_TOUR_BEGIN / CE_INT_TOUR_END
|
||
```
|
||
|
||
### Notecard Playlist Flow
|
||
```
|
||
User: /88 play MyTour
|
||
→ Controller: phPlay("MyTour", 0)
|
||
→ Playlist: reads notecard line-by-line
|
||
← Playlist: executes moveto/goto/load/wait commands
|
||
← Playlist: builds tour blocks, sends CE_CMD_TOUR to TourEngine
|
||
← TourEngine: drives continuous tour
|
||
← Playlist: chains next command on MOVE_DONE
|
||
```
|
||
|
||
## Linkset Data Keys
|
||
|
||
| Key | Format | Purpose |
|
||
|-----|--------|---------|
|
||
| `P1`…`P999` | `px|py|pz|fx|fy|fz|rx|ry|rz|rs|fovRad` | Preset storage |
|
||
| `HS_CAMS` | `shown|N` | Marker visibility state |
|
||
| `HS_FOLLOW` | `on|uuid` | Follow target state |
|
||
| `HS_LOCK` | `on|arg` | Lock target state |
|
||
|
||
## Communication Channel Map
|
||
|
||
| Prefix | Range | Direction | Purpose |
|
||
|--------|-------|-----------|---------|
|
||
| `CE_CMD_` | 1000–1060 | Ctrl→Core, Playlist, TourEngine | Engine commands |
|
||
| `CE_EVT_` | 2000–2060 | Core→Ctrl, Playlist | Engine events |
|
||
| `CE_INT_` | 3000–3003 | TourEngine→Core | Internal engine ops |
|
||
| `PH_CMD_` | 6100–6104 | Ctrl→Playlist, TourCommands | Playlist commands |
|
||
| `MC_CMD` | 5100 | Ctrl→Markers | Marker commands |
|
||
| `MC_EVT_*` | 5101 | Markers→Ctrl | Marker events |
|
||
| `MN_CMD` | 5200 | Menu→Ctrl | Menu commands |
|
||
| `CH` | 88 | User→Ctrl | Chat commands |
|
||
| `MARKER_CH` | -880088 | Markers→Region | Marker region say |
|
||
|
||
## Memory Architecture Principles
|
||
|
||
1. **Hot paths are timer-driven or high-frequency link_message** — `CE_INT_SET_CAM` in EngineCore, tour playback in TourEngine
|
||
2. **No `llParseString2List` in hot paths** — Use `llSubStringIndex` for separator lookup, `llGetSubString` for field extraction
|
||
3. **Runtime data in script memory, not Linkset Data** — Tour lists are read every timer tick; Linkset Data reads add latency
|
||
4. **Short-lived data patterns** — Tour blocks store indices, load position/focus at `endtour`, build final payload directly
|
||
5. **Mixed-type lists are expensive** — Avoid in all scripts; use parallel single-type lists instead
|