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
30 changes: 25 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,19 @@ jobs:
matrix:
python-version: ["3.11", "3.12", "3.13"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- uses: actions/checkout@v7
- uses: actions/setup-python@v7
with:
python-version: ${{ matrix.python-version }}
cache: pip
cache-dependency-path: pyproject.toml
- uses: Swatinem/rust-cache@v2
with:
workspaces: rust
- uses: astral-sh/setup-uv@v7
with:
enable-cache: true
- name: Install
- name: Install (builds the extension module nnnotes._deck)
run: uv pip install --system -e ".[test]"
- name: Lint
run: python -m pyflakes src tests
Expand All @@ -43,14 +46,17 @@ jobs:
# versions installed an output change without a stage or converter version bump fails instead of being skipped.
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- uses: actions/checkout@v7
- uses: actions/setup-python@v7
with:
python-version: "3.13"
cache: pip
cache-dependency-path: |
pyproject.toml
.github/goldens-constraints.txt
- uses: Swatinem/rust-cache@v2
with:
workspaces: rust
- uses: astral-sh/setup-uv@v7
with:
enable-cache: true
Expand All @@ -60,3 +66,17 @@ jobs:
env:
GOLDENS_STRICT: "1"
run: python -m pytest tests/test_goldens.py tests/test_objexport.py

rust:
# the extension module's own code (rust/); ournotes-deck is tested in its repository
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: Swatinem/rust-cache@v2
with:
workspaces: rust
- name: Format and lints
working-directory: rust
run: |
cargo fmt --check
cargo clippy --locked --all-targets -- -D warnings
6 changes: 3 additions & 3 deletions .github/workflows/commitlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ jobs:
commitlint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
with:
fetch-depth: 0
- uses: actions/setup-node@v4
- uses: actions/setup-node@v7
with:
node-version: 20
node-version: 24
- name: Install commitlint
run: npm install --no-save --no-package-lock @commitlint/cli@19 @commitlint/config-conventional@19
- name: Pull request title
Expand Down
50 changes: 50 additions & 0 deletions .github/workflows/deck.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: Deck model

# nnnotes builds the deck model ournotes-deck at the commit rust/Cargo.toml pins, so that one nnnotes version always
# carries one deck model. When ournotes-deck's main moves past that commit, this opens a pull request that moves the
# pin; CI then builds and tests it like any change.
on:
schedule:
- cron: "17 3 * * *"
workflow_dispatch:

permissions:
contents: write
pull-requests: write

jobs:
update:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Compare the pinned commit with ournotes-deck's main
id: deck
env:
GH_TOKEN: ${{ github.token }}
run: |
pinned=$(sed -nE 's/^ournotes-deck = .*rev = "([0-9a-f]{40})".*/\1/p' rust/Cargo.toml)
head=$(git ls-remote https://github.com/empty-sekai/ournotes-deck refs/heads/main | cut -f1)
# ahead: main contains the pinned commit and more; behind or diverged: leave the pin to a person
status=$(gh api "repos/empty-sekai/ournotes-deck/compare/$pinned...$head" --jq .status)
echo "pinned $pinned, main $head: $status"
echo "pinned=$pinned" >> "$GITHUB_OUTPUT"
echo "head=$head" >> "$GITHUB_OUTPUT"
echo "move=$([ "$status" = ahead ] && echo true || echo false)" >> "$GITHUB_OUTPUT"
- name: Move the pin
if: steps.deck.outputs.move == 'true'
working-directory: rust
run: |
sed -i -E 's/(rev = ")${{ steps.deck.outputs.pinned }}(")/\1${{ steps.deck.outputs.head }}\2/' Cargo.toml
cargo update -p ournotes-deck
- uses: peter-evans/create-pull-request@v7
if: steps.deck.outputs.move == 'true'
with:
branch: deck/update
commit-message: "build(deck): ournotes-deck ${{ steps.deck.outputs.head }}"
title: "build(deck): ournotes-deck ${{ steps.deck.outputs.head }}"
body: |
Moves the deck model to ournotes-deck's main,
https://github.com/empty-sekai/ournotes-deck/compare/${{ steps.deck.outputs.pinned }}...${{ steps.deck.outputs.head }}

Music data files made after this change name the new commit in `provenance.deck.commit`; their deck
statistics may differ.
88 changes: 71 additions & 17 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,80 @@ permissions:
contents: read

jobs:
build:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.13"
- name: Check the tag against the package version
- uses: actions/checkout@v7
- name: Check the tag against the package and extension versions
run: |
version=$(python -c 'import re, pathlib; print(re.search(r"__version__ = \"([^\"]+)\"", pathlib.Path("src/nnnotes/__init__.py").read_text()).group(1))')
if [ "v$version" != "$GITHUB_REF_NAME" ]; then
echo "tag $GITHUB_REF_NAME does not match the package version $version" >&2
version=$(python3 -c 'import re, pathlib; print(re.search(r"__version__ = \"([^\"]+)\"", pathlib.Path("src/nnnotes/__init__.py").read_text()).group(1))')
cargo=$(python3 -c 'import re, pathlib; print(re.search(r"^version = \"([^\"]+)\"", pathlib.Path("rust/Cargo.toml").read_text(), re.M).group(1))')
if [ "v$version" != "$GITHUB_REF_NAME" ] || [ "$cargo" != "$version" ]; then
echo "tag $GITHUB_REF_NAME, package $version and rust/Cargo.toml $cargo differ" >&2
exit 1
fi
- name: Build sdist and wheel

# one abi3 wheel (CPython 3.11 and later) per platform with the extension module nnnotes._deck, and the sdist
wheels:
needs: check
runs-on: ${{ matrix.os }}
strategy:
fail-fast: true
matrix:
include:
- {os: ubuntu-latest, target: x86_64, manylinux: "2_28"}
- {os: ubuntu-24.04-arm, target: aarch64, manylinux: "2_28"}
- {os: windows-latest, target: x64}
- {os: macos-latest, target: aarch64}
- {os: macos-15-intel, target: x86_64}
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7
with:
python-version: "3.13"
- uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.target }}
manylinux: ${{ matrix.manylinux || 'auto' }}
args: --release --locked --out dist
- uses: actions/upload-artifact@v7
with:
name: wheel-${{ matrix.os }}-${{ matrix.target }}
path: dist/
if-no-files-found: error

sdist:
needs: check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: PyO3/maturin-action@v1
with:
command: sdist
args: --out dist
- uses: actions/upload-artifact@v7
with:
name: sdist
path: dist/
if-no-files-found: error

build:
needs: [wheels, sdist]
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v8
with:
path: dist/
merge-multiple: true
- uses: actions/setup-python@v7
with:
python-version: "3.13"
- name: Check the distributions
run: |
python -m pip install --upgrade pip build twine
python -m build
python -m pip install --upgrade pip twine
python -m twine check --strict dist/*
- uses: actions/upload-artifact@v4
ls dist
- uses: actions/upload-artifact@v7
with:
name: dist
path: dist/
Expand All @@ -42,7 +96,7 @@ jobs:
permissions:
id-token: write
steps:
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@v8
with:
name: dist
path: dist/
Expand All @@ -57,7 +111,7 @@ jobs:
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Release notes
Expand All @@ -68,12 +122,12 @@ jobs:
env:
OUTPUT: RELEASE_NOTES.md
GITHUB_REPO: ${{ github.repository }}
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@v8
with:
name: dist
path: dist/
- name: GitHub release
uses: softprops/action-gh-release@v2
uses: softprops/action-gh-release@v3
with:
body_path: RELEASE_NOTES.md
files: |
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/story-site.yml
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@ jobs:
# The runner's preinstalled SDKs take most of its disk; a story build keeps its bundles and work files.
run: sudo rm -rf /usr/local/lib/android /usr/share/dotnet /opt/ghc /usr/local/.ghcup /opt/hostedtoolcache/CodeQL && df -h "$GITHUB_WORKSPACE"

- uses: Swatinem/rust-cache@v2 # the install builds the extension module nnnotes._deck (rust/)
with:
workspaces: rust
- uses: astral-sh/setup-uv@v7
with:
enable-cache: true
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@ nnnotes.toml
.env
out/
cache/
rust/target/
src/nnnotes/_deck*.pyd
src/nnnotes/_deck*.so
10 changes: 5 additions & 5 deletions README.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ nnnotes config check # each setting's origin and whether it is valid
| `player` | APK | render-related global settings (color space, quality levels, renderers) as JSON |
| `live` | music ID + difficulty | a full chart directory: chart and runtime notes, 3D scene, note and effect assets, BGM and sounds, sound routing |
| `web` | `--pair music:difficulty` (repeatable) or `--all`; `--live2d model` (repeatable) or `--all-live2d`; `--story episode` (repeatable) or `--all-stories`; `--region region` (repeatable) or `--all-regions` | an ournotes-player static site: shared player + per-chart / per-model / per-episode manifests + content-addressed assets; the Live2D models of the stories are built first as models, listed in `models.json`, and the story manifests reference them; one site can serve several regions, with listing texts in five languages; a story's interface texts are grouped by language, with TextMeshPro font assets generated from open fonts (the game's fonts with `--fonts game`); compressible assets (JSON, shaders, moc3, ...) are stored gzip encoded by default (`--compress br` / `none`) |
| `deck-data` | master data files (`--master-files` directory or `--apk-master`) | one JSON file for deck-building tools: every chart's runtime notes, skill events and fever ranges, and the master data tables about cards, skills, bonuses, scores and events ([format](https://github.com/MetaSekaiLab/nnnotes/blob/main/docs/deck-data.md)) |
| `music-data` | master data files (`--master-files` directory or `--apk-master`), or decoded master data with its manifest (`--decoded-master`) | one JSON file with every song and chart: titles and credits in five languages, bands, vocal characters, category, tags, release time, score ranks, BGM length; per difficulty the level, note counts, BPM, chart times, skill events and fever ranges; and the chart statistics the deck model ournotes-deck (built into nnnotes) measures on its whole-live simulation (the no-skill score, the weight of every score-up skill kind at every position). `--full` adds the deck model's input: every chart's runtime notes and the master data tables about cards, skills, bonuses, scores and events ([format](https://github.com/MetaSekaiLab/nnnotes/blob/main/docs/music-data.md)) |

Export conventions:

Expand All @@ -74,13 +74,13 @@ Based on all data of the Taiwan server, version 1.0.1 (zh-Hant):
| catalog / bundle decryption / dependency closure | working |
| master data decoding | working |
| stories, `adv` | 946 / 946 episodes |
| stories, `story` (full directory) | 946 / 946 episodes have all their resources in the catalog and of supported kinds (the resource closure equals the game's own per-episode download list); 759 exported one by one, the other 187 (with frames, effects, post effects, stills and the like) not yet one by one |
| stories, `story` (full directory) | 946 / 946 episodes have all their resources in the catalog and of supported kinds (the resource closure equals the game's own per-episode download list); 946 / 946 exported and checked one by one: the files, models and audio the output refers to exist, every JSON parses and every path it refers to resolves; the 235 episodes with frames, effects, post effects, stills, chats, talk windows or videos are byte-identical across two exports except for the models directory path |
| Live2D models | 239 / 239 (every model of the catalog; episodes use 185 of them) |
| CRI audio | 681 / 681 cue sheets |
| charts, `live` | 336 / 336 (music, difficulty) pairs |
| web site, `web` | 336 / 336 charts, 239 / 239 Live2D models, 946 / 946 story episodes (two full builds in English without audio are byte-identical and 946 / 946 pass the data validation; a 27-episode sample in five languages with AAC audio is likewise identical across two builds and valid) |
| spots, `spot` / `room` | one spot verified, the others not individually checked |
| other regions (en / kr) and languages | checked: the regions serve the same catalog for a language and the same bundles, and the keys are shared; the master tables the charts use are the same in the three regions, and the text tables have all five languages; the chart exports checked match the Taiwan server's. A full multi-region site build is not verified yet |
| web site, `web` | 336 / 336 charts, 239 / 239 Live2D models, 946 / 946 story episodes (two full builds in English without audio are byte-identical and 946 / 946 pass the data validation); two full builds of the three regions in five languages with AAC audio (`--all-regions`, with the live master data: 340 charts of 85 musics) are likewise byte-identical and pass the data validation in full |
| spots, `spot` / `room` | 39 / 39 spots, every reference resolves; `room` exports 20 / 20 room backgrounds of the catalog. Every glb passes the Khronos glTF-Validator with 0 errors |
| other regions (en / kr) and languages | checked: the regions serve the same catalog for a language and the same bundles, and the keys are shared; the master tables the charts use are the same in the three regions, and the text tables have all five languages; the chart exports checked match the Taiwan server's. A full multi-region site build is verified (the row above) |

## Requirements

Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ nnnotes config check # 每项设置的来源和格式是否有效,
| `player` | APK | 渲染相关的全局设置(色彩空间、画质等级、渲染器)JSON |
| `live` | 曲目 ID + 难度 | 完整谱面目录:谱面与运行时音符、3D 场景、音符与特效资源、BGM 与音效、声音路由 |
| `web` | `--pair 曲目:难度`(可重复)或 `--all`;`--live2d 模型`(可重复)或 `--all-live2d`;`--story 剧情 ID`(可重复)或 `--all-stories`;`--region 区服`(可重复)或 `--all-regions` | ournotes-player 静态站点:共享播放器 + 每谱 / 每模型 / 每集剧情清单 + 内容寻址资源;剧情用到的 Live2D 模型先按模型构建并列入 `models.json`,剧情清单引用它们;一个站点可服务多个区服,列表文本含五种语言;剧情的界面文字按语言分组,字形由开源字体生成 TextMeshPro 字体资源(`--fonts game` 时用游戏字体);可压缩的资源(JSON、着色器、moc3 等)默认以 gzip 存储,`--compress br` / `none` 可改 |
| `deck-data` | masterdata 文件(`--master-files` 目录或 `--apk-master`) | 供组卡工具读取的单个 JSON:每张谱面的运行时音符、技能事件与 fever 区间,以及卡牌、技能、加成、分数与活动相关的 masterdata 表([格式](docs/deck-data.md)) |
| `music-data` | masterdata 文件(`--master-files` 目录或 `--apk-master`),或带清单的解码 masterdata(`--decoded-master`) | 全部歌曲与谱面的单个 JSON:五语标题与作词作曲编曲、乐队、演唱角色、分类、标签、上线时间、评级线、BGM 时长;每个难度的等级、音符数、BPM、谱面时间、技能事件与 fever 区间;以及组卡模型 ournotes-deck(内置于 nnnotes)在整场模拟上实测的谱面统计(无技能得分、每种加分技能在每个演出位的权重)。`--full` 另附组卡模型的输入:每张谱面的运行时音符与卡牌、技能、加成、分数、活动相关的 masterdata 表([格式](docs/music-data.md)) |

导出约定:

Expand All @@ -59,13 +59,13 @@ nnnotes config check # 每项设置的来源和格式是否有效,
| catalog / 资源包解密 / 依赖闭包 | 可用 |
| masterdata 解码 | 可用 |
| 剧情 `adv` | 946 / 946 集可导出 |
| 剧情 `story`(完整目录) | 946 / 946 集的资源都在 catalog 中且类型均受支持(资源闭包与游戏自带的每集下载清单一致);已逐集导出验证 759 集,其余 187 集(用到 Frame / Effect / PostEffect / Still 等资源)未逐一验证 |
| 剧情 `story`(完整目录) | 946 / 946 集的资源都在 catalog 中且类型均受支持(资源闭包与游戏自带的每集下载清单一致);946 / 946 集已逐集导出验证:输出中引用的文件、模型与音频均存在,JSON 均可解析,引用的路径均可解析;其中 235 集用到 Frame / Effect / PostEffect / Still / Chat / TalkWindow / 视频资源,两次导出除模型目录路径外逐字节一致 |
| Live2D 模型 | 239 / 239 个可导出(catalog 中的全部模型,剧情用到其中 185 个) |
| CRI 音频 | 681 / 681 个 cue sheet 可解码 |
| 谱面 `live` | 336 / 336 个(曲目, 难度)组合可导出 |
| 网页站点 `web` | 336 / 336 张谱面,239 / 239 个 Live2D 模型,946 / 946 集剧情(英语、无音频的全量构建两次结果逐字节一致,946 / 946 集通过数据校验;五种语言含 AAC 音频的 27 集抽样同样一致,并通过数据校验) |
| 据点 `spot` / `room` | 已验证单个据点,其余未逐一验证 |
| 其他区服(en / kr)与其他语言 | 已核对:各区服同一语言的 catalog 相同、资源包相同,密钥通用;谱面用到的 masterdata 表在三个区服间一致,文本表五种语言齐全;抽查的谱面导出与台服一致。多区服整站构建未做全量验证 |
| 网页站点 `web` | 336 / 336 张谱面,239 / 239 个 Live2D 模型,946 / 946 集剧情(英语、无音频的全量构建两次结果逐字节一致,946 / 946 集通过数据校验);三个区服、五种语言、含 AAC 音频的全量构建(`--all-regions`,用线上 masterdata:85 首曲目的 340 张谱面)两次结果同样逐字节一致,全部通过数据校验 |
| 据点 `spot` / `room` | 39 / 39 个据点可导出,引用均可解析;catalog 中 20 / 20 个房间背景可由 `room` 导出。生成的 glb 均通过 Khronos glTF-Validator,0 个错误 |
| 其他区服(en / kr)与其他语言 | 已核对:各区服同一语言的 catalog 相同、资源包相同,密钥通用;谱面用到的 masterdata 表在三个区服间一致,文本表五种语言齐全;抽查的谱面导出与台服一致。多区服整站已全量构建验证(见上一行) |

## 需要准备

Expand Down
Loading
Loading