diff --git a/CHANGELOG.md b/CHANGELOG.md index 126e90d..b25d903 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ schemas in `nnnotes.schemas` and the documented Python API. Before 1.0 a minor v ### Features +- Ship schemas for shared product root documents and prefab objects, with numeric font and transform contracts. - Share catalog versions and labels through depot remotes, with verified catalog files fetched on demand. - Retain census planning results, emitted task descriptions and standalone task runs as depot collection roots. - Store content, task inputs, results and catalog versions in a persistent depot with optional remote storage, verified collection and bounded source downloads. diff --git a/docs/contracts.md b/docs/contracts.md index 0e5f779..9c47051 100644 --- a/docs/contracts.md +++ b/docs/contracts.md @@ -328,3 +328,32 @@ Object reads and writes name individual keys. Catalog discovery and `depot gc -- ## Site catalog [`site.schema.json`](https://github.com/MetaSekaiLab/nnnotes/blob/main/src/nnnotes/schemas/site.schema.json) describes `nnnotes.site/1`. A named site stores its complete region-topic catalog in `site.json`. Each topic names an immutable product manifest and the metadata of its listing entry. Partial selections retain other topics; complete selections replace the requested region and product kind. The successful run points at the complete site layout so depot collection retains every published dependency. + +## Product document schemas + +The fixed product documents have the following schemas, also available through `nnnotes.schemas.load`. + +| Schema | Document | +|---|---| +| [episode](https://github.com/MetaSekaiLab/nnnotes/blob/main/src/nnnotes/schemas/episode.schema.json) | Story episode commands and resource rows | +| [model-json](https://github.com/MetaSekaiLab/nnnotes/blob/main/src/nnnotes/schemas/model-json.schema.json) | Live2D model objects, resources and animation bindings | +| [prefab](https://github.com/MetaSekaiLab/nnnotes/blob/main/src/nnnotes/schemas/prefab.schema.json) | Prefab hierarchy nodes, transforms and components | +| [live](https://github.com/MetaSekaiLab/nnnotes/blob/main/src/nnnotes/schemas/live.schema.json) | Live entry and shared product parts | +| [live-scene](https://github.com/MetaSekaiLab/nnnotes/blob/main/src/nnnotes/schemas/live-scene.schema.json) | Live scene, lane resources and bands | +| [live-notes](https://github.com/MetaSekaiLab/nnnotes/blob/main/src/nnnotes/schemas/live-notes.schema.json) | Note prefabs and shared animation bindings | +| [live-noteskin](https://github.com/MetaSekaiLab/nnnotes/blob/main/src/nnnotes/schemas/live-noteskin.schema.json) | Note skin sprites and animation bindings | +| [live-noteeffect](https://github.com/MetaSekaiLab/nnnotes/blob/main/src/nnnotes/schemas/live-noteeffect.schema.json) | Note effect prefabs and animation bindings | +| [live-music](https://github.com/MetaSekaiLab/nnnotes/blob/main/src/nnnotes/schemas/live-music.schema.json) | Music layers and cue metadata | +| [live-sounds](https://github.com/MetaSekaiLab/nnnotes/blob/main/src/nnnotes/schemas/live-sounds.schema.json) | Sound groups, categories and routing | +| [story](https://github.com/MetaSekaiLab/nnnotes/blob/main/src/nnnotes/schemas/story.schema.json) | Story entry and shared product parts | +| [story-scene](https://github.com/MetaSekaiLab/nnnotes/blob/main/src/nnnotes/schemas/story-scene.schema.json) | Story scene objects, settings and graphics | +| [story-ui](https://github.com/MetaSekaiLab/nnnotes/blob/main/src/nnnotes/schemas/story-ui.schema.json) | Localized story UI objects and text settings | +| [story-asset](https://github.com/MetaSekaiLab/nnnotes/blob/main/src/nnnotes/schemas/story-asset.schema.json) | Story media prefab objects and animation bindings | + +### Scene and media document schemas + +- [room](https://github.com/MetaSekaiLab/nnnotes/blob/main/src/nnnotes/schemas/room.schema.json) +- [spot](https://github.com/MetaSekaiLab/nnnotes/blob/main/src/nnnotes/schemas/spot.schema.json) +- [story-video](https://github.com/MetaSekaiLab/nnnotes/blob/main/src/nnnotes/schemas/story-video.schema.json) +- [story-sound](https://github.com/MetaSekaiLab/nnnotes/blob/main/src/nnnotes/schemas/story-sound.schema.json) +- [crilips](https://github.com/MetaSekaiLab/nnnotes/blob/main/src/nnnotes/schemas/crilips.schema.json) diff --git a/docs/schemas.md b/docs/schemas.md index 8ed6374..003c67e 100644 --- a/docs/schemas.md +++ b/docs/schemas.md @@ -23,25 +23,58 @@ schemas.load("plan") # the schema as a dict | `common` | | definitions shared by the other schemas | | `cost` | `nnnotes.cost/1` | the measured cost of a task's last run | | `coverage` | `nnnotes.coverage/1` | the coverage of a run per class and per reason code | +| `crilips` | product document | CRI Lips tensor blocks and frontend constants | +| `episode` | product document | story commands and resource tables | | `failures` | `nnnotes.failures/1` | the failed tasks and items of a run | | `layout` | `nnnotes.layout/1` | the files a layout owns in its output directory | +| `live` | `1` | live chart entry and shared parts | +| `live-music` | `1` | music layers and cue metadata | +| `live-noteeffect` | `1` | note effect resources and animation bindings | +| `live-notes` | `1` | note prefabs, settings and animations | +| `live-noteskin` | `1` | note skin resources and animation bindings | +| `live-scene` | `1` | scene objects, settings and band timelines | +| `live-sounds` | `1` | sound groups and category routing | | `live-ui` | `nnnotes.live-ui/1` (`format`) | live start strings, text styles, glyph pages and image bindings | +| `model-json` | `3` | Live2D model objects and animation bindings | | `music-data` | `nnnotes.music-data/2` | every song and chart of one master data version ([music data](music-data.md)) | | `objects` | `nnnotes.objects/1` | the objects a product reaches: their class, item status and files ([products](products.md)) | | `plan` | `nnnotes.plan/1` | what a run would do and why (`depot plan`) | +| `prefab` | product document | prefab nodes, transforms and components | | `product` | `nnnotes.product/1` | a product's manifest: its files and the files of the objects it uses, as content-addressed assets ([products](products.md)) | | `result` | `nnnotes.result/1` | the result of a task, a function of its key alone | +| `room` | product document | room geometry, materials and textures | | `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 | | `site` | `nnnotes.site/1` | a named site's complete region-topic catalog and immutable product references | +| `spot` | product document | spot settings, characters and Spine data | +| `story` | `1` | story entry and shared parts | +| `story-asset` | `1` | story media prefab objects | | `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-scene` | `1` | story scene objects and graphics settings | | `story-simple-ui` | `nnnotes.story-simple-ui/1` (`format`) | localized Overlay talk-window and system-message prefabs | +| `story-sound` | product document | cue metadata and waveform files | +| `story-ui` | `1` | localized story UI and text bindings | +| `story-video` | `1` | video dimensions, timing and media tracks | | `task` | `nnnotes.task/1` | one stage applied to one subject, with everything needed to run it (`depot run`) | | `ui-library` | `nnnotes.ui-library/1` | selected UI keys and their product manifests | | `ui-pack` | `nnnotes.ui-pack/1` | native UI roots, prefab and controller identities, animation bindings | | `view` | `nnnotes.view/1` | a semantic view of a master table, each row with its names and the objects it names | + The documents of the asset export are described in [contracts](contracts.md), the views in [views](views.md). The schema files are in [src/nnnotes/schemas](https://github.com/MetaSekaiLab/nnnotes/tree/main/src/nnnotes/schemas). + +## Product documents + +The product root and prefab schemas are listed in [Product document schemas](contracts.md#product-document-schemas). Their fields match the shared object interfaces consumed by ournotes-player. + +Compare the shared constraints with an ournotes-player checkout or extracted package: + +```sh +python scripts/check_product_schemas.py /path/to/ournotes-player +``` + +The check covers product, live, model, story, prefab, room, spot, audio, video and UI documents. Schema identifiers, +titles and descriptions may differ by package; validation constraints must agree. diff --git a/scripts/check_product_schemas.py b/scripts/check_product_schemas.py new file mode 100644 index 0000000..0558f6b --- /dev/null +++ b/scripts/check_product_schemas.py @@ -0,0 +1,76 @@ +"""Compare the shared product contracts with an ournotes-player checkout or extracted package.""" + +from __future__ import annotations + +import argparse +import json +from pathlib import Path + +NAMES = ( + "product", + "episode", + "model-json", + "prefab", + "live", + "live-scene", + "live-notes", + "live-noteskin", + "live-noteeffect", + "live-music", + "live-sounds", + "story", + "story-scene", + "story-ui", + "story-asset", + "story-fonts", + "story-host", + "story-simple-ui", + "ui-pack", + "ui-library", + "room", + "spot", + "story-video", + "story-sound", + "crilips", +) + + +def contract(value): + if isinstance(value, dict): + out = {} + for key, item in value.items(): + if key in {"$id", "title", "description"}: + continue + if key in {"properties", "patternProperties", "$defs", "dependentSchemas"}: + out[key] = {name: contract(schema) for name, schema in item.items()} + elif key in {"const", "enum"}: + out[key] = item + else: + out[key] = contract(item) + return out + if isinstance(value, list): + return [contract(item) for item in value] + return value + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("player", type=Path, help="ournotes-player root containing schema/") + args = parser.parse_args() + source = Path(__file__).resolve().parents[1] / "src" / "nnnotes" / "schemas" + errors = [] + for name in NAMES: + paths = [source / f"{name}.schema.json", args.player / "schema" / f"{name}.schema.json"] + try: + left, right = [contract(json.loads(path.read_text(encoding="utf-8"))) for path in paths] + if left != right: + errors.append(f"{name}: schema constraints differ") + except (OSError, ValueError) as error: + errors.append(f"{name}: {error}") + if errors: + parser.exit(1, "\n".join(errors) + "\n") + print(f"{len(NAMES)} shared product schemas match") + + +if __name__ == "__main__": + main() diff --git a/src/nnnotes/schemas/crilips.schema.json b/src/nnnotes/schemas/crilips.schema.json new file mode 100644 index 0000000..36ae156 --- /dev/null +++ b/src/nnnotes/schemas/crilips.schema.json @@ -0,0 +1,57 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "required": [ + "dtype", + "order", + "total_floats", + "blocks", + "constants", + "frontend" + ], + "properties": { + "dtype": { + "const": "float32" + }, + "order": { + "const": "LE" + }, + "total_floats": { + "type": "integer", + "minimum": 0 + }, + "blocks": { + "type": "array", + "items": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "required": [ + "name", + "offset", + "count" + ], + "properties": { + "name": { + "type": "string" + }, + "offset": { + "type": "integer", + "minimum": 0 + }, + "count": { + "type": "integer", + "minimum": 0 + } + } + } + }, + "constants": { + "type": "object" + }, + "frontend": { + "type": "object" + } + }, + "$id": "urn:nnnotes:schema:crilips", + "title": "crilips document" +} diff --git a/src/nnnotes/schemas/episode.schema.json b/src/nnnotes/schemas/episode.schema.json new file mode 100644 index 0000000..805248d --- /dev/null +++ b/src/nnnotes/schemas/episode.schema.json @@ -0,0 +1,305 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "required": [ + "advId", + "asset", + "commandCount", + "commands", + "text", + "sounds", + "cuesheets", + "videos", + "resources", + "master", + "title" + ], + "properties": { + "advId": { + "type": "integer", + "minimum": 0 + }, + "asset": { + "type": "string" + }, + "commandCount": { + "type": "integer", + "minimum": 0 + }, + "commands": { + "type": "array", + "items": { + "$ref": "#/$defs/command" + } + }, + "text": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/texts" + } + }, + "sounds": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "_cueName": { + "type": "string" + }, + "_soundCueSheetID": { + "type": "integer" + } + } + } + }, + "cuesheets": { + "type": "object", + "additionalProperties": { + "type": "object", + "required": [ + "_cueSheetName" + ], + "properties": { + "_cueSheetName": { + "type": "string", + "minLength": 1 + } + } + } + }, + "videos": { + "type": "object", + "additionalProperties": { + "type": "object" + } + }, + "resources": { + "type": "array", + "items": { + "type": "object", + "required": [ + "kind", + "address" + ], + "properties": { + "kind": { + "type": "string", + "minLength": 1 + }, + "address": { + "type": "string", + "minLength": 1 + }, + "present": { + "type": "boolean" + } + } + } + }, + "master": { + "type": "object", + "required": [ + "_id", + "_advEpisodeAsset", + "_titleTextId", + "_playbackMode" + ], + "properties": { + "_id": { + "type": "integer", + "minimum": 0 + }, + "_sheetName": { + "type": "string" + }, + "_advEpisodeAsset": { + "type": "string" + }, + "_titleTextId": { + "type": "string" + }, + "_rubyTitleTextId": { + "type": "string" + }, + "_playbackMode": { + "enum": [ + 0, + 1 + ] + } + } + }, + "title": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/texts" + } + ] + } + }, + "$defs": { + "texts": { + "type": "object", + "properties": { + "japanese": { + "type": [ + "string", + "null" + ] + }, + "english": { + "type": [ + "string", + "null" + ] + }, + "traditionalChinese": { + "type": [ + "string", + "null" + ] + }, + "simplifiedChinese": { + "type": [ + "string", + "null" + ] + }, + "korean": { + "type": [ + "string", + "null" + ] + } + } + }, + "command": { + "type": "object", + "required": [ + "i", + "cmd", + "raw" + ], + "properties": { + "i": { + "type": "integer" + }, + "cmd": { + "type": "string", + "pattern": "^[A-Za-z][A-Za-z0-9]*$" + }, + "raw": { + "type": "integer", + "minimum": 0 + }, + "TargetName": { + "type": "string" + }, + "TargetAssetName": { + "type": "string" + }, + "TargetAssetIndex": { + "type": "integer" + }, + "PositionType": { + "type": "integer" + }, + "CameraDistance": { + "type": "number" + }, + "Duration": { + "type": "number" + }, + "DelaySeconds": { + "type": "number" + }, + "IsNoWait": { + "type": [ + "boolean", + "integer" + ] + }, + "IgnoreData": { + "type": [ + "boolean", + "integer" + ] + }, + "IgnoreLipSync": { + "type": [ + "boolean", + "integer" + ] + }, + "Parameter1": { + "type": "string" + }, + "Parameter2": { + "type": "string" + }, + "Parameter3": { + "type": "string" + }, + "Parameter4": { + "type": "string" + }, + "TargetTextIDs": { + "type": "array", + "items": { + "type": "string" + } + }, + "TargetStatus": { + "type": "integer" + }, + "AdvTextID": { + "type": "string" + }, + "lines": { + "$ref": "#/$defs/texts" + }, + "VoiceIDs": { + "type": "array", + "items": { + "type": "integer" + } + }, + "voiceCues": { + "type": "array", + "items": { + "type": [ + "string", + "null" + ] + } + }, + "MotionName": { + "type": "string" + }, + "ExpressionName": { + "type": "string" + }, + "BgmID": { + "type": "integer" + }, + "SeID": { + "type": "integer" + }, + "Key": { + "type": "string" + }, + "VideoID": { + "type": "integer" + }, + "TargetChatID": { + "type": "integer" + } + } + } + }, + "$id": "urn:nnnotes:schema:episode", + "title": "episode document" +} diff --git a/src/nnnotes/schemas/live-music.schema.json b/src/nnnotes/schemas/live-music.schema.json new file mode 100644 index 0000000..56b3f87 --- /dev/null +++ b/src/nnnotes/schemas/live-music.schema.json @@ -0,0 +1,152 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "required": [ + "format", + "musicId", + "music", + "sounds" + ], + "properties": { + "format": { + "const": 1 + }, + "musicId": { + "type": "integer" + }, + "music": { + "type": "object", + "required": [ + "soundId" + ], + "properties": { + "soundId": { + "$ref": "#/$defs/soundId" + } + } + }, + "sounds": { + "type": "object", + "propertyNames": { + "pattern": "^[1-9][0-9]*$" + }, + "additionalProperties": { + "$ref": "#/$defs/sound" + } + } + }, + "$defs": { + "soundId": { + "type": "integer", + "minimum": 1 + }, + "sound": { + "type": "object", + "required": [ + "sheet", + "cue", + "category", + "categories", + "volume", + "row", + "layers" + ], + "properties": { + "sheet": { + "type": "string" + }, + "cue": { + "type": "string" + }, + "category": { + "type": "integer" + }, + "categories": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1 + }, + "volume": { + "type": "number", + "minimum": 0 + }, + "row": { + "type": "object", + "required": [ + "_isRandomPitch" + ], + "properties": { + "_isRandomPitch": { + "const": false + } + } + }, + "layers": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/layer" + } + } + } + }, + "layer": { + "type": "object", + "required": [ + "file", + "sampleRate", + "samples", + "loopFlag", + "loopStart", + "loopEnd", + "volume" + ], + "properties": { + "file": { + "type": "string", + "pattern": "^[A-Za-z0-9_][^\\\\]*\\.(flac|m4a)$" + }, + "sampleRate": { + "const": 48000 + }, + "channels": { + "type": "integer", + "minimum": 1 + }, + "samples": { + "type": "integer", + "minimum": 1 + }, + "loopFlag": { + "type": "integer" + }, + "loopStart": { + "type": [ + "integer", + "null" + ], + "minimum": 0 + }, + "loopEnd": { + "type": [ + "integer", + "null" + ], + "minimum": 0 + }, + "volume": { + "type": "number", + "minimum": 0 + }, + "encoderDelay": { + "type": "integer", + "minimum": 0 + } + } + } + }, + "$id": "urn:nnnotes:schema:live-music", + "title": "live-music document" +} diff --git a/src/nnnotes/schemas/live-noteeffect.schema.json b/src/nnnotes/schemas/live-noteeffect.schema.json new file mode 100644 index 0000000..c2731e7 --- /dev/null +++ b/src/nnnotes/schemas/live-noteeffect.schema.json @@ -0,0 +1,84 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "required": [ + "format", + "name", + "settings", + "bindings", + "shaders" + ], + "properties": { + "format": { + "const": 1 + }, + "name": { + "type": "string" + }, + "settings": { + "$ref": "#/$defs/objectId" + }, + "bindings": { + "$ref": "#/$defs/bindings" + }, + "shaders": { + "$ref": "#/$defs/shaders" + } + }, + "$defs": { + "objectId": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + }, + "objectIdText": { + "pattern": "^.+:-?[0-9]+$" + }, + "bindings": { + "type": "object", + "propertyNames": { + "$ref": "#/$defs/objectIdText" + }, + "additionalProperties": { + "type": "object", + "additionalProperties": { + "type": "object", + "propertyNames": { + "$ref": "#/$defs/objectIdText" + }, + "additionalProperties": { + "type": "array", + "items": { + "type": "object", + "required": [ + "path", + "attribute" + ], + "properties": { + "path": { + "type": [ + "string", + "null" + ] + }, + "attribute": { + "type": [ + "string", + "null" + ] + } + } + } + } + } + } + }, + "shaders": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/objectId" + } + } + }, + "$id": "urn:nnnotes:schema:live-noteeffect", + "title": "live-noteeffect document" +} diff --git a/src/nnnotes/schemas/live-notes.schema.json b/src/nnnotes/schemas/live-notes.schema.json new file mode 100644 index 0000000..905c60f --- /dev/null +++ b/src/nnnotes/schemas/live-notes.schema.json @@ -0,0 +1,229 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "required": [ + "format", + "prefabs", + "laneEffect", + "settings", + "bindings", + "shaders" + ], + "properties": { + "format": { + "const": 1 + }, + "prefabs": { + "type": "object", + "required": [ + "tap_note_view", + "flick_note_view", + "flick_left_note_view", + "flick_right_note_view", + "slide_note_view", + "slide_end_note_view", + "connection_note_view", + "none_note_view", + "slide_line_view", + "pair_note_line" + ], + "additionalProperties": { + "$ref": "#/$defs/objectId" + } + }, + "laneEffect": { + "$ref": "#/$defs/objectId" + }, + "settings": { + "type": "object", + "required": [ + "laneCount", + "laneSize", + "laneTopRange", + "laneBottomRange", + "laneTopPosition", + "judgementScreenBottomPosition", + "optionDefaults", + "optionRanges", + "liveSettings" + ], + "properties": { + "laneCount": { + "type": "integer", + "minimum": 1 + }, + "laneSize": { + "type": "array", + "prefixItems": [ + { + "type": "number" + }, + { + "type": "number" + } + ], + "minItems": 2, + "maxItems": 2 + }, + "laneTopRange": { + "type": "number" + }, + "laneBottomRange": { + "type": "number" + }, + "laneTopPosition": { + "type": "number" + }, + "judgementScreenBottomPosition": { + "type": "number" + }, + "tiltCenterLane": { + "type": "number" + }, + "noteDisplayTimeMs": { + "type": "number" + }, + "optionDefaults": { + "type": "object", + "required": [ + "NoteSpeed", + "SlideOpacity", + "GuideOpacity" + ], + "additionalProperties": { + "type": "string" + } + }, + "optionRanges": { + "type": "object", + "required": [ + "SlideOpacity", + "GuideOpacity" + ], + "additionalProperties": { + "$ref": "#/$defs/range" + } + }, + "liveSettings": { + "type": "object", + "required": [ + "note_speed_min", + "note_speed_max", + "note_speed_view_min", + "note_speed_view_max" + ], + "properties": { + "note_speed_min": { + "type": "string" + }, + "note_speed_max": { + "type": "string" + }, + "note_speed_view_min": { + "type": "string" + }, + "note_speed_view_max": { + "type": "string" + } + } + }, + "skins": { + "type": "object", + "propertyNames": { + "pattern": "^[0-9]+$" + }, + "additionalProperties": { + "type": "string" + } + }, + "effects": { + "type": "object", + "propertyNames": { + "pattern": "^[0-9]+$" + }, + "additionalProperties": { + "type": "string" + } + }, + "effect": { + "type": "string" + } + } + }, + "bindings": { + "$ref": "#/$defs/bindings" + }, + "shaders": { + "$ref": "#/$defs/shaders" + } + }, + "$defs": { + "objectId": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + }, + "objectIdText": { + "pattern": "^.+:-?[0-9]+$" + }, + "bindings": { + "type": "object", + "propertyNames": { + "$ref": "#/$defs/objectIdText" + }, + "additionalProperties": { + "type": "object", + "additionalProperties": { + "type": "object", + "propertyNames": { + "$ref": "#/$defs/objectIdText" + }, + "additionalProperties": { + "type": "array", + "items": { + "type": "object", + "required": [ + "path", + "attribute" + ], + "properties": { + "path": { + "type": [ + "string", + "null" + ] + }, + "attribute": { + "type": [ + "string", + "null" + ] + } + } + } + } + } + } + }, + "shaders": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/objectId" + } + }, + "range": { + "type": "array", + "prefixItems": [ + { + "type": "number" + }, + { + "type": "number" + } + ], + "minItems": 2, + "maxItems": 2 + } + }, + "$id": "urn:nnnotes:schema:live-notes", + "title": "live-notes document" +} diff --git a/src/nnnotes/schemas/live-noteskin.schema.json b/src/nnnotes/schemas/live-noteskin.schema.json new file mode 100644 index 0000000..4077f29 --- /dev/null +++ b/src/nnnotes/schemas/live-noteskin.schema.json @@ -0,0 +1,84 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "required": [ + "format", + "name", + "skin", + "bindings", + "shaders" + ], + "properties": { + "format": { + "const": 1 + }, + "name": { + "type": "string" + }, + "skin": { + "$ref": "#/$defs/objectId" + }, + "bindings": { + "$ref": "#/$defs/bindings" + }, + "shaders": { + "$ref": "#/$defs/shaders" + } + }, + "$defs": { + "objectId": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + }, + "objectIdText": { + "pattern": "^.+:-?[0-9]+$" + }, + "bindings": { + "type": "object", + "propertyNames": { + "$ref": "#/$defs/objectIdText" + }, + "additionalProperties": { + "type": "object", + "additionalProperties": { + "type": "object", + "propertyNames": { + "$ref": "#/$defs/objectIdText" + }, + "additionalProperties": { + "type": "array", + "items": { + "type": "object", + "required": [ + "path", + "attribute" + ], + "properties": { + "path": { + "type": [ + "string", + "null" + ] + }, + "attribute": { + "type": [ + "string", + "null" + ] + } + } + } + } + } + } + }, + "shaders": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/objectId" + } + } + }, + "$id": "urn:nnnotes:schema:live-noteskin", + "title": "live-noteskin document" +} diff --git a/src/nnnotes/schemas/live-scene.schema.json b/src/nnnotes/schemas/live-scene.schema.json new file mode 100644 index 0000000..9666abf --- /dev/null +++ b/src/nnnotes/schemas/live-scene.schema.json @@ -0,0 +1,282 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "required": [ + "format", + "scene", + "graphics", + "laneSkins", + "prefabs", + "spriteAssets", + "bands", + "settings", + "bindings", + "shaders" + ], + "properties": { + "format": { + "const": 1 + }, + "scene": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/objectId" + } + }, + "renderSettings": { + "$ref": "#/$defs/optionalObjectId" + }, + "graphics": { + "type": "object", + "required": [ + "graphics" + ], + "properties": { + "graphics": { + "$ref": "#/$defs/objectId" + }, + "quality": { + "$ref": "#/$defs/objectId" + }, + "player": { + "$ref": "#/$defs/objectId" + } + } + }, + "laneSkins": { + "type": "object", + "additionalProperties": { + "type": "object", + "required": [ + "lane_base", + "lane_tap_area", + "out_side_line" + ], + "properties": { + "lane_base": { + "$ref": "#/$defs/objectId" + }, + "lane_tap_area": { + "$ref": "#/$defs/objectId" + }, + "out_side_line": { + "$ref": "#/$defs/objectId" + } + } + } + }, + "prefabs": { + "type": "object", + "required": [ + "laneLine", + "judgeEffectView" + ], + "properties": { + "laneLine": { + "$ref": "#/$defs/objectId" + }, + "judgeEffectView": { + "$ref": "#/$defs/objectId" + }, + "barLineView": { + "$ref": "#/$defs/optionalObjectId" + } + } + }, + "spriteAssets": { + "type": "object", + "required": [ + "combo", + "judgement" + ], + "properties": { + "combo": { + "$ref": "#/$defs/objectId" + }, + "judgement": { + "$ref": "#/$defs/objectId" + } + } + }, + "bands": { + "type": "object", + "propertyNames": { + "pattern": "^[0-9]+$" + }, + "additionalProperties": { + "type": "object", + "required": [ + "timeline", + "background", + "tracks" + ], + "properties": { + "timeline": { + "$ref": "#/$defs/objectId" + }, + "background": { + "$ref": "#/$defs/objectId" + }, + "tracks": { + "type": "object", + "propertyNames": { + "$ref": "#/$defs/objectIdText" + }, + "additionalProperties": { + "type": "object", + "required": [ + "prefab", + "animator" + ], + "properties": { + "name": { + "type": [ + "string", + "null" + ] + }, + "prefab": { + "$ref": "#/$defs/objectId" + }, + "animator": { + "type": "string" + } + } + } + } + } + } + }, + "settings": { + "type": "object", + "required": [ + "optionDefaults", + "qualitySettings", + "laneSkin" + ], + "properties": { + "optionDefaults": { + "type": "object", + "required": [ + "JudgePositionDisplay", + "NoteStartPosition" + ], + "additionalProperties": { + "type": "string" + } + }, + "optionRanges": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/range" + } + }, + "qualitySettings": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": [ + "_quality", + "_effectRenderingScale" + ], + "properties": { + "_quality": { + "type": "integer" + }, + "_effectRenderingScale": { + "type": "number" + } + } + } + }, + "laneSkin": { + "type": "string" + } + } + }, + "bindings": { + "$ref": "#/$defs/bindings" + }, + "shaders": { + "$ref": "#/$defs/shaders" + } + }, + "$defs": { + "objectId": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + }, + "objectIdText": { + "pattern": "^.+:-?[0-9]+$" + }, + "bindings": { + "type": "object", + "propertyNames": { + "$ref": "#/$defs/objectIdText" + }, + "additionalProperties": { + "type": "object", + "additionalProperties": { + "type": "object", + "propertyNames": { + "$ref": "#/$defs/objectIdText" + }, + "additionalProperties": { + "type": "array", + "items": { + "type": "object", + "required": [ + "path", + "attribute" + ], + "properties": { + "path": { + "type": [ + "string", + "null" + ] + }, + "attribute": { + "type": [ + "string", + "null" + ] + } + } + } + } + } + } + }, + "shaders": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/objectId" + } + }, + "range": { + "type": "array", + "prefixItems": [ + { + "type": "number" + }, + { + "type": "number" + } + ], + "minItems": 2, + "maxItems": 2 + }, + "optionalObjectId": { + "type": [ + "string", + "null" + ], + "pattern": "^.+:-?[0-9]+$" + } + }, + "$id": "urn:nnnotes:schema:live-scene", + "title": "live-scene document" +} diff --git a/src/nnnotes/schemas/live-sounds.schema.json b/src/nnnotes/schemas/live-sounds.schema.json new file mode 100644 index 0000000..a7420ef --- /dev/null +++ b/src/nnnotes/schemas/live-sounds.schema.json @@ -0,0 +1,279 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "required": [ + "format", + "sounds", + "categories", + "react", + "noteSe", + "liveSe", + "timeline" + ], + "properties": { + "format": { + "const": 1 + }, + "sounds": { + "type": "object", + "propertyNames": { + "pattern": "^[1-9][0-9]*$" + }, + "additionalProperties": { + "$ref": "#/$defs/sound" + } + }, + "categories": { + "type": "object", + "additionalProperties": { + "type": "number", + "minimum": 0 + } + }, + "react": { + "type": "array", + "items": { + "$ref": "#/$defs/react" + } + }, + "noteSe": { + "type": "object", + "required": [ + "types", + "volumes", + "mutes" + ], + "properties": { + "types": { + "$ref": "#/$defs/byType", + "additionalProperties": { + "$ref": "#/$defs/soundId" + } + }, + "volumes": { + "$ref": "#/$defs/byType", + "additionalProperties": { + "type": "number", + "minimum": 0 + } + }, + "mutes": { + "$ref": "#/$defs/byType", + "additionalProperties": { + "type": "boolean" + } + }, + "patternId": { + "type": "integer", + "minimum": 1 + }, + "groups": { + "type": "object", + "propertyNames": { + "pattern": "^[1-9][0-9]*$" + }, + "additionalProperties": { + "$ref": "#/$defs/byType", + "additionalProperties": { + "$ref": "#/$defs/soundId" + } + } + } + } + }, + "liveSe": { + "$ref": "#/$defs/byType", + "additionalProperties": { + "$ref": "#/$defs/soundId" + } + }, + "voice": { + "type": "object", + "properties": { + "startVoiceSoundId": { + "$ref": "#/$defs/optionalSoundId" + }, + "clearVoiceSoundId": { + "$ref": "#/$defs/optionalSoundId" + }, + "fullComboVoiceSoundId": { + "$ref": "#/$defs/optionalSoundId" + }, + "allPerfectVoiceSoundId": { + "$ref": "#/$defs/optionalSoundId" + } + } + }, + "timeline": { + "type": "object", + "required": [ + "startCheerDelaySec" + ], + "properties": { + "startCheerDelaySec": { + "type": "number", + "minimum": 0 + } + } + } + }, + "$defs": { + "soundId": { + "type": "integer", + "minimum": 1 + }, + "optionalSoundId": { + "type": "integer", + "minimum": -1 + }, + "byType": { + "type": "object", + "propertyNames": { + "pattern": "^[0-9]+$" + } + }, + "sound": { + "type": "object", + "required": [ + "sheet", + "cue", + "category", + "categories", + "volume", + "row", + "layers" + ], + "properties": { + "sheet": { + "type": "string" + }, + "cue": { + "type": "string" + }, + "category": { + "type": "integer" + }, + "categories": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1 + }, + "volume": { + "type": "number", + "minimum": 0 + }, + "row": { + "type": "object", + "required": [ + "_isRandomPitch" + ], + "properties": { + "_isRandomPitch": { + "const": false + } + } + }, + "layers": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/layer" + } + } + } + }, + "layer": { + "type": "object", + "required": [ + "file", + "sampleRate", + "samples", + "loopFlag", + "loopStart", + "loopEnd", + "volume" + ], + "properties": { + "file": { + "type": "string", + "pattern": "^[A-Za-z0-9_][^\\\\]*\\.(flac|m4a)$" + }, + "sampleRate": { + "const": 48000 + }, + "channels": { + "type": "integer", + "minimum": 1 + }, + "samples": { + "type": "integer", + "minimum": 1 + }, + "loopFlag": { + "type": "integer" + }, + "loopStart": { + "type": [ + "integer", + "null" + ], + "minimum": 0 + }, + "loopEnd": { + "type": [ + "integer", + "null" + ], + "minimum": 0 + }, + "volume": { + "type": "number", + "minimum": 0 + }, + "encoderDelay": { + "type": "integer", + "minimum": 0 + } + } + }, + "react": { + "type": "object", + "required": [ + "src", + "dest", + "level", + "decrementMs", + "incrementMs", + "holdMs" + ], + "properties": { + "src": { + "type": "string" + }, + "dest": { + "type": "string" + }, + "level": { + "type": "number", + "minimum": 0 + }, + "decrementMs": { + "type": "number", + "minimum": 0 + }, + "incrementMs": { + "type": "number", + "minimum": 0 + }, + "holdMs": { + "type": "number", + "minimum": 0 + } + } + } + }, + "$id": "urn:nnnotes:schema:live-sounds", + "title": "live-sounds document" +} diff --git a/src/nnnotes/schemas/live.schema.json b/src/nnnotes/schemas/live.schema.json new file mode 100644 index 0000000..e638703 --- /dev/null +++ b/src/nnnotes/schemas/live.schema.json @@ -0,0 +1,89 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "required": [ + "format", + "musicId", + "difficulty", + "band", + "jacket", + "notes", + "parts" + ], + "properties": { + "format": { + "const": 1 + }, + "musicId": { + "type": "integer" + }, + "difficulty": { + "type": "string" + }, + "band": { + "type": "object", + "required": [ + "band" + ], + "properties": { + "band": { + "type": "integer" + } + } + }, + "jacket": { + "$ref": "#/$defs/objectId" + }, + "notes": { + "$ref": "#/$defs/file" + }, + "notesMirror": { + "$ref": "#/$defs/file" + }, + "parts": { + "type": "object", + "required": [ + "scene", + "notes", + "noteskin", + "noteeffect", + "sounds", + "music" + ], + "additionalProperties": { + "type": "string", + "pattern": "^[^/\\\\]+/[^/\\\\]+\\.json$" + }, + "properties": { + "ui": { + "type": "object", + "propertyNames": { + "enum": [ + "ja", + "en", + "zh-Hant", + "zh-Hans", + "ko" + ] + }, + "additionalProperties": { + "type": "string", + "pattern": "^[^/\\\\]+/[^/\\\\]+\\.json$" + } + } + } + } + }, + "$defs": { + "objectId": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + }, + "file": { + "type": "string", + "pattern": "^[A-Za-z0-9_][^\\\\]*\\.json$" + } + }, + "$id": "urn:nnnotes:schema:live", + "title": "live document" +} diff --git a/src/nnnotes/schemas/model-json.schema.json b/src/nnnotes/schemas/model-json.schema.json new file mode 100644 index 0000000..84ef221 --- /dev/null +++ b/src/nnnotes/schemas/model-json.schema.json @@ -0,0 +1,141 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "required": [ + "format", + "name", + "key", + "prefab", + "moc", + "canvas", + "textures", + "shaders", + "resources", + "motionSync", + "bindings" + ], + "additionalProperties": false, + "properties": { + "format": { + "const": 3 + }, + "name": { + "type": "string", + "pattern": "^[A-Za-z0-9][A-Za-z0-9_.-]*$" + }, + "key": { + "type": "string" + }, + "prefab": { + "$ref": "#/$defs/objectId" + }, + "moc": { + "$ref": "#/$defs/objectId" + }, + "canvas": { + "type": "object", + "required": [ + "pixelsPerUnit", + "originX", + "originY", + "width", + "height", + "mocVersion" + ], + "additionalProperties": false, + "properties": { + "pixelsPerUnit": { + "type": "number" + }, + "originX": { + "type": "number" + }, + "originY": { + "type": "number" + }, + "width": { + "type": "number" + }, + "height": { + "type": "number" + }, + "mocVersion": { + "type": "integer" + } + } + }, + "textures": { + "type": "array", + "items": { + "$ref": "#/$defs/objectId" + } + }, + "shaders": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/objectId" + } + }, + "resources": { + "type": "object", + "required": [ + "cubismMask", + "cubismMaskCulling" + ], + "additionalProperties": false, + "properties": { + "cubismMask": { + "$ref": "#/$defs/objectId" + }, + "cubismMaskCulling": { + "$ref": "#/$defs/objectId" + } + } + }, + "motionSync": { + "type": "boolean" + }, + "bindings": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "object", + "required": [ + "path", + "attribute" + ], + "additionalProperties": false, + "properties": { + "path": { + "type": [ + "string", + "null" + ] + }, + "attribute": { + "type": [ + "string", + "null" + ] + }, + "pathCandidates": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + }, + "$defs": { + "objectId": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + } + }, + "$id": "urn:nnnotes:schema:model-json", + "title": "model-json document" +} diff --git a/src/nnnotes/schemas/prefab.schema.json b/src/nnnotes/schemas/prefab.schema.json new file mode 100644 index 0000000..a3af7c7 --- /dev/null +++ b/src/nnnotes/schemas/prefab.schema.json @@ -0,0 +1,174 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "required": [ + "nodes" + ], + "properties": { + "name": { + "type": "string" + }, + "scene": { + "type": "string" + }, + "nodes": { + "type": "array", + "items": { + "$ref": "#/$defs/node" + } + } + }, + "$defs": { + "vec2": { + "type": "object", + "required": [ + "x", + "y" + ], + "properties": { + "x": { + "type": "number" + }, + "y": { + "type": "number" + } + } + }, + "vec3": { + "type": "object", + "required": [ + "x", + "y", + "z" + ], + "properties": { + "x": { + "type": "number" + }, + "y": { + "type": "number" + }, + "z": { + "type": "number" + } + } + }, + "quat": { + "type": "object", + "required": [ + "x", + "y", + "z", + "w" + ], + "properties": { + "x": { + "type": "number" + }, + "y": { + "type": "number" + }, + "z": { + "type": "number" + }, + "w": { + "type": "number" + } + } + }, + "component": { + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "minLength": 1 + }, + "pathId": { + "type": "string", + "pattern": "^-?[0-9]+$" + }, + "script": { + "type": [ + "string", + "null" + ] + }, + "fields": { + "type": "object" + } + } + }, + "node": { + "type": "object", + "required": [ + "path", + "localPosition", + "localRotation", + "localScale", + "components" + ], + "properties": { + "path": { + "type": "string" + }, + "name": { + "type": "string" + }, + "pathId": { + "type": "string", + "pattern": "^-?[0-9]+$" + }, + "transformPathId": { + "type": "string", + "pattern": "^-?[0-9]+$" + }, + "active": { + "type": "boolean" + }, + "layer": { + "type": "integer" + }, + "localPosition": { + "$ref": "#/$defs/vec3" + }, + "localRotation": { + "$ref": "#/$defs/quat" + }, + "localScale": { + "$ref": "#/$defs/vec3" + }, + "components": { + "type": "array", + "items": { + "$ref": "#/$defs/component" + } + }, + "rect": { + "type": "object", + "properties": { + "m_AnchorMin": { + "$ref": "#/$defs/vec2" + }, + "m_AnchorMax": { + "$ref": "#/$defs/vec2" + }, + "m_AnchoredPosition": { + "$ref": "#/$defs/vec2" + }, + "m_SizeDelta": { + "$ref": "#/$defs/vec2" + }, + "m_Pivot": { + "$ref": "#/$defs/vec2" + } + } + } + } + } + }, + "$id": "urn:nnnotes:schema:prefab", + "title": "prefab document" +} diff --git a/src/nnnotes/schemas/product.schema.json b/src/nnnotes/schemas/product.schema.json index 21c4678..2c510f9 100644 --- a/src/nnnotes/schemas/product.schema.json +++ b/src/nnnotes/schemas/product.schema.json @@ -1,8 +1,5 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "urn:nnnotes:schema:product", - "title": "nnnotes.product/1", - "description": "The manifest of a product in a product layout: its files and the files of the objects it uses, each a content-addressed asset whose path is relative to the directory above the manifest's.", "type": "object", "required": [ "schema", @@ -11,29 +8,44 @@ "files", "objects" ], + "additionalProperties": false, "properties": { "schema": { "const": "nnnotes.product/1" }, "product": { - "$ref": "urn:nnnotes:schema:common#/$defs/stageName" + "type": "string", + "pattern": "^[a-z][a-z0-9]*(\\.[a-z][a-z0-9-]*)*$" }, "subject": { - "type": "string" + "type": "string", + "minLength": 1 }, "files": { "type": "object", + "propertyNames": { + "pattern": "^[A-Za-z0-9_][^\\\\]*$" + }, "additionalProperties": { "$ref": "#/$defs/file" } }, "objects": { "type": "object", + "propertyNames": { + "pattern": "^.+:-?[0-9]+$" + }, "additionalProperties": { "type": "object", + "required": [ + "class", + "files" + ], + "additionalProperties": false, "properties": { "class": { - "type": "string" + "type": "string", + "minLength": 1 }, "name": { "type": "string" @@ -44,11 +56,7 @@ "$ref": "#/$defs/file" } } - }, - "required": [ - "files" - ], - "additionalProperties": false + } } }, "root": { @@ -66,7 +74,6 @@ }, "options": { "type": "object", - "description": "Optional: Live option name -> the values the files support (options that select files).", "properties": { "LiveQuality": { "type": "array", @@ -93,8 +100,7 @@ } }, "audio": { - "type": "boolean", - "description": "Whether audio streams are included." + "type": "boolean" } }, "$defs": { @@ -116,17 +122,14 @@ "pattern": "^assets/[0-9a-f]{64}\\.[a-z0-9]+$" }, "encodedAsset": { - "description": "An asset stored gzip- or brotli-encoded: the SHA-256 of its decoded bytes, the file's extension, then .gz or .br.", "type": "string", "pattern": "^assets/[0-9a-f]{64}\\.[a-z0-9]+\\.(gz|br)$" }, "size": { - "description": "The byte count of the file (decoded).", "type": "integer", "minimum": 0 }, "stored": { - "description": "The byte count of an encoded asset as stored (less than size).", "type": "integer", "minimum": 0 }, @@ -222,5 +225,7 @@ } } } - } + }, + "$id": "urn:nnnotes:schema:product", + "title": "nnnotes.product/1" } diff --git a/src/nnnotes/schemas/room.schema.json b/src/nnnotes/schemas/room.schema.json new file mode 100644 index 0000000..3f4dac2 --- /dev/null +++ b/src/nnnotes/schemas/room.schema.json @@ -0,0 +1,81 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "required": [ + "backgroundKey", + "glb", + "meshCount", + "inactiveMeshes", + "nodes", + "materialIds", + "materials", + "textures", + "samplers" + ], + "properties": { + "backgroundKey": { + "type": "string" + }, + "glb": { + "type": "string" + }, + "meshCount": { + "type": "integer", + "minimum": 0 + }, + "inactiveMeshes": { + "type": "integer", + "minimum": 0 + }, + "nullMaterialSubmeshes": { + "type": "array", + "items": { + "type": "object" + } + }, + "nullMeshFilters": { + "type": "array", + "items": { + "type": "string" + } + }, + "emptyMeshes": { + "type": "array", + "items": { + "type": "object" + } + }, + "nodes": { + "type": "array", + "items": { + "type": "object" + } + }, + "materialIds": { + "type": "array", + "items": { + "type": "string" + } + }, + "materials": { + "type": "array", + "items": { + "type": "object" + } + }, + "textures": { + "type": "array", + "items": { + "type": "string" + } + }, + "samplers": { + "type": "array", + "items": { + "type": "object" + } + } + }, + "$id": "urn:nnnotes:schema:room", + "title": "room document" +} diff --git a/src/nnnotes/schemas/spot.schema.json b/src/nnnotes/schemas/spot.schema.json new file mode 100644 index 0000000..eb658d5 --- /dev/null +++ b/src/nnnotes/schemas/spot.schema.json @@ -0,0 +1,59 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "required": [ + "spotId", + "master", + "name", + "tapTalk", + "situationSettings", + "controller", + "characters", + "spineCharacters", + "skeletons", + "matrixConvention" + ], + "properties": { + "spotId": { + "type": "integer" + }, + "master": { + "type": "object" + }, + "name": { + "type": "object" + }, + "tapTalk": { + "type": "object" + }, + "situationSettings": { + "type": "object" + }, + "controller": { + "type": "object" + }, + "characters": { + "type": "array", + "items": { + "type": "object" + } + }, + "spineCharacters": { + "type": "array", + "items": { + "type": "object" + } + }, + "skeletons": { + "type": "array", + "items": { + "type": "object" + } + }, + "matrixConvention": { + "type": "string" + } + }, + "$id": "urn:nnnotes:schema:spot", + "title": "spot document" +} diff --git a/src/nnnotes/schemas/story-asset.schema.json b/src/nnnotes/schemas/story-asset.schema.json new file mode 100644 index 0000000..699775e --- /dev/null +++ b/src/nnnotes/schemas/story-asset.schema.json @@ -0,0 +1,76 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "required": [ + "format", + "key", + "objects", + "bindings", + "shaders" + ], + "properties": { + "format": { + "const": 1 + }, + "key": { + "type": "string" + }, + "objects": { + "type": "array", + "items": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + } + }, + "bindings": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "object", + "required": [ + "path", + "attribute" + ], + "properties": { + "path": { + "type": [ + "string", + "null" + ] + }, + "attribute": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "propertyNames": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + } + } + }, + "propertyNames": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + } + }, + "shaders": { + "type": "object", + "additionalProperties": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + } + } + }, + "$id": "urn:nnnotes:schema:story-asset", + "title": "story-asset document" +} diff --git a/src/nnnotes/schemas/story-fonts.schema.json b/src/nnnotes/schemas/story-fonts.schema.json index 6deecc2..dd81327 100644 --- a/src/nnnotes/schemas/story-fonts.schema.json +++ b/src/nnnotes/schemas/story-fonts.schema.json @@ -1,26 +1,1025 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "urn:nnnotes:schema:story-fonts", - "title": "Episode font records and glyph pages", "type": "object", - "required": ["format", "language", "source", "fonts", "textures", "materials", "materialKeywords", "texts", "sprites", "coverage", "lineBreaking", "shaders"], + "required": [ + "format", + "language", + "source", + "fonts", + "textures", + "materials", + "materialKeywords", + "texts", + "coverage", + "shaders", + "sprites", + "lineBreaking" + ], "properties": { - "format": {"const": "nnnotes.story-fonts/1"}, - "language": {"enum": ["ja", "en", "zh-Hant", "zh-Hans", "ko"]}, - "source": {"enum": ["open", "game"]}, - "fonts": {"type": "object", "additionalProperties": {"type": "object", "required": ["characters", "glyphs", "material", "fallbacks"]}}, - "textures": {"type": "object", "additionalProperties": {"type": "object", "required": ["texture", "width", "height"], "properties": {"texture": {"type": "string"}, "width": {"type": "integer", "minimum": 1}, "height": {"type": "integer", "minimum": 1}}}}, - "materials": {"type": "object", "additionalProperties": {"type": "object"}}, - "materialKeywords": {"type": "object", "additionalProperties": {"type": "array", "items": {"type": "string"}}}, - "texts": {"type": "object", "additionalProperties": {"type": "object", "required": ["style", "localized", "font"], "properties": {"style": {"type": "object"}, "localized": {"oneOf": [{"type": "null"}, {"$ref": "#/$defs/font"}]}, "font": {"$ref": "#/$defs/font"}}}}, - "sprites": {"type": "object", "additionalProperties": {"type": "object", "required": ["spriteAsset", "m_tintAllSprites"]}}, - "coverage": {"type": "object", "required": ["characters", "missing"], "properties": {"characters": {"type": "integer", "minimum": 0}, "missing": {"type": "array", "items": {"type": "string"}}}}, - "lineBreaking": {"type": "object", "required": ["leading", "following", "useModernHangulLineBreakingRules"], "properties": {"leading": {"type": "string"}, "following": {"type": "string"}, "useModernHangulLineBreakingRules": {"type": "boolean"}}}, - "shaders": {"type": "object", "additionalProperties": {"type": "string"}}, - "spriteAssets": {"type": "object", "additionalProperties": {"type": "object"}}, - "emojiSpriteAsset": {"type": "string"} + "format": { + "const": "nnnotes.story-fonts/1" + }, + "language": { + "enum": [ + "ja", + "en", + "zh-Hant", + "zh-Hans", + "ko" + ] + }, + "source": { + "enum": [ + "open", + "game" + ] + }, + "fonts": { + "type": "object", + "propertyNames": { + "minLength": 1 + }, + "additionalProperties": { + "$ref": "#/$defs/font" + } + }, + "textures": { + "type": "object", + "propertyNames": { + "minLength": 1 + }, + "additionalProperties": { + "$ref": "#/$defs/texture" + } + }, + "materials": { + "type": "object", + "propertyNames": { + "minLength": 1 + }, + "additionalProperties": { + "$ref": "#/$defs/material" + } + }, + "materialKeywords": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + } + }, + "texts": { + "type": "object", + "propertyNames": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + }, + "additionalProperties": { + "$ref": "#/$defs/text" + } + }, + "spriteAssets": { + "type": "object", + "propertyNames": { + "minLength": 1 + }, + "additionalProperties": { + "$ref": "#/$defs/spriteAsset" + } + }, + "emojiSpriteAsset": { + "type": "string", + "minLength": 1 + }, + "coverage": { + "type": "object", + "required": [ + "characters", + "missing" + ], + "properties": { + "characters": { + "type": "integer", + "minimum": 0 + }, + "missing": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "missingGlyph": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "sprites": { + "type": "object", + "required": [ + "characters", + "missing" + ], + "properties": { + "characters": { + "type": "integer", + "minimum": 0 + }, + "missing": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "underline": { + "type": "object", + "required": [ + "font", + "character", + "inAsset" + ], + "properties": { + "font": { + "type": "string", + "minLength": 1 + }, + "character": { + "const": "_" + }, + "inAsset": { + "type": "boolean" + } + } + } + } + }, + "lineBreaking": { + "type": "object", + "required": [ + "leading", + "following", + "useModernHangulLineBreakingRules" + ], + "properties": { + "leading": { + "type": "string" + }, + "following": { + "type": "string" + }, + "useModernHangulLineBreakingRules": { + "type": "boolean" + } + } + }, + "shaders": { + "type": "object", + "additionalProperties": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + } + }, + "sprites": { + "type": "object", + "additionalProperties": { + "type": "object", + "required": [ + "spriteAsset" + ], + "properties": { + "spriteAsset": { + "type": "string", + "minLength": 1 + } + } + }, + "propertyNames": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + } + } }, "$defs": { - "font": {"type": "object", "required": ["fontAsset", "material", "lineSpacing"], "properties": {"fontAsset": {"type": "string"}, "material": {"type": "string"}, "lineSpacing": {"type": "number"}}} - } + "int": { + "type": "integer" + }, + "num": { + "type": "number" + }, + "faceInfo": { + "type": "object", + "required": [ + "m_PointSize", + "m_Scale", + "m_UnitsPerEM", + "m_LineHeight", + "m_AscentLine", + "m_CapLine", + "m_MeanLine", + "m_Baseline", + "m_DescentLine", + "m_SuperscriptOffset", + "m_SuperscriptSize", + "m_SubscriptOffset", + "m_SubscriptSize", + "m_UnderlineOffset", + "m_UnderlineThickness", + "m_StrikethroughOffset", + "m_StrikethroughThickness", + "m_TabWidth" + ], + "properties": { + "m_FaceIndex": { + "type": "integer", + "minimum": 0 + }, + "m_FamilyName": { + "type": "string" + }, + "m_StyleName": { + "type": "string" + }, + "m_PointSize": { + "type": "number", + "exclusiveMinimum": 0 + }, + "m_Scale": { + "type": "number", + "exclusiveMinimum": 0 + }, + "m_UnitsPerEM": { + "type": "integer", + "minimum": 1 + }, + "m_LineHeight": { + "$ref": "#/$defs/num" + }, + "m_AscentLine": { + "$ref": "#/$defs/num" + }, + "m_CapLine": { + "$ref": "#/$defs/num" + }, + "m_MeanLine": { + "$ref": "#/$defs/num" + }, + "m_Baseline": { + "$ref": "#/$defs/num" + }, + "m_DescentLine": { + "$ref": "#/$defs/num" + }, + "m_SuperscriptOffset": { + "$ref": "#/$defs/num" + }, + "m_SuperscriptSize": { + "$ref": "#/$defs/num" + }, + "m_SubscriptOffset": { + "$ref": "#/$defs/num" + }, + "m_SubscriptSize": { + "$ref": "#/$defs/num" + }, + "m_UnderlineOffset": { + "$ref": "#/$defs/num" + }, + "m_UnderlineThickness": { + "$ref": "#/$defs/num" + }, + "m_StrikethroughOffset": { + "$ref": "#/$defs/num" + }, + "m_StrikethroughThickness": { + "$ref": "#/$defs/num" + }, + "m_TabWidth": { + "$ref": "#/$defs/num" + } + } + }, + "metrics": { + "type": "object", + "required": [ + "m_Width", + "m_Height", + "m_HorizontalBearingX", + "m_HorizontalBearingY", + "m_HorizontalAdvance" + ], + "properties": { + "m_Width": { + "type": "number", + "minimum": 0 + }, + "m_Height": { + "type": "number", + "minimum": 0 + }, + "m_HorizontalBearingX": { + "$ref": "#/$defs/num" + }, + "m_HorizontalBearingY": { + "$ref": "#/$defs/num" + }, + "m_HorizontalAdvance": { + "$ref": "#/$defs/num" + } + } + }, + "rect": { + "type": "object", + "required": [ + "m_X", + "m_Y", + "m_Width", + "m_Height" + ], + "properties": { + "m_X": { + "$ref": "#/$defs/int" + }, + "m_Y": { + "$ref": "#/$defs/int" + }, + "m_Width": { + "type": "integer", + "minimum": 0 + }, + "m_Height": { + "type": "integer", + "minimum": 0 + } + } + }, + "glyph": { + "type": "object", + "required": [ + "metrics", + "rect", + "scale", + "atlasIndex" + ], + "properties": { + "metrics": { + "$ref": "#/$defs/metrics" + }, + "rect": { + "$ref": "#/$defs/rect" + }, + "scale": { + "type": "number", + "exclusiveMinimum": 0 + }, + "atlasIndex": { + "type": [ + "integer", + "null" + ], + "minimum": 0 + }, + "packed": { + "type": "object", + "required": [ + "texture", + "dx", + "dy" + ], + "additionalProperties": false, + "properties": { + "texture": { + "type": "string", + "minLength": 1 + }, + "dx": { + "$ref": "#/$defs/int" + }, + "dy": { + "$ref": "#/$defs/int" + } + } + }, + "runtime": { + "type": "boolean" + } + } + }, + "character": { + "type": "object", + "required": [ + "glyph", + "scale", + "elementType" + ], + "properties": { + "glyph": { + "type": "integer", + "minimum": 0 + }, + "scale": { + "type": "number", + "exclusiveMinimum": 0 + }, + "elementType": { + "type": "integer" + } + } + }, + "valueRecord": { + "type": "object", + "required": [ + "xPlacement", + "yPlacement", + "xAdvance", + "yAdvance" + ], + "properties": { + "xPlacement": { + "$ref": "#/$defs/num" + }, + "yPlacement": { + "$ref": "#/$defs/num" + }, + "xAdvance": { + "$ref": "#/$defs/num" + }, + "yAdvance": { + "$ref": "#/$defs/num" + } + } + }, + "source": { + "type": "object", + "required": [ + "family", + "style", + "version", + "license", + "file", + "faceIndex", + "bytes", + "sha256", + "generator" + ], + "properties": { + "family": { + "type": "string" + }, + "style": { + "type": "string" + }, + "version": { + "type": "string" + }, + "license": { + "type": "string" + }, + "file": { + "type": "string", + "pattern": "^[^/\\\\]+$" + }, + "faceIndex": { + "type": "integer", + "minimum": 0 + }, + "bytes": { + "type": "integer", + "minimum": 1 + }, + "sha256": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "generator": { + "type": "object" + } + } + }, + "font": { + "type": "object", + "required": [ + "faceInfo", + "atlasWidth", + "atlasHeight", + "atlasPadding", + "atlasRenderMode", + "atlasPopulationMode", + "atlases", + "normalStyle", + "normalSpacingOffset", + "boldStyle", + "boldSpacing", + "italicStyle", + "tabSize", + "material", + "fallbacks", + "glyphPairAdjustmentRecords", + "characters", + "glyphs", + "runtimeCharacters", + "runtimeGlyphs", + "subset" + ], + "properties": { + "faceInfo": { + "$ref": "#/$defs/faceInfo" + }, + "atlasWidth": { + "type": "integer", + "minimum": 1 + }, + "atlasHeight": { + "type": "integer", + "minimum": 1 + }, + "atlasPadding": { + "type": "integer", + "minimum": 0 + }, + "atlasRenderMode": { + "$ref": "#/$defs/int" + }, + "atlasPopulationMode": { + "enum": [ + 0, + 1 + ] + }, + "atlases": { + "type": "array", + "items": { + "type": [ + "string", + "null" + ] + } + }, + "normalStyle": { + "$ref": "#/$defs/num" + }, + "normalSpacingOffset": { + "$ref": "#/$defs/num" + }, + "boldStyle": { + "$ref": "#/$defs/num" + }, + "boldSpacing": { + "$ref": "#/$defs/num" + }, + "italicStyle": { + "$ref": "#/$defs/int" + }, + "tabSize": { + "$ref": "#/$defs/int" + }, + "material": { + "type": "string", + "minLength": 1 + }, + "fallbacks": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "glyphPairAdjustmentRecords": { + "type": "integer", + "minimum": 0 + }, + "glyphPairAdjustments": { + "type": "object", + "propertyNames": { + "pattern": "^[0-9]+$" + }, + "additionalProperties": { + "type": "object", + "required": [ + "first", + "second", + "flags" + ], + "properties": { + "first": { + "$ref": "#/$defs/valueRecord" + }, + "second": { + "$ref": "#/$defs/valueRecord" + }, + "flags": { + "$ref": "#/$defs/int" + } + } + } + }, + "characters": { + "type": "object", + "propertyNames": { + "pattern": "^[0-9]+$" + }, + "additionalProperties": { + "$ref": "#/$defs/character" + } + }, + "missingGlyph": { + "type": "object", + "required": [ + "unicode", + "characters" + ], + "additionalProperties": false, + "properties": { + "unicode": { + "type": "integer", + "minimum": 0 + }, + "characters": { + "type": "array", + "minItems": 1, + "items": { + "type": "integer", + "minimum": 0 + } + } + } + }, + "glyphs": { + "type": "object", + "propertyNames": { + "pattern": "^[0-9]+$" + }, + "additionalProperties": { + "$ref": "#/$defs/glyph" + } + }, + "runtimeCharacters": { + "type": "array", + "items": { + "type": "integer", + "minimum": 0 + } + }, + "runtimeGlyphs": { + "type": [ + "object", + "null" + ] + }, + "sourceFont": { + "type": [ + "object", + "null" + ] + }, + "source": { + "$ref": "#/$defs/source" + }, + "browser": { + "type": "object", + "required": [ + "family" + ], + "additionalProperties": false, + "properties": { + "family": { + "type": "string", + "minLength": 1 + }, + "weight": { + "type": "string", + "minLength": 1 + }, + "style": { + "type": "string", + "minLength": 1 + }, + "pageWidth": { + "type": "integer", + "minimum": 1 + }, + "pageHeight": { + "type": "integer", + "minimum": 1 + }, + "underlayOffset": { + "type": "number", + "minimum": 0 + }, + "gameFont": { + "type": "string", + "minLength": 1 + } + } + }, + "subset": { + "type": "string" + } + } + }, + "spriteAsset": { + "type": "object", + "required": [ + "faceInfo", + "characters", + "glyphs", + "sequences", + "material" + ], + "properties": { + "faceInfo": { + "type": "object", + "required": [ + "m_PointSize", + "m_Scale", + "m_Baseline", + "m_AscentLine", + "m_DescentLine" + ], + "properties": { + "m_PointSize": { + "type": "number", + "minimum": 0 + }, + "m_Scale": { + "$ref": "#/$defs/num" + }, + "m_Baseline": { + "$ref": "#/$defs/num" + }, + "m_AscentLine": { + "$ref": "#/$defs/num" + }, + "m_DescentLine": { + "$ref": "#/$defs/num" + } + } + }, + "characters": { + "type": "array", + "items": { + "type": "object", + "required": [ + "index", + "unicode", + "name", + "glyph", + "scale" + ], + "properties": { + "index": { + "type": "integer", + "minimum": 0 + }, + "unicode": { + "type": "integer", + "minimum": 0 + }, + "name": { + "type": "string" + }, + "glyph": { + "type": "integer", + "minimum": 0 + }, + "scale": { + "$ref": "#/$defs/num" + } + } + } + }, + "glyphs": { + "type": "object", + "propertyNames": { + "pattern": "^[0-9]+$" + }, + "additionalProperties": { + "$ref": "#/$defs/glyph" + } + }, + "sequences": { + "type": "array", + "items": { + "type": "object", + "required": [ + "name", + "unicode" + ], + "properties": { + "name": { + "type": "string", + "pattern": "-" + }, + "unicode": { + "type": "integer", + "minimum": 0 + } + } + } + }, + "material": { + "type": "string", + "minLength": 1 + }, + "source": { + "anyOf": [ + { + "$ref": "#/$defs/source" + }, + { + "type": "null" + } + ] + }, + "subset": { + "type": "string" + } + } + }, + "texture": { + "type": "object", + "required": [ + "texture", + "name", + "width", + "height", + "mipCount", + "settings" + ], + "properties": { + "texture": { + "type": "string", + "pattern": "^(fonts|textures)/[^/\\\\]+\\.png$" + }, + "name": { + "type": "string", + "minLength": 1 + }, + "width": { + "type": "integer", + "minimum": 1 + }, + "height": { + "type": "integer", + "minimum": 1 + }, + "mipCount": { + "const": 1 + }, + "settings": { + "type": "object", + "required": [ + "m_FilterMode", + "m_WrapU", + "m_WrapV" + ], + "properties": { + "m_FilterMode": { + "enum": [ + 0, + 1, + 2 + ] + }, + "m_WrapU": { + "enum": [ + 0, + 1, + 2 + ] + }, + "m_WrapV": { + "enum": [ + 0, + 1, + 2 + ] + } + } + } + } + }, + "material": { + "type": "object", + "required": [ + "material", + "shader", + "keywords", + "textures", + "floats", + "colors" + ], + "properties": { + "material": { + "type": "string", + "minLength": 1 + }, + "shader": { + "type": "object", + "required": [ + "shader" + ], + "properties": { + "shader": { + "type": "string", + "minLength": 1 + } + } + }, + "keywords": { + "type": "array", + "items": { + "type": "string" + } + }, + "textures": { + "type": "object" + }, + "floats": { + "type": "object", + "additionalProperties": { + "type": "number" + } + }, + "ints": { + "type": "object", + "additionalProperties": { + "type": "number" + } + }, + "colors": { + "type": "object" + } + } + }, + "text": { + "type": "object", + "required": [ + "font", + "localized", + "style" + ], + "properties": { + "font": { + "type": "object", + "required": [ + "fontAsset", + "material", + "lineSpacing" + ], + "properties": { + "fontAsset": { + "type": "string", + "minLength": 1 + }, + "material": { + "type": "string", + "minLength": 1 + }, + "lineSpacing": { + "type": "number" + } + } + }, + "localized": { + "type": [ + "object", + "null" + ], + "required": [ + "fontAsset", + "material", + "lineSpacing" + ], + "properties": { + "fontAsset": { + "type": "string", + "minLength": 1 + }, + "material": { + "type": "string", + "minLength": 1 + }, + "lineSpacing": { + "type": "number" + } + } + }, + "style": { + "type": "object" + }, + "localizeKoreanAdjust": { + "type": "object" + } + } + } + }, + "$id": "urn:nnnotes:schema:story-fonts", + "title": "Episode font records and glyph pages" } diff --git a/src/nnnotes/schemas/story-host.schema.json b/src/nnnotes/schemas/story-host.schema.json index d39bc06..99a158f 100644 --- a/src/nnnotes/schemas/story-host.schema.json +++ b/src/nnnotes/schemas/story-host.schema.json @@ -1,23 +1,185 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "urn:nnnotes:schema:story-host", - "title": "Overlay story host screen", "type": "object", - "required": ["format", "kind", "prefabs", "cameraTarget", "overlayRoot", "layoutRoot", "openedSequences", "home", "afterlive", "graphics", "dotween", "bindings", "shaders"], + "required": [ + "format", + "kind", + "cameraTarget", + "dotween", + "prefabs", + "overlayRoot", + "layoutRoot", + "openedSequences", + "home", + "afterlive", + "graphics", + "bindings", + "shaders" + ], "properties": { - "format": {"const": "nnnotes.story-host/1"}, - "kind": {"enum": ["home", "afterlive"]}, - "prefabs": {"type": "object", "additionalProperties": {"type": "string"}}, - "cameraTarget": {"type": "string"}, - "overlayRoot": {"$ref": "#/$defs/location"}, - "layoutRoot": {"allOf": [{"$ref": "#/$defs/location"}, {"type": "object", "required": ["fields"], "properties": {"fields": {"type": "object"}}}]}, - "openedSequences": {"type": "array", "items": {"$ref": "#/$defs/location"}}, - "home": {"type": ["object", "null"]}, - "afterlive": {"type": ["object", "null"]}, - "graphics": {"type": "object", "required": ["graphics"], "additionalProperties": {"type": "string"}}, - "dotween": {"type": "string"}, - "bindings": {"type": "object"}, - "shaders": {"type": "object", "additionalProperties": {"type": "string"}} + "format": { + "const": "nnnotes.story-host/1" + }, + "kind": { + "enum": [ + "home", + "afterlive" + ] + }, + "cameraTarget": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + }, + "dotween": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + }, + "prefabs": { + "type": "object", + "additionalProperties": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + } + }, + "overlayRoot": { + "type": "object", + "required": [ + "prefab", + "path" + ], + "properties": { + "prefab": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + }, + "path": { + "type": "string", + "minLength": 1 + } + } + }, + "layoutRoot": { + "type": "object", + "required": [ + "prefab", + "path", + "fields" + ], + "properties": { + "prefab": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + }, + "path": { + "type": "string", + "minLength": 1 + }, + "fields": { + "type": "object" + } + } + }, + "openedSequences": { + "type": "array", + "items": { + "type": "object", + "required": [ + "prefab", + "path" + ], + "properties": { + "prefab": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + }, + "path": { + "type": "string", + "minLength": 1 + } + } + } + }, + "home": { + "type": [ + "object", + "null" + ] + }, + "afterlive": { + "type": [ + "object", + "null" + ] + }, + "graphics": { + "type": "object", + "required": [ + "graphics" + ], + "properties": { + "graphics": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + }, + "player": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + }, + "quality": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + } + } + }, + "bindings": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "object", + "required": [ + "path", + "attribute" + ], + "properties": { + "path": { + "type": [ + "string", + "null" + ] + }, + "attribute": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "propertyNames": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + } + } + }, + "propertyNames": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + } + }, + "shaders": { + "type": "object", + "additionalProperties": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + } + } }, - "$defs": {"location": {"type": "object", "required": ["prefab", "path"], "properties": {"prefab": {"type": "string"}, "path": {"type": "string"}}}} + "$id": "urn:nnnotes:schema:story-host", + "title": "Overlay story host screen" } diff --git a/src/nnnotes/schemas/story-scene.schema.json b/src/nnnotes/schemas/story-scene.schema.json new file mode 100644 index 0000000..c5e66ce --- /dev/null +++ b/src/nnnotes/schemas/story-scene.schema.json @@ -0,0 +1,146 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "required": [ + "format", + "scene", + "renderSettings", + "prefabs", + "settings", + "graphics", + "bindings", + "shaders" + ], + "properties": { + "format": { + "const": 1 + }, + "scene": { + "type": "array", + "items": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + } + }, + "renderSettings": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + }, + "prefabs": { + "type": "object", + "required": [ + "cameraManager", + "characterField", + "backgroundField", + "globalVolume" + ], + "properties": { + "cameraManager": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + }, + "characterField": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + }, + "backgroundField": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + }, + "globalVolume": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + } + } + }, + "settings": { + "type": "object", + "required": [ + "playerSettings", + "masterIdSettings" + ], + "properties": { + "playerSettings": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + }, + "masterIdSettings": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + } + } + }, + "graphics": { + "type": "object", + "required": [ + "graphics", + "player", + "quality" + ], + "properties": { + "graphics": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + }, + "player": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + }, + "quality": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + } + } + }, + "bindings": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "object", + "required": [ + "path", + "attribute" + ], + "properties": { + "path": { + "type": [ + "string", + "null" + ] + }, + "attribute": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "propertyNames": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + } + } + }, + "propertyNames": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + } + }, + "shaders": { + "type": "object", + "additionalProperties": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + } + } + }, + "$id": "urn:nnnotes:schema:story-scene", + "title": "story-scene document" +} diff --git a/src/nnnotes/schemas/story-simple-ui.schema.json b/src/nnnotes/schemas/story-simple-ui.schema.json index 5b8b2ee..344036f 100644 --- a/src/nnnotes/schemas/story-simple-ui.schema.json +++ b/src/nnnotes/schemas/story-simple-ui.schema.json @@ -1,20 +1,241 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "urn:nnnotes:schema:story-simple-ui", - "title": "Overlay story talk-window and system-message UI", "type": "object", - "required": ["format", "language", "talkWindow", "systemMessage", "uiManager", "settings", "graphics", "texts", "textStyle", "bindings", "shaders"], + "required": [ + "uiManager", + "language", + "graphics", + "texts", + "textStyle", + "bindings", + "shaders", + "format", + "settings", + "talkWindow", + "systemMessage" + ], "properties": { - "format": {"const": "nnnotes.story-simple-ui/1"}, - "language": {"type": "object", "required": ["code", "mode", "field", "lineSpacing", "fontNames", "fontSwap"], "properties": {"code": {"enum": ["ja", "en", "zh-Hant", "zh-Hans", "ko"]}}}, - "talkWindow": {"type": "string"}, - "systemMessage": {"type": "string"}, - "uiManager": {"type": "string"}, - "settings": {"type": "object", "required": ["localizeManager", "tmpSettings", "dotween"], "additionalProperties": {"type": "string"}}, - "graphics": {"type": "object", "required": ["graphics"], "additionalProperties": {"type": "string"}}, - "texts": {"type": "object", "additionalProperties": {"type": "object", "required": ["style", "localized"], "properties": {"style": {"type": "object"}, "localized": {"type": ["object", "null"]}}}}, - "textStyle": {"type": "object"}, - "bindings": {"type": "object"}, - "shaders": {"type": "object", "additionalProperties": {"type": "string"}} - } + "uiManager": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + }, + "language": { + "type": "object", + "required": [ + "code", + "mode", + "field", + "lineSpacing", + "fontNames", + "additionalFontNames", + "materialTypes", + "fontSwap" + ], + "properties": { + "code": { + "enum": [ + "ja", + "en", + "zh-Hant", + "zh-Hans", + "ko" + ] + }, + "mode": { + "enum": [ + 0, + 1, + 2, + 3, + 4 + ] + }, + "field": { + "enum": [ + "japanese", + "english", + "traditionalChinese", + "simplifiedChinese", + "korean" + ] + }, + "lineSpacing": { + "type": "number" + }, + "fontNames": { + "type": "array", + "items": { + "type": "string" + } + }, + "additionalFontNames": { + "type": "array", + "items": { + "type": "string" + } + }, + "materialTypes": { + "type": "array", + "items": { + "type": "string" + } + }, + "fontSwap": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + }, + "graphics": { + "type": "object", + "required": [ + "graphics" + ], + "properties": { + "graphics": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + }, + "player": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + }, + "quality": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + } + } + }, + "texts": { + "type": "object", + "additionalProperties": { + "type": "object", + "required": [ + "localized", + "style" + ], + "properties": { + "localized": { + "type": [ + "object", + "null" + ], + "required": [ + "fontAsset", + "material", + "lineSpacing" + ], + "properties": { + "fontAsset": { + "type": "string", + "minLength": 1 + }, + "material": { + "type": "string", + "minLength": 1 + }, + "lineSpacing": { + "type": "number" + } + } + }, + "style": { + "type": "object" + } + } + }, + "propertyNames": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + } + }, + "textStyle": { + "type": "object" + }, + "bindings": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "object", + "required": [ + "path", + "attribute" + ], + "properties": { + "path": { + "type": [ + "string", + "null" + ] + }, + "attribute": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "propertyNames": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + } + } + }, + "propertyNames": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + } + }, + "shaders": { + "type": "object", + "additionalProperties": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + } + }, + "format": { + "const": "nnnotes.story-simple-ui/1" + }, + "settings": { + "type": "object", + "required": [ + "dotween", + "tmpSettings", + "localizeManager" + ], + "properties": { + "dotween": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + }, + "tmpSettings": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + }, + "localizeManager": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + } + } + }, + "talkWindow": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + }, + "systemMessage": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + } + }, + "$id": "urn:nnnotes:schema:story-simple-ui", + "title": "Overlay story talk-window and system-message UI" } diff --git a/src/nnnotes/schemas/story-sound.schema.json b/src/nnnotes/schemas/story-sound.schema.json new file mode 100644 index 0000000..ad750b8 --- /dev/null +++ b/src/nnnotes/schemas/story-sound.schema.json @@ -0,0 +1,56 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "additionalProperties": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "required": [ + "file", + "sampleRate", + "samples" + ], + "properties": { + "file": { + "type": "string" + }, + "sampleRate": { + "type": "integer", + "minimum": 1 + }, + "samples": { + "type": "integer", + "minimum": 0 + }, + "channels": { + "type": "integer", + "minimum": 1 + }, + "categories": { + "type": "array", + "items": { + "type": "string" + } + }, + "encoderDelay": { + "type": "integer", + "minimum": 0 + }, + "loopStart": { + "type": [ + "integer", + "null" + ], + "minimum": 0 + }, + "loopEnd": { + "type": [ + "integer", + "null" + ], + "minimum": 0 + } + } + }, + "$id": "urn:nnnotes:schema:story-sound", + "title": "story-sound document" +} diff --git a/src/nnnotes/schemas/story-ui.schema.json b/src/nnnotes/schemas/story-ui.schema.json new file mode 100644 index 0000000..56e3d0c --- /dev/null +++ b/src/nnnotes/schemas/story-ui.schema.json @@ -0,0 +1,283 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "required": [ + "format", + "widget", + "chatWidget", + "uiManager", + "letterbox", + "talkWindows", + "dialogs", + "clips", + "controllers", + "language", + "settings", + "graphics", + "texts", + "textStyle", + "bindings", + "shaders" + ], + "properties": { + "format": { + "const": 1 + }, + "widget": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + }, + "chatWidget": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + }, + "uiManager": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + }, + "letterbox": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + }, + "talkWindows": { + "type": "object", + "additionalProperties": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + } + }, + "dialogs": { + "type": "object", + "additionalProperties": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + } + }, + "clips": { + "type": "object", + "additionalProperties": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + } + }, + "controllers": { + "type": "object", + "additionalProperties": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + } + }, + "language": { + "type": "object", + "required": [ + "code", + "mode", + "field", + "lineSpacing", + "fontNames", + "additionalFontNames", + "materialTypes", + "fontSwap" + ], + "properties": { + "code": { + "enum": [ + "ja", + "en", + "zh-Hant", + "zh-Hans", + "ko" + ] + }, + "mode": { + "enum": [ + 0, + 1, + 2, + 3, + 4 + ] + }, + "field": { + "enum": [ + "japanese", + "english", + "traditionalChinese", + "simplifiedChinese", + "korean" + ] + }, + "lineSpacing": { + "type": "number" + }, + "fontNames": { + "type": "array", + "items": { + "type": "string" + } + }, + "additionalFontNames": { + "type": "array", + "items": { + "type": "string" + } + }, + "materialTypes": { + "type": "array", + "items": { + "type": "string" + } + }, + "fontSwap": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + }, + "settings": { + "type": "object", + "required": [ + "dotween", + "tmpSettings", + "localizeManager", + "masterIdSettings" + ], + "properties": { + "dotween": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + }, + "tmpSettings": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + }, + "localizeManager": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + }, + "masterIdSettings": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + } + } + }, + "graphics": { + "type": "object", + "required": [ + "graphics" + ], + "properties": { + "graphics": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + }, + "player": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + }, + "quality": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + } + } + }, + "texts": { + "type": "object", + "additionalProperties": { + "type": "object", + "required": [ + "localized", + "style" + ], + "properties": { + "localized": { + "type": [ + "object", + "null" + ], + "required": [ + "fontAsset", + "material", + "lineSpacing" + ], + "properties": { + "fontAsset": { + "type": "string", + "minLength": 1 + }, + "material": { + "type": "string", + "minLength": 1 + }, + "lineSpacing": { + "type": "number" + } + } + }, + "style": { + "type": "object" + } + } + }, + "propertyNames": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + } + }, + "textStyle": { + "type": "object" + }, + "bindings": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "object", + "required": [ + "path", + "attribute" + ], + "properties": { + "path": { + "type": [ + "string", + "null" + ] + }, + "attribute": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "propertyNames": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + } + } + }, + "propertyNames": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + } + }, + "shaders": { + "type": "object", + "additionalProperties": { + "type": "string", + "pattern": "^.+:-?[0-9]+$" + } + } + }, + "$id": "urn:nnnotes:schema:story-ui", + "title": "story-ui document" +} diff --git a/src/nnnotes/schemas/story-video.schema.json b/src/nnnotes/schemas/story-video.schema.json new file mode 100644 index 0000000..1416adf --- /dev/null +++ b/src/nnnotes/schemas/story-video.schema.json @@ -0,0 +1,85 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "required": [ + "format", + "file", + "codec", + "width", + "height", + "displayWidth", + "displayHeight", + "frameRate", + "frames", + "tracks" + ], + "properties": { + "format": { + "const": 1 + }, + "file": { + "type": "string" + }, + "codec": { + "type": "string" + }, + "width": { + "type": "integer", + "minimum": 1 + }, + "height": { + "type": "integer", + "minimum": 1 + }, + "displayWidth": { + "type": "integer", + "minimum": 1 + }, + "displayHeight": { + "type": "integer", + "minimum": 1 + }, + "frameRate": { + "type": "array", + "minItems": 2, + "maxItems": 2, + "items": { + "type": "integer", + "minimum": 1 + } + }, + "frames": { + "type": "integer", + "minimum": 0 + }, + "tracks": { + "type": "array", + "items": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "required": [ + "kind", + "channel", + "codec" + ], + "properties": { + "kind": { + "type": "string" + }, + "channel": { + "type": "integer", + "minimum": 0 + }, + "codec": { + "type": "string" + }, + "source": { + "type": "string" + } + } + } + } + }, + "$id": "urn:nnnotes:schema:story-video", + "title": "story-video document" +} diff --git a/src/nnnotes/schemas/story.schema.json b/src/nnnotes/schemas/story.schema.json new file mode 100644 index 0000000..f000dd4 --- /dev/null +++ b/src/nnnotes/schemas/story.schema.json @@ -0,0 +1,176 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "required": [ + "format", + "advId", + "episode", + "parts" + ], + "properties": { + "format": { + "const": 1 + }, + "advId": { + "type": "integer", + "minimum": 0 + }, + "episode": { + "const": "episode.json" + }, + "chats": { + "type": "object" + }, + "effects": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "host": { + "type": [ + "object", + "null" + ] + }, + "parts": { + "type": "object", + "required": [ + "scene", + "assets", + "models", + "sounds", + "ui", + "fonts" + ], + "properties": { + "scene": { + "type": "string", + "pattern": "^[^/\\\\?#]+(?:/[^/\\\\?#]+)+\\.json$" + }, + "chat": { + "anyOf": [ + { + "type": "string", + "pattern": "^[^/\\\\?#]+(?:/[^/\\\\?#]+)+\\.json$" + }, + { + "type": "null" + } + ] + }, + "crilips": { + "anyOf": [ + { + "type": "string", + "pattern": "^[^/\\\\?#]+(?:/[^/\\\\?#]+)+\\.json$" + }, + { + "type": "null" + } + ] + }, + "assets": { + "type": "object", + "additionalProperties": { + "type": "string", + "pattern": "^[^/\\\\?#]+(?:/[^/\\\\?#]+)+\\.json$" + } + }, + "models": { + "type": "object", + "additionalProperties": { + "type": "string", + "pattern": "^[^/\\\\?#]+(?:/[^/\\\\?#]+)+\\.json$" + } + }, + "sounds": { + "type": "object", + "additionalProperties": { + "type": "string", + "pattern": "^[^/\\\\?#]+(?:/[^/\\\\?#]+)+\\.json$" + } + }, + "videos": { + "type": "object", + "additionalProperties": { + "type": "string", + "pattern": "^[^/\\\\?#]+(?:/[^/\\\\?#]+)+\\.json$" + } + }, + "host": { + "type": "object", + "additionalProperties": { + "type": "string", + "pattern": "^[^/\\\\?#]+(?:/[^/\\\\?#]+)+\\.json$" + } + }, + "spots": { + "type": "object", + "additionalProperties": { + "type": "string", + "pattern": "^[^/\\\\?#]+(?:/[^/\\\\?#]+)+\\.json$" + } + }, + "rooms": { + "type": "object", + "additionalProperties": { + "type": "string", + "pattern": "^[^/\\\\?#]+(?:/[^/\\\\?#]+)+\\.json$" + } + }, + "ui": { + "type": "object", + "additionalProperties": { + "type": "string", + "pattern": "^[^/\\\\?#]+(?:/[^/\\\\?#]+)+\\.json$" + }, + "propertyNames": { + "enum": [ + "ja", + "en", + "zh-Hant", + "zh-Hans", + "ko" + ] + } + }, + "fonts": { + "type": "object", + "additionalProperties": { + "type": "string", + "pattern": "^[^/\\\\?#]+(?:/[^/\\\\?#]+)+\\.json$" + }, + "propertyNames": { + "enum": [ + "ja", + "en", + "zh-Hant", + "zh-Hans", + "ko" + ] + } + }, + "simpleui": { + "type": "object", + "additionalProperties": { + "type": "string", + "pattern": "^[^/\\\\?#]+(?:/[^/\\\\?#]+)+\\.json$" + }, + "propertyNames": { + "enum": [ + "ja", + "en", + "zh-Hant", + "zh-Hans", + "ko" + ] + } + } + }, + "additionalProperties": false + } + }, + "$id": "urn:nnnotes:schema:story", + "title": "story document" +} diff --git a/src/nnnotes/schemas/ui-library.schema.json b/src/nnnotes/schemas/ui-library.schema.json index e130916..963e743 100644 --- a/src/nnnotes/schemas/ui-library.schema.json +++ b/src/nnnotes/schemas/ui-library.schema.json @@ -1,6 +1,5 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "urn:nnnotes:schema:ui-library", "type": "object", "required": [ "format", @@ -144,5 +143,6 @@ } } }, + "$id": "urn:nnnotes:schema:ui-library", "title": "UI product library" } diff --git a/src/nnnotes/schemas/ui-pack.schema.json b/src/nnnotes/schemas/ui-pack.schema.json index ed6b3cc..1c48070 100644 --- a/src/nnnotes/schemas/ui-pack.schema.json +++ b/src/nnnotes/schemas/ui-pack.schema.json @@ -1,6 +1,5 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "urn:nnnotes:schema:ui-pack", "type": "object", "required": [ "format", @@ -125,5 +124,6 @@ } } }, + "$id": "urn:nnnotes:schema:ui-pack", "title": "UI object product" } diff --git a/tests/content/test_live.py b/tests/content/test_live.py index 6e81030..3111bba 100644 --- a/tests/content/test_live.py +++ b/tests/content/test_live.py @@ -7,8 +7,16 @@ from nnnotes.pipeline.assemble import Objects from nnnotes.pipeline.depot import Depot from tests.helpers import live as lh +from tests.helpers.schemas import validate -PARTS = {"scene": "scenes/default.json", "music": "music/7.json"} +PARTS = { + "scene": "scenes/default.json", + "music": "music/7.json", + "notes": "notes/default.json", + "noteskin": "noteskins/skin001.json", + "noteeffect": "noteeffects/effect001.json", + "sounds": "sounds/default.json", +} def objects_of(tmp_path, **kw) -> Objects: @@ -61,6 +69,7 @@ def test_the_record_holds_the_musics_rows_the_difficulty_and_the_band(): def test_the_live_holds_its_chart_converted_and_names_its_parts(tmp_path, gz): record = live.live_record(lh.master(), "7/expert") doc, chart, converted, keep = live.live(objects_of(tmp_path, gz=gz), record, PARTS) + validate("live", doc) notes, mirror = score.convert(lh.CHART), score.convert(lh.CHART, mirror=True) assert doc == { "format": live.LIVE_FORMAT, @@ -76,7 +85,7 @@ def test_the_live_holds_its_chart_converted_and_names_its_parts(tmp_path, gz): "lastNoteTimeMs": notes["lastNoteTimeMs"], "master": record["music"], "jacket": lh.JACKET, - "parts": {"music": "music/7.json", "scene": "scenes/default.json"}, + "parts": PARTS, } assert chart == contract.encode(lh.CHART) # as shipped, un-gzipped source = { diff --git a/tests/content/test_live2d.py b/tests/content/test_live2d.py index 0a82828..a098bdb 100644 --- a/tests/content/test_live2d.py +++ b/tests/content/test_live2d.py @@ -8,6 +8,7 @@ from nnnotes.pipeline.assemble import Objects from nnnotes.pipeline.depot import Depot from tests.helpers import live2dmodel as lm +from tests.helpers.schemas import validate KEY_A = "Character/Live2D/001_adv/adv_model_a/model/adv_model_a" KEY_B = "Character/Live2D/sub_x/adv_model_b/model/adv_model_b" @@ -20,6 +21,7 @@ def objects_of(tmp_path, objs=None, roots=(lm.ROOT, lm.MASK_MAT, lm.CULL_MAT)) - def test_model_document(tmp_path): doc, keep, roles = live2d.model(objects_of(tmp_path), lm.KEY) + validate("model-json", doc) assert doc == { "format": live2d.MODEL_FORMAT, "name": "m001", diff --git a/tests/content/test_livenotes.py b/tests/content/test_livenotes.py index 55d25ad..18a3213 100644 --- a/tests/content/test_livenotes.py +++ b/tests/content/test_livenotes.py @@ -6,6 +6,7 @@ from nnnotes.pipeline.assemble import Objects from nnnotes.pipeline.depot import Depot from tests.helpers import livenotes as ln +from tests.helpers.schemas import validate FLICKS = sorted(livenotes.FLICK_SKIN_UNITS) ARROW = [{"path": "arrow", "attribute": "m_Color.a"}] @@ -19,7 +20,14 @@ def objects_of(tmp_path, keys, objs=None) -> Objects: def test_the_notes_document(tmp_path): record = livenotes.notes_record(ln.master()) + for row in record["MasterLiveSettings"]: + row["_value"] = str(row["_value"]) + for name in ("SlideOpacity", "GuideOpacity"): + item = next(i for i, key in livenotes.NOTE_ITEMS.items() if key == name) + record["MasterOptionDefault"].append({"_presetId": 1, "_optionItemType": item, "_valueString": "100"}) + record["MasterOptionRange"].append({"_optionItemType": item, "_minValue": 0, "_maxValue": 100}) doc, keep, roles = livenotes.notes(objects_of(tmp_path, ln.NOTES_KEYS), record) + validate("live-notes", doc) assert doc["format"] == livenotes.NOTES_FORMAT assert doc["prefabs"] == {n: ln.prefab_id(n) for n in livenotes.NOTE_PREFABS} assert doc["laneEffect"] == ln.LANE @@ -61,6 +69,7 @@ def test_the_notes_settings(tmp_path): def test_a_skin_binds_the_arrow_loop_of_its_flick_units(tmp_path): doc, keep, _ = livenotes.noteskin(objects_of(tmp_path, ln.SKIN_KEYS), "skin001") + validate("live-noteskin", doc) assert (doc["format"], doc["name"], doc["skin"]) == (livenotes.NOTES_FORMAT, "skin001", ln.SKIN) # the flick unit's loop plays on the flick view's time sync Animator; the left unit has none, the right is unset assert doc["bindings"] == {ln.prefab_id("flick_note_view"): {"flick_note_view/anim_root": {ln.SKIN_CLIP: ARROW}}} @@ -69,6 +78,7 @@ def test_a_skin_binds_the_arrow_loop_of_its_flick_units(tmp_path): def test_an_effect_set(tmp_path): doc, _, roles = livenotes.noteeffect(objects_of(tmp_path, ln.EFFECT_KEYS), "effect001Light") + validate("live-noteeffect", doc) assert (doc["name"], doc["settings"]) == ("effect001Light", ln.EFFECT) assert doc["bindings"][ln.EFFECT_FX] == {"note_normal/root": {ln.FX_CLIP: GLOW}} assert doc["shaders"] == {"Sirius/Sprite": ln.SHADER} and set(roles) == {ln.SHADER} diff --git a/tests/content/test_livescene.py b/tests/content/test_livescene.py index e413932..7451377 100644 --- a/tests/content/test_livescene.py +++ b/tests/content/test_livescene.py @@ -6,6 +6,7 @@ from nnnotes.pipeline.assemble import Objects from nnnotes.pipeline.depot import Depot from tests.helpers import livescene as ls +from tests.helpers.schemas import validate LANE = [{"path": "LiveGameLane", "attribute": "m_Color.a"}] @@ -20,7 +21,12 @@ def record(): def test_the_scene_document(tmp_path): - doc, keep, roles = livescene.scene(objects_of(tmp_path, record()), record()) + settings = record() + for name in ("JudgePositionDisplay", "NoteStartPosition"): + item = next(i for i, key in livescene.OPTION_ITEMS.items() if key == name) + settings["MasterOptionDefault"].append({"_presetId": 1, "_optionItemType": item, "_valueString": "0"}) + doc, keep, roles = livescene.scene(objects_of(tmp_path, settings), settings) + validate("live-scene", doc) assert doc["format"] == livescene.SCENE_FORMAT assert doc["scene"] == [ls.LIVE, ls.GAME, ls.DIRECTOR] and doc["renderSettings"] == ls.RENDER assert doc["graphics"] == {"graphics": ls.GRAPHICS, "quality": ls.QUALITY, "player": ls.PLAYER} diff --git a/tests/content/test_room.py b/tests/content/test_room.py index 3331bf6..6e0a7d2 100644 --- a/tests/content/test_room.py +++ b/tests/content/test_room.py @@ -166,6 +166,9 @@ def objects_of(tmp_path, objs=None) -> Objects: def test_room_bakes_the_drawn_meshes_and_lists_what_draws_nothing(tmp_path): glb, doc = room.room(objects_of(tmp_path), so.BACKGROUND) + from tests.helpers.schemas import validate + + validate("room", doc) gdoc, body = gltf.read(glb) assert [n["name"] for n in gdoc["nodes"]] == ["quad", "quad2", "quad"] # wall, floor, wall2: hierarchy order assert [n["extras"]["unityActive"] for n in gdoc["nodes"]] == [True, False, True] diff --git a/tests/content/test_spot.py b/tests/content/test_spot.py index 19d928c..841c0d0 100644 --- a/tests/content/test_spot.py +++ b/tests/content/test_spot.py @@ -36,6 +36,9 @@ def test_master_record(): def test_spot_document(tmp_path): doc, files = spot.spot(objects_of(tmp_path), so.SITUATION, spot.master_record(so.MASTER, so.SITUATION)) + from tests.helpers.schemas import validate + + validate("spot", doc) assert doc == { "spotId": 3, "master": so.MASTER["MasterHomeSpot"][0], diff --git a/tests/content/test_storyasset.py b/tests/content/test_storyasset.py index d5ddae8..3b8d9f8 100644 --- a/tests/content/test_storyasset.py +++ b/tests/content/test_storyasset.py @@ -41,6 +41,9 @@ def test_manifest_name_rejects_other_keys(key): def test_the_document_of_a_prefab_key(tmp_path): keys = {KEY: [PREFAB, "CAB-n:999"]} # 999 is not in the table: not one of the key's objects doc, _, _ = storyasset.asset(objects_of(tmp_path, keys, SMALL), KEY) + from tests.helpers.schemas import validate + + validate("story-asset", doc) assert doc == { "format": storyasset.ASSET_FORMAT, "key": KEY, diff --git a/tests/content/test_storyui.py b/tests/content/test_storyui.py index 8042e54..29b0b21 100644 --- a/tests/content/test_storyui.py +++ b/tests/content/test_storyui.py @@ -9,6 +9,7 @@ from nnnotes.pipeline.depot import Depot from tests.helpers import storyui as sui from tests.helpers.livenotes import comp +from tests.helpers.schemas import validate def ui(objects, language, master): @@ -37,6 +38,7 @@ def test_the_story_ui_keys_are_the_keys_of_its_table(): def test_the_document_has_the_story_ui_fields_in_the_language(tmp_path): doc, keep, roles = ui(story(tmp_path), "zh-Hant", sui.MASTER_RECORD) + validate("story-ui", doc) assert set(doc) == { "format", "language", diff --git a/tests/content/test_storyvideo.py b/tests/content/test_storyvideo.py index 8921311..f17f202 100644 --- a/tests/content/test_storyvideo.py +++ b/tests/content/test_storyvideo.py @@ -75,6 +75,9 @@ def test_video_describes_the_movie_and_carries_the_tracks_of_the_webm(): key = "Cri/Video/adv/m/m" objects = table(key, {"movieInfo": movie_info()}) doc = storyvideo.video(objects, key, TRACKS) + from tests.helpers.schemas import validate + + validate("story-video", doc) assert doc == { "format": 1, "file": "movie.webm", diff --git a/tests/helpers/schemas.py b/tests/helpers/schemas.py new file mode 100644 index 0000000..0c29aff --- /dev/null +++ b/tests/helpers/schemas.py @@ -0,0 +1,9 @@ +"""Validate synthetic product documents against the schemas shipped in nnnotes.""" + +from jsonschema import Draft202012Validator + +from nnnotes import schemas + + +def validate(name: str, document: dict) -> None: + Draft202012Validator(schemas.load(name)).validate(document) diff --git a/tests/helpers/storyui.py b/tests/helpers/storyui.py index abb9176..3b94258 100644 --- a/tests/helpers/storyui.py +++ b/tests/helpers/storyui.py @@ -219,7 +219,26 @@ def table_doc(depot, objs: dict | None = None, keys: dict | None = None, extra_r def font_settings() -> fontsettings.FontSettings: """The font settings the texts draw with: the Japanese and the traditional Chinese font assets, and the default and outline materials of the Japanese font and the outline material of the traditional Chinese one.""" - face = {"m_Scale": 1.0, "m_PointSize": 100.0, "m_LineHeight": 120.0, "m_AscentLine": 90.0, "m_DescentLine": -30.0} + face = { + "m_Scale": 1.0, + "m_PointSize": 100.0, + "m_UnitsPerEM": 1000, + "m_LineHeight": 120.0, + "m_AscentLine": 90.0, + "m_DescentLine": -30.0, + "m_CapLine": 70.0, + "m_MeanLine": 50.0, + "m_Baseline": 0.0, + "m_SuperscriptOffset": 60.0, + "m_SuperscriptSize": 0.5, + "m_SubscriptOffset": -20.0, + "m_SubscriptSize": 0.5, + "m_UnderlineOffset": -10.0, + "m_UnderlineThickness": 2.0, + "m_StrikethroughOffset": 30.0, + "m_StrikethroughThickness": 2.0, + "m_TabWidth": 40.0, + } fonts = { name: {"faceInfo": face, "normalSpacingOffset": 0.0, "boldSpacing": 7.0} for name in ("MainJ SDF", "MainT SDF") } diff --git a/tests/pipeline/test_objexport.py b/tests/pipeline/test_objexport.py index 6316257..633f302 100644 --- a/tests/pipeline/test_objexport.py +++ b/tests/pipeline/test_objexport.py @@ -976,6 +976,9 @@ def test_items_name_the_objects_they_reference(exported): def test_prefab(exported): depot, doc = exported p = obj(depot, doc, f"{oid(60)}#json") + from tests.helpers.schemas import validate + + validate("prefab", p) assert p["name"] == "root" and [n["path"] for n in p["nodes"]] == ["root", "root/child"] root, child = p["nodes"] assert root["pathId"] == "60" and root["transformPathId"] == "61" and root["layer"] == 5