Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,10 @@ OUT/videos/<name>.json each video of the episode, as WebM (`story.video`
OUT/storychat/default.json the shared chat rows, when the episode shows chat (`story.chat`)
OUT/storyui/<lang>.json the story UI in each requested language (`story.ui`)
OUT/storyfonts/<id>_<lang>.json the episode's font records and glyph pages (`story.fonts`)
OUT/storysimpleui/<lang>.json the Overlay talk-window and system-message UI (`story.simpleui`)
OUT/storyhosts/<host>.json the Overlay host screen (`story.host`)
OUT/spots/<situation>.json a home host's spot and Spine files (`spot.files`)
OUT/rooms/<place>.json a home host's background (`room.gltf`)
OUT/crilips/default.json the CRI Lips analysis data, when a voice can reach it (`story.crilips`)
OUT/assets/<sha256>.<ext> the files the manifests name
```
Expand All @@ -216,6 +220,10 @@ images. `--fonts game` uses the client's TMP glyphs and generates the runtime gl
Both sources use font settings from the client's assets and require the `fonts` extra. Font files are explicit
content-addressed inputs, so changing their bytes rebuilds the corresponding font product.

Overlay episodes also request their home or live-result host, the simple UI in each requested language, and,
for home hosts, the shared spot and room products. The host's master-data relationship is recorded in
`story.json` `host`; the episode font product covers simple UI text and any area title in both font modes.

`--format` (default `aac`) is the web audio format of the cue sheets: `aac` (AAC-LC in MP4, `cues.json` records each
file's encoder delay), `opus`, `vorbis`, `mp3`, or `flac` (the decoded FLAC as it is); `--flac-level` is the FLAC
compression level of the decoded sounds (as for `export audio`). `crilips/` holds the weights of the game's CRI Lips
Expand Down
4 changes: 4 additions & 0 deletions docs/contracts.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ Live start UI data uses the `format` field `nnnotes.live-ui/1`, described in
[live-ui.schema.json](https://github.com/MetaSekaiLab/nnnotes/blob/main/src/nnnotes/schemas/live-ui.schema.json)
and [live start UI products](products.md#live-start-ui).

Overlay story products use `nnnotes.story-host/1` and `nnnotes.story-simple-ui/1` in their `format` fields:
[story-host.schema.json](https://github.com/MetaSekaiLab/nnnotes/blob/main/src/nnnotes/schemas/story-host.schema.json)
and [story-simple-ui.schema.json](https://github.com/MetaSekaiLab/nnnotes/blob/main/src/nnnotes/schemas/story-simple-ui.schema.json).

Stages carry their own version. A change of what a stage writes for the same inputs is a new stage version, which
changes the key of every task of that stage (golden tests fail when a stage's output changes while its version
stays).
Expand Down
3 changes: 3 additions & 0 deletions docs/export.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ themselves are under `assets/`.
files from `[paths.fonts]` or repeatable `--font LANG=PATH` flags; supply any languages needed by the fallback chain.
Both font sources require the `fonts` extra.

Overlay episodes also export their home or live-result host and the simple talk-window UI. Home hosts share the
spot's Spine files and room glTF products. Their area title and simple UI text use the episode's font product.

Products that several stories share (the scene, assets, models and cue sheets) are placed once, so exporting several
stories into one directory keeps one copy of each. Every product goes through the depot, so a second export reuses
its results.
Expand Down
29 changes: 29 additions & 0 deletions docs/products.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,10 @@ texture a material's main slot names is one glTF texture with its own sampler (f
mip count), and textures whose PNG bytes are equal share one glTF image. The `textures` of `room.json` has one entry
per glTF texture, in glTF order: the Unity texture's name.

`room.json` also records `nodes`, each node's prefab `path` and Mesh object id in glTF node order, and
`materialIds`, Material object ids in glTF material order. These identify the serialized objects when the room
is displayed with a host scene's activation and material state.

What draws nothing is not written and `room.json` lists it: a submesh whose material slot is empty
(`nullMaterialSubmeshes`: `mesh`, object `path` in the prefab, `submesh` index), a MeshFilter without a mesh
(`nullMeshFilters`: the object path) and a mesh without vertices or triangles (`emptyMeshes`: `mesh` and `path`).
Expand Down Expand Up @@ -466,6 +470,31 @@ The retained object table is the UI closure, so the font assets' files remain in
`font-bundles` names the bundles holding the font assets, and `apk:AndroidManifest.xml` supplies the client version.
A client requests the product for each language it uses.

### Overlay hosts and simple UI

For episodes with `MasterAdv._playbackMode` 1, `story.json` includes a `host` descriptor. The host is selected
from one matching `MasterStoryHomeSpotTapTalkEpisode`, `MasterHomeSpot` or `MasterStoryLiveResultEpisode` row.
It identifies the host kind and subject, the home talk kind and character where applicable, and the area title
in each language. A missing or ambiguous host is an error.

`parts.host` maps its subject to `storyhosts/home_<spot id>.json` or `storyhosts/afterlive.json`. A home host also
uses `parts.spots` and `parts.rooms`, whose keys are the situation and background addresses. These name the shared
`spot.files` and `room.gltf` products.

`story.host` writes `host.json` with format `nnnotes.story-host/1`. `prefabs` identifies the host's UI, capture
camera, background and situation objects. `overlayRoot` and `openedSequences` are `{prefab, path}` locations;
`layoutRoot` also carries the serialized `SimpleAdvLayoutRoot` fields. `home` identifies the room and spot
products, the Spot scene roots, and `backgroundState`: activation after selecting the situation, and the floor
renderer's queue. `afterlive` identifies the result background image and sprite, reward panel and substitute
elements. Graphics, DOTween settings, animation bindings and shaders are object references.

`parts.simpleui` maps each language to `storysimpleui/<language>.json`. `story.simpleui` writes `ui.json` with
format `nnnotes.story-simple-ui/1`: the simple talk-window, system-message and UIManager prefab ids, settings,
text records, animation bindings and shaders. The episode's `story.fonts` product includes these texts and the
area title in either font mode. Font files are read as inputs; the retained UI closure uses fenced font references.

### Episode fonts

`story.fonts` contains the font records for one episode and language. Its `episode` input is the first run's
`episode.json`, supplied in the planning fact `product_inputs` under `episode:<id>`. This snapshot determines the
chat-window and frame roots before object export. Its other inputs are localized `MasterText` rows, the font
Expand Down
2 changes: 2 additions & 0 deletions docs/schemas.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ schemas.load("plan") # the schema as a dict
| `run` | `nnnotes.run/1` | a run manifest: context, selection, tasks with keys and statuses, layouts |
| `scripts` | `nnnotes.scripts/1` | every MonoScript of the censuses, by object id |
| `story-fonts` | `nnnotes.story-fonts/1` (`format`) | an episode's text bindings, font records, glyph pages and text shaders |
| `story-host` | `nnnotes.story-host/1` (`format`) | an Overlay story's host screen, layout and initial state |
| `story-simple-ui` | `nnnotes.story-simple-ui/1` (`format`) | localized Overlay talk-window and system-message prefabs |
| `task` | `nnnotes.task/1` | one stage applied to one subject, with everything needed to run it (`depot run`) |
| `view` | `nnnotes.view/1` | a semantic view of a master table, each row with its names and the objects it names |

Expand Down
2 changes: 2 additions & 0 deletions docs/stages.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ codec and channel of a USM) and `catalog.index` (version 2: gzip input and Japan
| `story.chat` | `default` | `objects`: the object table of the shared chat shards | `chat.json` |
| `story.ui` | a client language (`ja`, `en`, `zh-Hant`, `zh-Hans`, `ko`) | `objects`: UI, font and chat-window objects; `ui.objects`: the retained UI closure; `master`: localized `MasterText` rows; `font-bundles`; `apk:AndroidManifest.xml` ([products](products.md#stories)) | `ui.json` (text styles and chat status texts) and the UI object table |
| `story.fonts` | `<episode id>/<language>` | `objects`; `episode`: the parsed episode snapshot; `master`: localized texts; `font-bundles`; `apk:AndroidManifest.xml`; `font:<language>` and `font:emoji` for parameter `fonts: open` ([products](products.md#stories)) | `fonts.json`, glyph pages and the text shader objects; `fonts: game` uses TMP assets |
| `story.simpleui` | a client language | `objects`: font and simple UI objects; `ui.objects`: retained UI closure; `master`; `font-bundles`; `apk:AndroidManifest.xml` | `ui.json`, localized text styles and the UI object table |
| `story.host` | `home/<spot id>` or `afterlive` | `objects`: host UI, capture camera and scene; `master`: host row and related product paths | `host.json` with prefab references, layout, opened sequences and home background state |
| `story` | a `MasterAdv` id | `objects`: the object table of the episode's command list, its shards and the ADV player settings ([products](products.md#stories)); `master`: the episode's master record | `episode.json` and `story.json` |
| `link.addresses` | global | catalog index, censuses (the built-in files' too) | key -> objects (a scene key: the objects of the scene's file outside a hierarchy), Resources.Load path -> objects, built-in selector (class, MonoBehaviour script class) -> objects, serialized file -> bundle or built-in file, bundle -> keys |
| `view.<name>` | a catalog (`main`) | `master:<Table>` per master table it reads, `rules` (the view's rule and the resolvers it uses), `addresses` (the part of the address table it read: the keys looked up and the prefixes listed); no parameters, atoms or context | `view.<name>:<subject>#view` (facts: rows, entries, gaps, unreferenced) |
Expand Down
2 changes: 2 additions & 0 deletions src/nnnotes/cli/assets.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@ def matches(self, stage: str) -> bool:
StageSource("story.chat", "nnnotes.pipeline.story:ChatStage", output=True, product=True),
StageSource("story.ui", "nnnotes.pipeline.story:UiStage", output=True, product=True),
StageSource("story.fonts", "nnnotes.pipeline.story:FontsStage", output=True, product=True),
StageSource("story.simpleui", "nnnotes.pipeline.story:SimpleUiStage", output=True, product=True),
StageSource("story.host", "nnnotes.pipeline.storyhost:HostStage", output=True, product=True),
StageSource("story", "nnnotes.pipeline.story:StoryStage", output=True, product=True),
StageSource("view.", "nnnotes.pipeline.views:stages", output=True),
StageSource("view.voices", "nnnotes.pipeline.voices:VoiceStage", output=True),
Expand Down
5 changes: 4 additions & 1 deletion src/nnnotes/content/room.py
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,7 @@ def material_for(oid: str) -> int:
return materials[oid]

counts = {"meshes": 0, "inactive": 0}
null_slots, null_meshes, empty = [], [], []
null_slots, null_meshes, empty, nodes = [], [], [], []
for i, node in enumerate(prefab.nodes):
comps = {component_class(c): c["fields"] for c in node["components"]}
mf, mr = comps.get("MeshFilter"), comps.get("MeshRenderer")
Expand Down Expand Up @@ -466,6 +466,7 @@ def material_for(oid: str) -> int:
uv = mesh.uv if mesh.uv is not None else np.zeros((len(mesh.pos), 2), np.float32)
active = prefab.active(i)
glb.add_mesh(mesh.name, p, nw, uv, prims, active=active)
nodes.append({"path": path, "mesh": mesh_id})
counts["meshes"] += 1
counts["inactive"] += 0 if active else 1

Expand All @@ -477,6 +478,8 @@ def material_for(oid: str) -> int:
"nullMaterialSubmeshes": null_slots,
"nullMeshFilters": null_meshes,
"emptyMeshes": empty,
"nodes": nodes,
"materialIds": list(materials),
**glb.summary(),
}
return glb.bytes({"backgroundKey": key}), doc
7 changes: 6 additions & 1 deletion src/nnnotes/content/storyfontproduct.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from ..unity import fontsettings, textstyle, tmpfont
from ..unity.export import MemoryPacker, _safe, packed_png
from ..unity.ids import object_id, parse_object_id
from . import advui, storyfonts, storyui, tmpassets
from . import advui, storyfonts, storysimpleui, storyui, tmpassets
from .prefabs import component_class, key_object, mono_json, ref_id, shader_table

FORMAT = "nnnotes.story-fonts/1"
Expand Down Expand Up @@ -81,6 +81,9 @@ def shown_texts(
"""The strings an episode can display, including localized labels and dynamic chat/frame text."""
texts = [row.get(field) for row in episode["text"].values()]
texts.append((episode.get("title") or {}).get(field))
host_title = (episode.get("host") or {}).get("title") or {}
code = next((code for code, (_, column) in languages.LANGUAGES.items() if column[1:] == field), None)
texts.append(host_title.get(code))
texts += [r["text"] for r in storyui.master_id_texts(settings, master).values()]
texts += list(status.values()) + advui.chat_runtime_texts(episode)
for r in records.values():
Expand Down Expand Up @@ -200,6 +203,8 @@ def build(
*storyui.window_keys(objects.keys()).values(),
*storyui.DIALOG_KEYS.values(),
]
if (episode.get("master") or {}).get("_playbackMode") == 1:
ui_keys += storysimpleui.keys(objects.keys())
records, sprite_bindings, frames = text_records(objects, ui_keys + asset_keys(episode), language, lang, settings)
status = storyui.chat_status_texts(objects, master)
ids = objects.json(key_object(objects, storyui.MASTER_ID_SETTINGS_KEY, "AdvMasterIdSettings"))
Expand Down
Loading
Loading