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 README.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ and the npm package contain no game assets: the player reads data prepared in th
[nnnotes](https://github.com/MetaSekaiLab/nnnotes) toolkit produces that data from the user's own game files.
BanG Dream! and related names and trademarks belong to their respective owners.

An independent experimental [UI prefab preview](docs/ui-preview.md) is available through `ournotes-player/ui`
and `<ournotes-ui>`. It inspects `nnnotes ui` exports with Canvas 2D and a motion subset, without pixel-fidelity or
full game-runtime claims. The [example viewer](examples/ui) loads a library supplied by the user.

## Components

| | Live charts | Stories | Live2D models |
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ ournotes-player 在网页中重现 BanG Dream! Our Notes 的三类画面:Live

本项目为非官方爱好者项目,与游戏的开发、发行和运营方无关。仓库与 npm 包不包含任何游戏资源:播放器读取按数据格式([Live 与 Live2D](docs/data-format.md)、[剧情](docs/story-data-format.md))准备的数据,数据由使用者自行提供;[nnnotes](https://github.com/MetaSekaiLab/nnnotes) 工具包可以从使用者自己的游戏文件生成这种数据。BanG Dream! 及相关名称与商标归各自权利人所有。

另有独立、实验性的 [UI 预制体预览](docs/ui-preview.md):`ournotes-player/ui` 与 `<ournotes-ui>` 读取 `nnnotes ui` 导出的数据,以 Canvas 2D 检查布局和部分动画,不宣称像素一致或完整游戏业务逻辑。可使用 [examples/ui](examples/ui) 打开自己的库。

## 组件

| | Live 谱面 | 剧情 | Live2D 模型 |
Expand Down
4 changes: 3 additions & 1 deletion docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ live UI, with the chart's music and sound effects, auto-played at Perfect. The d
|---|---|
| `ournotes-player` | `ChartPlayer`, `ChartSession`, `AssetStore`, `defineOurnotesPlayer`, `OurnotesPlayerElement`, `LIVE_SPEEDS`, `LIVE_OPTIONS`, `LIVE_OPTION_GROUPS`, `LiveSettingsError`, `PLAYER_LANGUAGES`, `formatTime` |
| `ournotes-player/element` | the same exports; importing it defines `<ournotes-player>` |
| `ournotes-player/ui` (experimental) | `UILibrary`, `UISession`, `UIPlayer`, `OurnotesUIElement`, `defineOurnotesUI`; [prefab preview API and limits](ui-preview.md) |
| `ournotes-player/ui/element` (experimental) | the same UI exports; importing it defines `<ournotes-ui>` |

Every time in the API is in milliseconds of chart time.
Chart API times are in milliseconds of chart time. The independent UI preview API uses seconds.

## `<ournotes-player>`

Expand Down
11 changes: 11 additions & 0 deletions docs/fidelity.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
What the player reproduces from the game, under which settings, what it adds as a viewer, and where it relies on
the documented behaviour of the engine rather than on the game's own code.

The independent experimental [UI prefab preview](ui-preview.md) has a separate fidelity scope. Its Canvas 2D
graphics and motion are inspection approximations; the GPU-rendering claims below do not apply to it.

## Source of the behaviour

The player follows the game's own managed code; comments in `src/` name the game class and method each piece
Expand Down Expand Up @@ -562,4 +565,12 @@ Every `ENGINE:` note in `src/`, by file. `npm test` checks that this list matche
- Animator.writeDefaultValuesOnDisable is not applied: every animated node here is invisible while disabled and resampled on its first update after enable.
- UniTask's DOTween awaiter completes on the tween's kill callback, so a flash killed by the next Flash call (or Refresh) ends its await inside DOKill and hides the view before the new flash shows it.

**`src/ui/layout.js`**

- RectTransform sizes remain signed. A negative intermediate rect can

**`src/ui/text-layout.js`**

- TMP's vertical anchor and preferred height use the visible text

<!-- engine-notes:end -->
108 changes: 108 additions & 0 deletions docs/ui-preview.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# UI prefab preview (experimental)

The optional `ournotes-player/ui` module previews serialized UI assets exported by `nnnotes ui`. It uses Canvas 2D
and is an inspector, not the game's GPU renderer or an implementation of every page Presenter. It does not change
the chart, story or Live2D entry points. The repository, example and npm package include no game assets.

## Load a library

Export data outside this repository with [nnnotes ui](https://github.com/MetaSekaiLab/nnnotes), then serve that data
over HTTP. Cross-origin resources need CORS. The index and packs use the
`ournotes-ui-library` / `ournotes-ui-pack` format (schema 1). Legacy library indexes with
`dependency-controllers.json` are also accepted.

```js
import { UILibrary, UIPlayer } from 'ournotes-player/ui';

const library = await UILibrary.load('/ui-data/index.json');
const entry = library.entries.find(e => e.kind === 'prefab' && e.file);
const player = await UIPlayer.create(document.querySelector('#stage'), { library, entry });
await player.playState('Open', { animator: player.animators[0].index });
player.play();
```

`UIPlayer.create(host, {src: '/ui-data/packs/ID.json'})` loads a standalone pack. A standalone pack can use an
inline controller; separately exported controllers require a `UILibrary`. Importing `ournotes-player/ui` does not
register elements or require a DOM at import time. Constructing the player requires a browser Canvas 2D context.
The [example viewer](../examples/ui/index.html) takes a user-supplied library URL and provides selection and motion
controls; it has no bundled sample data.

## Custom element

```html
<script type="module">import 'ournotes-player/ui/element';</script>
<ournotes-ui src="/ui-data/index.json" entry="YOUR_EXPORTED_KEY"></ournotes-ui>
```

Without `entry`, a library's first exported prefab is selected. A standalone pack URL needs no `entry`.
`src` and `entry` are also properties. `show-hidden` and `bounds` are boolean inspection attributes.
`ready` resolves to the current UIPlayer; replacing an in-flight source or disconnecting rejects that pending
promise with AbortError. Reconnecting creates a fresh player. A transient DOM move keeps the current player.
`playState`, `selectClip`, `selectSequence` and `seek` wait for ready; `play` and `pause` act on the loaded player.

## API and state

All UI motion times are **seconds**, unlike chart/story API times. `UILibrary.load(src, {fetch, signal})` loads an
index, while packs and controllers load on demand. `find` accepts an entry ID, exact catalog key, unique name or
an entry record. Ambiguous names throw; same-named controllers resolve by their serialized IDs.

`UISession(pack, {bindings: true})` is DOM-independent. It clones the input and provides:

- `edit(node, component, field, value)` for a node index, stable nodeId or unique path/name. `component: null`
edits a node field; dotted field paths are supported and prototype-related paths are rejected.
- `setController(node, {document, resources})`, `playState(nameOrIndex)`, `selectClip(nameOrIndex)` and
`selectSequence(node)`.
- `setParameter(name, value)`, `seek(seconds)`, `update(deltaSeconds)`, `reset()` and `prepare()`.

`prepare` returns a new preview pack. It applies edits, optional component bindings and the selected motion;
the input is unchanged. `bindings: false` leaves serialized state intact except for explicit edits and motion.
`reset` clears edits and motion while retaining loaded controllers. Parameter history is replayed on seek.

UIPlayer wraps the same session with `load`, `controller`, `playState`, `selectClip`, `selectSequence`,
`setParameter`, `edit`, `applyFixture`, `seek`, `reset`, `render`, `play`, `pause` and `destroy`. Options include
`viewport: [1920,1080]`, `assetBase`, `showHidden`, `bounds` and `bindings`. The player exposes `canvas`, `nodes`,
`animators`, `sequences`, `time`, `duration`, `paused`, `report` and `session`. `applyFixture({patches})` applies
caller-supplied `{node, component, field, value}` patches; `_previewSource` image URLs resolve against its optional
baseURL. Page-specific sample data and business rules belong to the caller.

Events are `ready`, `render`, `play`, `pause`, `timechange` and `error`, with CustomEvent details. Reports retain
applied numeric/Sprite counts, unsupported/missing-binding diagnostics and recorded animation events.
Recorded events do not execute arbitrary native callbacks. Destroying a player stops scheduled playback and
invalidates pending loads/renders.

## Rendering and limits

Implemented preview paths include Sprite crops, nine-slice/fill/tiled images, tint/gradients, basic masks,
RectTransform geometry, linear/grid layout, content/aspect fitting, basic text, component state bindings,
numeric and Sprite-reference curves, single-layer direct-motion Animator states, parameters/triggers,
exit-time transitions, crossfade, and the supported DOTween sequence/target subset. Unsupported motion and
callback cases are reported rather than executed.

Signed intermediate rectangles are retained; their positive stretched children must not be widened by clamping
the parent to zero. Isolated prefab canvases include graphics outside the root, while a screen Canvas retains
the requested viewport. Fixed-size layout children remain fixed inside flexible cells. Trailing text line feeds
do not add to visible vertical alignment. VibeMO ASCII faces use separate exported glyph metrics; available FZ
TTFs provide the fallback text face. Other font faces fall back to the browser font.

TMP rich text, exact glyph layout/antialiasing, width-dependent preferred height and uGUI rebuild ordering,
CanvasGroup/mask details, custom GPU materials, particles, localization, dynamic lists, Presenter data,
Live2D/video/camera composition and complex Animator layers/BlendTrees are incomplete. Animator stepping and
crossfade are preview approximations; long Animator advances are limited to 1,000 seconds. Initial prefab text,
unbound images or active flags may be placeholders, not evidence that a component is obsolete. Use bindings,
edits or your own fixtures for its real use case.

The rendering review was based on international Android 1.0.1 (versionCode 25), not a full JP UI comparison.
The data reader accepts a separately exported JP library; differing schemas/fonts can remain unsupported.
Neither region is advertised as pixel-identical or runtime-complete.

## Evidence and verification

Game-specific state-binding rules were studied in the international binary's methods such as UIToggle.SwitchObjects
(`0x6ac4e74`), UIToggleButtonFrame (`0x647fcb8` / `0x64888e0`), DOTweenSequence.CreateSequence (`0x6aded88`) and
SimpleAnimationTrigger.PlayAnimation (`0x6b1f2f8`). Unity geometry/layout semantics are marked ENGINE in the helpers.
GetChildSizes' force-expand operation was checked in native instructions at `0xbd29fec..0xbd2a004`; its C dump
omitted that branch. These addresses are version-specific evidence, not runtime offsets or JP method identities.

Unit tests use only synthetic records/fonts and cover loading, cancellation, lifecycle, duplicate instances,
curves, state seek, sequence timing and layout invariants. Separate local visual review uses user-supplied data
outside the repository. A successful static render/curve audit is not pixel-fidelity verification.
23 changes: 23 additions & 0 deletions examples/ui/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<!doctype html>
<html lang="en">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Our Notes · UI preview</title>
<link rel="stylesheet" href="ui.css">
<body>
<header><div><span class="brand">OUR NOTES</span><h1>UI preview <span class="badge">Experimental</span></h1></div><p>Inspect a prefab library from your own APK.</p></header>
<form id="open"><label for="source">Library URL</label><input id="source" type="url" placeholder="http://localhost:8000/ui-data/index.json" required><button>Open library</button></form>
<main>
<aside><label for="search">Find a prefab</label><input id="search" type="search" placeholder="Name or key"><select id="assets" size="15" aria-label="Prefabs"></select><p id="count">No library loaded</p></aside>
<section class="viewer">
<div class="title"><div><h2 id="name">Choose a library</h2><p id="key">Export with nnnotes ui and serve the output over HTTP.</p></div><label><input id="hidden" type="checkbox">Hidden layers</label><label><input id="bounds" type="checkbox">Bounds</label></div>
<div class="motions"><select id="animator" aria-label="Animator" disabled><option value="">Animator</option></select><select id="state" aria-label="Animator state" disabled><option value="">State</option></select><select id="clip" aria-label="Animation clip" disabled><option value="">Clip</option></select><select id="sequence" aria-label="DOTween sequence" disabled><option value="">Sequence</option></select><button id="play" disabled>Play</button><button id="reset" disabled>Reset</button></div>
<div id="parameters"></div>
<div class="stage"><canvas id="canvas" aria-label="Prefab preview"></canvas><p id="empty">Your exported prefab will appear here.</p></div>
<div class="timeline"><input id="time" type="range" min="0" max="1" step="0.001" value="0" aria-label="Motion time" disabled><output id="seconds">0.000 s</output></div>
<p id="status" role="status">Serialized initial states may include placeholder text and unbound images.</p>
<details><summary>Binding and motion diagnostics</summary><pre id="diagnostics">No motion selected.</pre></details>
</section>
</main>
<script type="module" src="ui.js"></script>
</body></html>
1 change: 1 addition & 0 deletions examples/ui/ui.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading