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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ schemas in `nnnotes.schemas` and the documented Python API. Before 1.0 a minor v

### Features

- Store content, task inputs, results and catalog versions in a persistent depot with optional remote storage, verified collection and bounded source downloads.
- Assemble Live2D, room, spot, live, story and UI products from shared object tables and explicit manifest references.
- Generate localized live and story text products with game or supplied fonts, shared glyph atlases and material records.
- Build named sites through depot product, player read-set, asset and index tasks.
- Retain unselected regional topics, publish immutable product dependencies in order, and restore complete sites from their saved depot layouts.
- Offer Live variant products from chart manifests and select story UI/font languages explicitly.
Expand All @@ -16,6 +19,7 @@ schemas in `nnnotes.schemas` and the documented Python API. Before 1.0 a minor v

- Site construction uses shared product requests, named depot catalogs and ordered layout publication. Player page collection and catalog model queries have dedicated interfaces.
- Conversion memoization is bounded within each process, with persistent reuse provided by depot task results.
- Product assembly consumes stored class-converter outputs. Font lookup, texel packing and material construction share object-based implementations.

## [0.2.1](https://github.com/MetaSekaiLab/nnnotes/compare/v0.2.0...v0.2.1) - 2026-10-13

Expand Down
4 changes: 3 additions & 1 deletion docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ subpackage of `nnnotes` and imports only the layers below it.
| Products | `nnnotes.site`, `nnnotes.musicdata` | the ournotes-player site (charts, models, stories, indexes); the music data file and its deck statistics |
| Pipeline | `nnnotes.pipeline` | stages, task descriptions and results, the content-addressed depot, plans, layouts, the catalog versions, the views of the master data |
| Content | `nnnotes.content` | the game's subjects as exports: ADV episodes and stories, lives and scores, spots, rooms, the UI library |
| Unity objects | `nnnotes.unity` | bundle reading, object ids, the atoms (the pure functions objects are converted with: texture decoding, sprite images, mesh arrays, glTF, PNG, file sniffing), the converters (one per Unity class, `nnnotes.unity.convert`), clip bindings named in the hierarchy that plays a clip (`nnnotes.unity.bindings`), the type trees shipped for the player's script classes (`nnnotes.unity.scripttypes`), the typetree exporter, the player data, Live2D prefabs, text styles and TextMesh Pro fonts |
| Unity objects | `nnnotes.unity` | bundle reading, object ids, atomic texture/sprite/mesh/glTF/PNG conversions, class converters (`nnnotes.unity.convert`), clip bindings (`nnnotes.unity.bindings`), script type trees (`nnnotes.unity.scripttypes`), player data, text styles, font protocols and texel packing |
| File formats | `nnnotes.formats` | formats read from bytes: CRI ACB/AWB and HCA, USM movies, CRI Lips data, shaders, motions |
| Sources | `nnnotes.source` | the configuration, the APK, Addressables catalogs and bundles, the game API, master data, languages, `Session` |
| Core | `nnnotes.core` | the one JSON writer, canonical JSON and its hashes, media types, the process caches and the downloads cache |
Expand Down Expand Up @@ -40,6 +40,8 @@ decrypted bundles in the depot's `downloads/` directory.

`export live`, `export story`, `export live2d`, `export spot`, `export room` and `export ui` request the products of selected subjects through that same pipeline. Stories first produce an episode, then request its referenced products. Shared objects and unchanged products reuse committed tasks.

Class converters emit object records and binary files. Product assemblers read those stored records through an object table and identify shared dependencies by object id and product manifest. Text products use the same font lookup, material and in-memory atlas functions. A product request contains its complete object and media dependencies before assembly.

`site build` uses the same product requests, then runs player read-set, asset, document and index tasks. A named catalog retains unselected topics and references immutable products. Publication writes dependencies before entry manifests and indexes. The successful run stores the complete layout so a new output directory can be restored through the depot.

The site's `Builder` composes product snapshots, catalog indexes and player page files. Product assembly and conversion belong to the pipeline and content layers. `catalog_models` queries model subjects from a source catalog.
Expand Down
2 changes: 2 additions & 0 deletions docs/products.md
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,8 @@ every episode whose key the selection holds.

A stage with `INCLUDE_FONTS = True` requests traversal through its referenced font objects. The request records these roots in `font_roots`; unit selection and assembly follow the same rule. Other products keep their own font selection behavior.

The maintainer tool `scripts/gen_font_settings.py TASK.json --depot DEPOT -o OUTPUT.json` generates a font settings table from a saved text-product task. It reads the task's object table, font-bundle identities and APK manifest through the depot. The task must contain the complete font inputs, as used by `story.fonts` or `live.ui`.

## Site snapshots

`nnnotes.site.products.Snapshot` takes a depot and product-layout manifests keyed by path. It builds each product's
Expand Down
4 changes: 2 additions & 2 deletions scripts/atom_eval.py
Original file line number Diff line number Diff line change
Expand Up @@ -1184,7 +1184,7 @@ def _apply_atoms(atoms) -> None:
def _import_stage_modules() -> dict:
"""Import what a unity.export task needs, timed (wall, CPU)."""
t0, c0 = time.perf_counter(), time.process_time()
for m in ("numpy", "PIL.Image", "nnnotes.pipeline.objexport", "nnnotes.unity.export"):
for m in ("numpy", "PIL.Image", "nnnotes.pipeline.objexport", "nnnotes.unity.texels"):
importlib.import_module(m)
return {"wall": round(time.perf_counter() - t0, 4), "cpu": round(time.process_time() - c0, 4)}

Expand Down Expand Up @@ -1409,7 +1409,7 @@ def cmd_m3_one(a) -> None:

# ---------------------------------------------------------------- imports
def cmd_imports(a) -> dict:
sets = [m.split(",") for m in (a.module or ["numpy,PIL.Image,nnnotes.pipeline.objexport,nnnotes.unity.export"])]
sets = [m.split(",") for m in (a.module or ["numpy,PIL.Image,nnnotes.pipeline.objexport,nnnotes.unity.texels"])]
code = (
"import resource, sys, time, json\n"
"t = time.perf_counter(); c = time.process_time()\n"
Expand Down
69 changes: 30 additions & 39 deletions scripts/gen_font_settings.py
Original file line number Diff line number Diff line change
@@ -1,54 +1,45 @@
"""Regenerate the font settings table the open-font exports read (maintainer tool; not installed, not needed to use
nnnotes).

With `--fonts open` (the default) the story, start canvas and talk window exports give each localized game font
asset an open font asset with that asset's point size, padding, render mode, style settings, line metrics, fallback
chain and text materials. nnnotes ships these values in `src/nnnotes/unity/fontsettings.json` (fontsettings.FORMAT), so
those exports read no game font asset; this script reads them from the game's font assets and writes the file.

Supporting a new game version
-----------------------------
When the game changes its font assets or their materials (an open-font export stops with "... not in the font
settings", or the values differ from the game's), regenerate the table with a configured catalog and APK of that
version (`nnnotes config check`):

python scripts/gen_font_settings.py [--region REGION] [--game-version VERSION] [-o PATH]

It reads LocalizeManager's font names of every language and their material types from the player data, each of
those font assets with the assets of its fallback chain, their default materials and every localized text material
("<font> - <type>") the catalog has, and writes the table (`--game-version` default: the APK's versionName). The
`fonts` extra must be installed.
"""Generate a font settings table from the inputs of a saved text-product task.

Usage: python scripts/gen_font_settings.py TASK.json --depot DEPOT [-o OUTPUT]

The task's object table, font-bundle identities and APK manifest are read through the depot. The table contains
the font assets LocalizeManager names, their fallback chains, materials and object ids. Text products with these
inputs include story.fonts and live.ui. The fonts extra must be installed.
"""

from __future__ import annotations

import argparse
import sys
import tempfile
from pathlib import Path

from nnnotes.content import storyui
from nnnotes.content.prefabs import mono_json
from nnnotes.pipeline import contract, textfonts
from nnnotes.pipeline.assemble import Objects
from nnnotes.pipeline.depot import open_depot
from nnnotes.source.config import Config, use
from nnnotes.source.session import Session
from nnnotes.unity import fontsettings, tmpfont
from nnnotes.unity.export import Exporter
from nnnotes.unity.player import PlayerData
from nnnotes.unity import fontsettings, textstyle


def generate(task, depot) -> dict:
"""Font settings from the complete stored inputs of a text product task."""
objects = Objects(contract.loads(depot.input_bytes(task.input("objects"))), depot)
localize = storyui.script_root(objects, "LocalizeManager")
language = textstyle.language_fonts_json(mono_json(objects, localize))
return textfonts.font_settings(task, objects, language).doc


def main(argv=None) -> None:
p = argparse.ArgumentParser(
description="regenerate src/nnnotes/unity/fontsettings.json from the game's font assets"
)
p.add_argument("--region", help="region of the catalog (default: [catalog] region)")
p.add_argument("--game-version", help="game version to record (default: the APK manifest's versionName)")
p.add_argument("-o", "--out", type=Path, default=fontsettings.FILE, help="output file")
args = p.parse_args(argv)
cfg = use(Config.load())
session = Session(cfg)
cat = session.catalog(region=args.region)
player = PlayerData(session.apk(args.region))
with tempfile.TemporaryDirectory(prefix="fontsettings-") as work:
ex = Exporter(cat, Path(work), player=player, textures="deferred")
doc = tmpfont.build_font_settings(ex, player, args.game_version or player.game_version)
parser = argparse.ArgumentParser(description="generate font settings from a saved text-product task")
parser.add_argument("task", type=Path, help="nnnotes.task/1 JSON with font inputs")
parser.add_argument("--depot", type=Path, help="depot holding the task inputs ([paths] depot)")
parser.add_argument("-o", "--out", type=Path, default=fontsettings.FILE, help="output table")
args = parser.parse_args(argv)
overrides = {("paths", "depot"): str(args.depot.resolve())} if args.depot else {}
cfg = use(Config.load(overrides=overrides))
task = contract.Task.from_json(contract.loads(args.task.read_bytes()))
doc = generate(task, open_depot(cfg))
fontsettings.write(doc, args.out)
print(
f"{args.out}: {len(doc['fonts'])} font assets, {len(doc['materials'])} materials, "
Expand Down
Loading
Loading