highascg

Optional modules (Previs / Tracking / Auto-follow / CG Studio)

HighAsCG ships with a small set of optional feature modules that can be enabled or deleted independently of the base server. The packaging rules below are the ones enforced by WO-30; everything in the Previs / Tracking / Stage Auto-follow WOs (17 / 19 / 31) is built on top of them.

CG Studio (WO-32) is served in-process by the playout server through the module registry — UI at /cg-studio/, API under /api/cg-studio, enabled by default — see src/cg-studio/README.md and the section below.

Acceptance test for the “lean build”: delete the module directories listed below and the base server must still boot normally.

Directory layout

src/
├── module-registry.js          ← core
├── api/
│   └── routes-modules.js       ← core (GET /api/modules)
├── previs/                     ← optional module (delete to remove)
│   └── register.js
├── tracking/                   ← optional module (delete to remove)
│   └── register.js
└── autofollow/                 ← optional module (delete to remove)
    └── register.js
└── cg-studio/                  ← optional module (delete to remove)
    └── register.js

web/
├── lib/optional-modules.js     ← core loader
└── components/
    ├── previs-*.js             ← optional (delete to remove)
    ├── tracking-*.js           ← optional (delete to remove)
    └── autofollow-*.js         ← optional (delete to remove)

Core code must never require('./previs/...'), require('./tracking/...'), require('./autofollow/...') or statically import from client/components/{previs,tracking,autofollow}-*.js. Every entry into a module goes through the registry.

Enabling the module

Either set the environment variable:

HIGHASCG_PREVIS=1 node index.js

…or flip the runtime config:

{
  "features": {
    "previs3d": true
  }
}

When the flag is off, index.js logs a single “Previs/tracking module disabled” line and skips the tryLoad calls entirely. Nothing from src/previs/, src/tracking/, or src/autofollow/ is touched.

When the flag is on, index.js calls moduleRegistry.tryLoad('previs' | 'tracking' | 'autofollow'). Each tryLoad swallows MODULE_NOT_FOUND and descriptor-level errors, so an incomplete or half-deleted install degrades gracefully: the other modules still load.

Installing optional dependencies

The heavy native/runtime deps are marked optionalDependencies so a base install never pays for them:

Package Purpose Size on disk
three WebGL2 scene for 3D previs ~2 MB JS
naudiodon PortAudio device capture native addon

Browser-only deps for Previs (three) live in optionalDependencies. CG Studio ships as vanilla static files under src/cg-studio/public/ (no GrapesJS, no client build). onnxruntime-node (WO-19 person tracking) will be added when that module ships — not in the base server install.

Two install scripts:

# Lean: skip optional deps (recommended for playout servers)
npm run install:base

# Full: include optional deps (three, naudiodon)
npm run install:previs

npm install --include=optional is also fine; install:previs is a thin alias that keeps the intent visible in shell history / CI logs.

Registration API

Each module exports a descriptor from src/<name>/register.js. All fields except name are optional.

// src/previs/register.js  (example)
module.exports = {
  name: 'previs',

  // Called once after the WebSocket server is up and appCtx._wsBroadcast is wired.
  onBoot(ctx) { /* start workers, subscribe to WS, seed state, … */ },

  // Called on SIGINT / SIGTERM before HTTP close.
  async onShutdown() { /* stop workers, release GPU handles, … */ },

  // Any incoming HTTP path matching one of these prefixes is dispatched to handleApi.
  apiPathPrefixes: ['/api/previs'],

  // Signature: ({ method, path, body, ctx, req, query }) => response | null
  handleApi: async ({ method, path, body, ctx }) => { /* … */ },

  // WebSocket event name prefixes this module broadcasts on. Purely informational
  // (exposed through GET /api/modules). The registry does not enforce them.
  wsNamespaces: ['previs:'],

  // URLs served by the core web server. The web client dynamically imports each
  // bundle and, if it has a default export, awaits default(sharedContext).
  webBundles: ['/assets/modules/previs/entry.js'],
  webStyles:  ['/assets/modules/previs/previs.css'],
}

The web client fetches GET /api/modules during init() and forwards a shared context ({ stateStore, ws, api, sceneState, settingsState, streamState }) to each module’s default export. A module that only extends the server-side API does not need a web bundle at all.

GET /api/modules

{
  "enabled":      ["previs", "tracking", "autofollow"],
  "bundles":      ["/assets/modules/previs/entry.js", "…"],
  "styles":       ["/assets/modules/previs/previs.css"],
  "wsNamespaces": ["previs:", "tracking:", "autofollow:"]
}

Always returns 200 — empty arrays when no modules are registered.

WebSocket namespaces

Reserved event prefixes (see individual WOs for payload shapes):

Prefix Owner Example events
previs: WO-17 previs:scene, previs:camera
tracking: WO-19 tracking:persons, tracking:stats
autofollow: WO-31 autofollow:device, autofollow:state

Core code never broadcasts on these prefixes. If a module is deleted, the prefix simply goes silent.

Stage coordinate system

Shared reference frame used by all three modules so data can flow between them (tracking → auto-follow; previs floor plane; device calibration) without per-module conversions.

Positions emitted on the wire (tracking:persons[].stagePosition, autofollow:device[].position, etc.) all obey this frame. The Previs 3D scene renders with the same axes so no conversion is needed when aiming a tracked person at a modelled device.

Deletion checklist

To remove the entire optional-module stack:

rm -rf src/previs src/tracking src/autofollow
rm -f  client/components/previs-*.js client/components/tracking-*.js client/components/autofollow-*.js
rm -rf web/assets/modules
# Optional — shrink the lockfile:
npm uninstall three onnxruntime-node

Then boot. GET /api/modules returns { enabled: [] }, the web client logs loaded: [—], and the base app runs untouched.

CG Studio (server-hosted module)

CG Studio runs in-process on the playout server. src/cg-studio/register.js mounts it through the module registry: static UI at /cg-studio/index.html (+ /studio-assets/), API under /api/cg-studio, and a workspace tab in the operator UI. It is enabled by default (WO-265); opt out with features.cgStudio: false in config or HIGHASCG_CG_STUDIO=0 in the environment. The simulator (node index.js --no-caspar) serves the same module, so launcher-driven simulation gets CG Studio for free.

Standalone dev mode for module development still exists: npm run cg-studio (scripts/cg-studio-run.js) runs the studio on its own HTTP server, port 4300 (cgStudio.httpPort).

The Electron launcher (client/tools/electron-launcher/) no longer hosts its own copy — its CG Studio button opens http://<server>:<port>/cg-studio/index.html on the connected (or simulated) server.

The operator UI (dashboard, scenes, device view) is always client/ in this repo → dist-web/ on playout :4200.

Exported templates land in template/studio/ on the server and appear in GET /api/lower-thirds/templates after playout picks up the files.