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.
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.
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.
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.
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.
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.
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.
+X → stage right (audience-left; positive to the operator’s left when facing the stage is a common pitfall — we mirror this once at the UI level and never in the data).+Y → upstage (away from the audience).+Z → up (vertical, gravity-opposite).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.
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 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.