The ChapterScript format
Every chapter is a folder of plain files: one JSON document plus your media. It's open, readable, and diffable, so nothing about your work is locked inside Chapter Vision.
The bundle
A project is a .chapterscript directory bundle:
MyStory.chapterscript/
├── chapter.json the entire document
└── assets/ your media, unmodified
├── intro.mp4
├── ambience.m4a
└── lantern.usdz
chapter.json describes everything the player needs; assets/ holds
the referenced files byte-for-byte as imported. Copy the folder and you've copied the project.
Chapter Studio, Chapter Vision, and the player all read and write this same format, on disk and over
the wire.
chapter.json, top to bottom
The document (format version 2) opens with identity (an experience id, a display name, and the id of the default segment to play first), then five main collections:
- segments: the timed scenes of the chapter (detailed below).
- entities: definitions for everything placeable (video panels, 3D models, text, lights, primitives, particle emitters), each with a transform, its kind-specific settings, and whether it starts enabled.
- environment: document-wide lighting preset and distance fog.
- particlePresets: reusable emitter recipes referenced by emitter entities, carrying the particle editor's full parameter surface.
- manifest: one entry per asset file with its relative path, byte size, and SHA-256 content hash (plus probed duration and dimensions for media). Hashes drive cache invalidation and sync convergence everywhere.
Segments, steps, actions
Each segment carries a presentation (immersive, mixed, or windowed), an optional immersive backdrop — either a video skybox with layout (mono, side-by-side, over-under, or multiview HEVC), field of view (180° or 360°), and loop flag, or a USDZ scene — and its steps.
A step is a timed beat: a duration, an optional gate (holds the step
until the audience interacts or a timeout passes), a list of actions that fire at the
step's start, and scheduled actions that fire at second offsets within the step.
Actions are the verbs: reveal, hide, fade, move, scale, play video, play audio, effects, and
so on, each a small typed JSON object.
Segments also own animation tracks: one track per animated entity, with up to ten scalar channels (position x/y/z, rotation x/y/z, scale x/y/z, opacity). Keys sit at absolute seconds from the segment's start and carry Bézier tangent handles (or linear/stepped modes) for easing; rotations are continuous Euler degrees with an explicit rotate order, so a two-turn spin is stored as 720° — no shortest-path surprises.
A condensed example
{
"formatVersion": 2,
"id": "experience-9b21c4d0",
"displayName": "Lantern Walk",
"defaultSegmentId": "segment_intro",
"segments": [
{
"id": "segment_intro",
"name": "Intro",
"presentation": "immersive",
"immersiveBackdrop": {
"kind": "video",
"file": "forest_360.mp4",
"layout": "mono", "field": "field360", "loop": true
},
"steps": [
{
"id": "step_1", "duration": 8.0,
"actions": [
{ "revealEntity": { "entity": "lantern.usdz", "fadeIn": 0.5 } }
],
"scheduledActions": [
{ "at": 2.0, "action": { "playVideo": {
"file": "intro.mp4", "channel": "video-intro",
"sourceIn": 12.5, "sourceOut": 47.25 } } }
]
},
{
"id": "step_2", "duration": 10.0,
"gate": { "type": "tap", "prompt": "Tap the lantern" },
"actions": []
}
],
"animationTracks": [
{
"entity": "lantern.usdz",
"rotateOrder": "xyz",
"channels": {
"ty": [
{ "t": 0.0, "v": 1.0, "interp": "bezier",
"inTangent": { "dt": -0.4, "dv": 0.0 },
"outTangent": { "dt": 0.4, "dv": 0.0 } },
{ "t": 4.0, "v": 1.6, "interp": "bezier" }
]
}
}
]
}
],
"entities": [
{ "id": "lantern.usdz", "kind": "model",
"transform": { "position": [0, 1, -1.5] }, "initiallyEnabled": false }
],
"environment": { "lightingPreset": "dusk", "fogDensity": 0.02 },
"particlePresets": [],
"manifest": { "entries": [
{ "id": "intro.mp4", "relativePath": "intro.mp4",
"byteSize": 48211930, "sha256": "9f31ab…" }
] }
}
Note. This example is condensed for reading; a real document spells out every field, and the apps write more metadata (probe dimensions, tint colors, and so on) than shown here.
Trims are metadata, not surgery
Notice sourceIn/sourceOut on the video action above: that's a
non-destructive trim. The player cues the master at sourceIn, stops (or loops) at
sourceOut, and the file in assets/ is never re-encoded or modified.
Several clips can window the same master differently. Omitting the fields means "play the whole
file." Only the explicit Consolidate — Overwrite Master File… command actually rewrites a master, and
it rebases every clip's window so nothing shifts.
Built for version control
Saves are deterministic: pretty-printed JSON with sorted keys, every time, on every device.
Save without changing anything and the bytes are identical; change one fade duration and the
diff is one line. That makes .chapterscript bundles genuinely pleasant in git:
diffs are reviewable, history is meaningful, and non-colliding edits merge cleanly.
Forward compatibility
Decoding is tolerant by design. Unknown fields are ignored, missing newer fields take defaults, and documents from older versions are migrated on open (legacy step-embedded animation, for example, is hoisted into segment animation tracks). A newer app opens your old chapters; an older player degrades gracefully on newer ones. Migration happens in memory, and your file on disk changes only when you save.