2026-05-27 14:07:23 +01:00
|
|
|
# Visual Manifest
|
|
|
|
|
|
|
|
|
|
Sai's visual manifest is optional JSON that tells the player how Ink tags map
|
|
|
|
|
to images, avatars, scenes, slides, and references.
|
|
|
|
|
|
|
|
|
|
The manifest accepts snake_case or camelCase for the common base paths where the
|
|
|
|
|
older GameCult files already used both.
|
|
|
|
|
|
|
|
|
|
## Visual Novel Shape
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
{
|
|
|
|
|
"asset_base": "/static/stories/agora/",
|
|
|
|
|
"default_scene": "agora",
|
|
|
|
|
"scenes": {
|
|
|
|
|
"agora": {
|
|
|
|
|
"label": "The Aquarium Agora",
|
|
|
|
|
"background": "agora-swarm.png"
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
"speakers": {
|
|
|
|
|
"Void": {
|
|
|
|
|
"name": "Void",
|
2026-05-31 00:41:14 +01:00
|
|
|
"avatar": "void.png",
|
|
|
|
|
"default_sprite": "idle",
|
|
|
|
|
"sprites": {
|
|
|
|
|
"idle": "sprites/void-idle.png",
|
|
|
|
|
"welcome": {
|
|
|
|
|
"image": "sprites/void-welcome.png",
|
|
|
|
|
"position": "left",
|
|
|
|
|
"scale": 1.08
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-05-27 14:07:23 +01:00
|
|
|
},
|
|
|
|
|
"Aqua": {
|
|
|
|
|
"name": "Aqua",
|
2026-05-31 00:41:14 +01:00
|
|
|
"avatar": "aqua.png",
|
|
|
|
|
"sprites": {
|
|
|
|
|
"idle": "sprites/aqua-idle.png",
|
|
|
|
|
"listen": "sprites/aqua-listen.png"
|
|
|
|
|
}
|
2026-05-27 14:07:23 +01:00
|
|
|
}
|
2026-05-30 22:26:41 +01:00
|
|
|
},
|
|
|
|
|
"dom_cards": {
|
|
|
|
|
"roadmap": {
|
|
|
|
|
"title": "Roadmap",
|
|
|
|
|
"selector": "#public-roadmap"
|
|
|
|
|
},
|
|
|
|
|
"receipts": {
|
|
|
|
|
"title": "Receipts",
|
|
|
|
|
"selector": "[data-sai-dom-source='receipts']"
|
|
|
|
|
}
|
2026-05-27 14:07:23 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Useful Ink tags:
|
|
|
|
|
|
|
|
|
|
```ink
|
|
|
|
|
# scene: agora
|
|
|
|
|
# speaker: Void
|
|
|
|
|
# avatar: void
|
2026-05-31 00:41:14 +01:00
|
|
|
# sprite: welcome@left
|
|
|
|
|
# sprites: Void.welcome@left, Aqua.listen@right
|
2026-05-30 22:26:41 +01:00
|
|
|
# dom: roadmap, receipts
|
2026-05-27 14:07:23 +01:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Sai resolves assets relative to `asset_base` unless the asset is already an
|
|
|
|
|
absolute URL or root-relative path.
|
|
|
|
|
|
2026-05-31 00:41:14 +01:00
|
|
|
Sprite tags are optional. If no sprite tag is present, Sai tries to show the
|
|
|
|
|
current speaker's `default_sprite`, then `idle`, then `default`. Use
|
|
|
|
|
`# sprite: none` to hide sprites for a line. Use `# sprite:` for the current
|
|
|
|
|
speaker and `# sprites:` for a comma-separated stage composition. Sprite
|
|
|
|
|
references accept `expression@position`, `Actor.expression@position`, or
|
|
|
|
|
`Actor.expression:position`. Common positions are `farleft`, `left`,
|
|
|
|
|
`midleft`, `center`, `midright`, `right`, and `farright`.
|
|
|
|
|
|
2026-05-30 22:26:41 +01:00
|
|
|
The optional `dom_cards` map lets a visual-novel line stage existing page DOM
|
|
|
|
|
inside the scene. Each card points at a selector in the rendered document; Sai
|
|
|
|
|
clones that element into the stage, strips duplicate `id` attributes from the
|
|
|
|
|
clone, and leaves the original Quartz/HTML element in place as the canonical
|
|
|
|
|
fallback.
|
|
|
|
|
|
2026-05-27 14:07:23 +01:00
|
|
|
## Cinematic Shape
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
{
|
|
|
|
|
"image_base": "/static/stories/deck/",
|
|
|
|
|
"default_caption": "The Room",
|
|
|
|
|
"slides": [
|
|
|
|
|
{
|
|
|
|
|
"image": "slide-01.webp",
|
|
|
|
|
"caption": "The Room",
|
|
|
|
|
"references": [
|
|
|
|
|
{
|
|
|
|
|
"label": "Source note",
|
|
|
|
|
"url": "https://example.com"
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
"references_by_caption": {
|
|
|
|
|
"The Room": [
|
|
|
|
|
{
|
|
|
|
|
"label": "Background reading",
|
|
|
|
|
"url": "https://example.com"
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Useful Ink tags:
|
|
|
|
|
|
|
|
|
|
```ink
|
|
|
|
|
# slide: 0
|
|
|
|
|
The first slide speaks.
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
References attached directly to a slide win. If a slide has no `references`,
|
|
|
|
|
Sai checks `references_by_caption` using the slide caption, then the manifest
|
|
|
|
|
default caption.
|
|
|
|
|
|
|
|
|
|
## Stability
|
|
|
|
|
|
2026-05-31 00:41:14 +01:00
|
|
|
This manifest is intentionally small. Sprite staging extends the scene/speaker
|
|
|
|
|
model; it is not a separate VN engine loop.
|