# Third-Party Notices

> **参照来源许可声明**：本文档引用的 `wer-ref/` 是**第三方参考实现**
> （`Aromatic05/wallpaper-engine-renderer`，为 `catsout/wallpaper-scene-renderer` 的 fork，
> **GPL-2.0-only**），**不是 Wallpaper Engine 官方代码，也不是"真值源"**。
> 与本项目渲染器（GPL-3.0-or-later）**许可不兼容**：仅用于**行为对照**，
> **不得复制、改写、逐行翻译其代码、注释、常量组织或错误文案**。
> `we-layerd-ref/`（`Aromatic05/we-layerd`）**无任何许可**（保留所有权利），同样只可读行为结论。
> 血缘自查结论见 `docs/WER-REF-LICENSE-AUDIT.md`。

This repository contains code derived from, or vendored from, the following third-party
projects. Their licenses are reproduced in full below, as required.

---

## 1. elysia395/dsh-wallpaper-engine  (MIT)

Portions of `elysia/**` (CPU scene renderer, MDL/puppet parsing, skinned-mesh rasterisation,
effect implementations, GLSL interpreter, scene-script runtime, CFF text rasteriser) and the
whole of `core/attach-transform.mjs` are ported from, or are derivative works of, the
`lib/we-renderer/**` and `lib/*.js` sources of this project.

  Upstream:  https://github.com/elysia395/dsh-wallpaper-engine
  Author:    YV3507 — wrote the scene renderer (all commits under lib/we-renderer/)
             elysia395 — repository author and maintainer; holder of the license below
  License:   MIT
  Copyright: Copyright (c) 2026 elysia395

The renderer was contributed upstream as pull request #47
(https://github.com/elysia395/dsh-wallpaper-engine/pull/47), merged 2026-08-25.

Files in this repository that contain ported material:
  - elysia/we-renderer/**            (ported; 30 of 43 files are byte-identical upstream)
  - elysia/font-render.js            (ported)
  - elysia/scene-scripts.js          (ported and extended)
  - elysia/scene-script-apis.js      (ported and extended)
  - core/attach-transform.mjs             (verbatim port of four upstream functions)

### MIT License

Copyright (c) 2026 elysia395

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

---

## 2. @shaderfrog/glsl-parser 7.0.1  (ISC)

Vendored, unmodified, at `elysia/vendor/@shaderfrog/glsl-parser/`.
Used by `elysia/we-renderer/glsl/{executor,preprocess}.js`.

  Upstream:  https://github.com/ShaderFrog/glsl-parser
  Author:    Andrew Ray
  License:   ISC (declared in the package's package.json; the upstream project
             publishes no LICENSE file and no README licensing section, so the
             notice below is reconstructed from the package metadata and the
             SPDX ISC specification, https://spdx.org/licenses/ISC.json)
  Copyright: Copyright (c) 2022 Andrew Ray   [year = npm first-publish date; see note]

### ISC License

Copyright (c) 2022 Andrew Ray

Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.

---

## 3. Peggy  (MIT) — generated-code courtesy notice, optional

`elysia/vendor/@shaderfrog/glsl-parser/parser/parser.js` is machine-generated output
("Generated by Peggy 1.2.0", https://peggyjs.org). Peggy itself is MIT licensed and is not
redistributed here; this line is a courtesy acknowledgement only.

---

## 4. Fonts bundled with this project  (P-86, 2026-09-15)

The renderer resolves a text layer's font through a **four-tier chain**
(`demo.html` → `ensureTextFont`):

1. the font inside the wallpaper's own `.pkg` (`lib.getEntry`),
2. **the fonts in this repository** — `we-scene-demo/assets/fonts/**`, served at `/assets/fonts/<file>`,
3. the user's own Wallpaper Engine installation (`/weassist/fonts/<name>`, read at runtime, never redistributed),
4. `sans-serif`.

This section covers tier 2 only. Every file here is taken **from the author's / upstream's own
release**, with the download URL, fetch date and sha256 recorded below so anyone can re-verify it.

> ### ⛔ Provenance rule (do not break this)
> **No file in `assets/fonts/` was copied out of a Wallpaper Engine installation**
> (`<WE>/assets/fonts/`). Wallpaper Engine's own font directory is *read at runtime* (tier 3) and
> is never a source of bundled files: WE is not the licensor of these typefaces, its Steam
> distribution grants no redistribution right, and mixing "upstream-licensed copies" with
> "copies of Steam-distributed files" would make the licensing story of this directory
> unexplainable. The full evidence (15 fonts, per-font licences, Steam Subscriber Agreement
> §2.G, comparison with six similar projects) is in the research record
> `docs/FONT-REDISTRIBUTION-RESEARCH.md` — that file is the *research log*; this section is
> the *shipping notice*, and the per-file data below is what was actually taken and verified.
>
> The one place where a byte-comparison with a WE copy appears below is marked
> "informational only — not the source of the file"; it is there to show *whether* our upstream
> copy happens to equal the build WE ships (sometimes it does, sometimes it deliberately does not).

**SPDX for this directory (as a whole).** The seven bundled files are under three different
licences, so the bundled font set is expressed as a conjunction, plus one locally-defined
`LicenseRef-` for the font whose terms are the author's own freeware terms:

```
OFL-1.1 AND CC-BY-4.0 AND LicenseRef-BVFonts-Freeware-2006
```

- `OFL-1.1` — `Blackout 2 AM.ttf`, `monof55.ttf`, `NotoSans-Regular.ttf`,
  `RobotoMono-Regular.ttf`, `Segment7Standard.otf` (five files).
- `CC-BY-4.0` — `Twemoji.Mozilla.ttf` **artwork** (the emoji drawings); the build **code** in that
  same upstream repository is `Apache-2.0`, but no code is redistributed by shipping the font. Both
  texts travel in `licenses/` anyway, and `MIT` (the npm packaging terms of that upstream) is
  **not** asserted here — it covers code, not the font file. Per-file expressions are in §4.1.
- `LicenseRef-BVFonts-Freeware-2006` — `spincycle_3d_ot.otf`. This is a **locally-defined**
  `LicenseRef-` (SPDX allows `LicenseRef-` for terms not on the SPDX list); it is *not* a registered
  SPDX identifier, and the identifier string itself is **pending confirmation** (see §4.6.1, where
  every condition is mapped to how this repository satisfies it).

**Provenance declaration（照律师建议的原话口径 / verbatim-ish, per counsel）.**

> 本仓库随仓分发的 7 个字体文件，**全部取自上游作者/项目的官方发布渠道，非从 Wallpaper Engine 安装目录
> （`<WE>/assets/fonts/`）复制**；每个文件的来源 URL、取件日期、sha256、许可、版权方与"是否修改"逐文件见下表。

> All seven bundled font files were **taken from the upstream author's / project's own official release
> channel, and are not copies made from a Wallpaper Engine installation directory**. Per file: source URL,
> fetch date, sha256, licence, copyright holder and "modified by us?" are in the table below.

**Enforcement**: `tests/publish-check.mjs` check ③ compares our files against a WE asset root whenever one is
given; every hit (including the two files that *do* equal the WE build) is printed with an evidence
reference to this section, and `assets/fonts/README.md` states the same rule next to the files.

### 4.1 At a glance

| File in `assets/fonts/` | SPDX | Licence | Copyright line | Upstream URL used | Fetched (UTC) | Bytes | SHA-256 | Modified by us? | Direct from the author's/upstream's own release channel (never copied from a WE install)? |
|---|---|---|---|---|---|---|---|---|---|
| `Blackout 2 AM.ttf` | `OFL-1.1` | **OFL-1.1** (RFN `Blackout`) | `Copyright (c) 2012, Tyler Finck <hello@sursly.com>, with Reserved Font Name: "Blackout".` (upstream `Open Font License.markdown`; the copy's own `name` table still carries stale 2014 "All rights reserved." text — see §4.8) | `https://github.com/theleagueof/blackout` → `raw.githubusercontent.com/theleagueof/blackout/master/Blackout%202%20AM.ttf` | 2026-09-15 | 28,308 | `48e96e2a3e9be781e1884b670a69434612c319fe21b6595813f1193f69cea2d0` | **否**（逐字节 = 上游原件） | **是** — 作者/上游官方发布渠道 |
| `monof55.ttf` | `OFL-1.1` | **OFL-1.1** (`debian/copyright: License: SIL-OFL-1.1`, plus the author's own 2018 e-mail granting OFL-1.1) | `Copyright: 2000 tobias b köhler (unci@tigerden.com)` | Debian `fonts-monofur` 1.0 **upstream tarball** `https://deb.debian.org/debian/pool/main/f/fonts-monofur/fonts-monofur_1.0.orig.tar.xz` (in-package path `monofur/monof55.ttf`); reviewed metadata `https://sources.debian.org/src/fonts-monofur/1.0-1/` | 2026-09-15 | 169,452 | `025676779b4ea99781930b6916ce3c575f9bfda77e1d726e8d70032c007b2b44` | **否**（逐字节 = 上游原件） | **是** — 作者/上游官方发布渠道 |
| `NotoSans-Regular.ttf` | `OFL-1.1` | **OFL-1.1** | `Copyright 2015-2021 Google LLC. All Rights Reserved.` (in-file `name` ID0) | `https://github.com/notofonts/noto-fonts` → `raw.githubusercontent.com/notofonts/noto-fonts/main/hinted/ttf/NotoSans/NotoSans-Regular.ttf` | 2026-09-15 | 569,208 | `b85c38ecea8a7cfb39c24e395a4007474fa5a4fc864f6ee33309eb4948d232d5` | **否**（逐字节 = 上游原件） | **是** — 作者/上游官方发布渠道 |
| `RobotoMono-Regular.ttf` | `OFL-1.1` | **OFL-1.1** (upstream's *current* licence — **not** Apache-2.0; the 2015 build inside WE is the Apache-2.0 one) | `Copyright 2015 The Roboto Mono Project Authors (https://github.com/googlefonts/robotomono)` | `https://github.com/googlefonts/RobotoMono` → `raw.githubusercontent.com/googlefonts/RobotoMono/main/fonts/ttf/RobotoMono-Regular.ttf` (licence `…/main/OFL.txt`) | 2026-09-15 | 125,748 | `af0bff7599c3df3831755c16e39b3c496df74b8c8d8a1161b14dc8461be17cb4` | **否**（逐字节 = 上游原件） | **是** — 作者/上游官方发布渠道 |
| `Segment7Standard.otf` | `OFL-1.1` | **OFL-1.1** (RFN `Segment7`) | `(c) Cedric Knight 2014. Licensed under SIL Open Font Licence v1.1. Reserved name: Segment7.` (in-file `name` ID0) | `https://fontlibrary.org/en/font/segment7` → download zip `https://fontlibrary.org/assets/downloads/segment7/4cc82137fc130708919bf201c0dc9aae/segment7.zip` (contains `Segment7Standard.otf` + `OFL.txt`) | 2026-09-15 | 10,464 | `f35b8ce74c9aedbd51e790b178c5dfbfe62068772db6e924a455247781cc7356` | **否**（逐字节 = 上游原件） | **是** — 作者/上游官方发布渠道 |
| `Twemoji.Mozilla.ttf` | `CC-BY-4.0`（美术；见下） | **artwork CC-BY-4.0 / code Apache-2.0** | `Copyright 2016-2018, Mozilla Foundation` (code); emoji art © Twitter, Inc. and other contributors (Twemoji) | `https://github.com/mozilla/twemoji-colr` → release asset `https://github.com/mozilla/twemoji-colr/releases/download/v0.7.0/Twemoji.Mozilla.ttf` (v0.7.0) | 2026-09-15 | 1,474,284 | `6d90152ee0d29e82fe2a87793af5aa4b7ad13e6538360889e141e81ed299ee8e` | **否**（逐字节 = 上游原件） | **是** — 作者/上游官方发布渠道 |
| `spincycle_3d_ot.otf` | `LicenseRef-BVFonts-Freeware-2006`（本地定义，见下） | **author freeware terms** (use + redistribution allowed, commercial use allowed; see §4.6 for the condition-by-condition mapping) | `Jess Latham (c) 2006 All Rights Reserved — bvfonts.com` (in-package `README.TXT`) | **Author's own site**: `https://www.bvfonts.com/fonts/details.php?id=44` → `https://www.bvfonts.com/fonts/files/spin_cycle_threed.zip` (in-zip path `Open Type/spincycle_3d_ot.otf`) | 2026-09-15 | 44,228 | `cc4a580ac0d112ef0eb5199fe08d497a5875fd24c2361038dc6c847a3962da4d` | **否**（逐字节 = 上游原件） | **是** — 作者/上游官方发布渠道 |
| `PixelOperator8.ttf` | `CC0-1.0` | **CC0-1.0** (public-domain dedication; attribution not legally required — we credit the author anyway) | `This typeface is made by Jayvee Enaguas (HarvettFox96), licensed under a Creative Commons Zero (CC0) 1.0. © 2009-2018.` (dafont author note; in-file `name` ID13 = `Creative Commons Zero (CC0) 1.0`) | **Author's own DaFont release**: `https://www.dafont.com/pixel-operator.font` → `https://dl.dafont.com/dl/?f=pixel_operator` (in-zip `PixelOperator8.ttf` + `LICENSE.txt` = CC0 1.0 full text) | 2026-09-18 | 19,944 | `5cccb9ef6cf18977b6e5721d49a1a6e78dd6a6f1c4f69537470f7dc1dc829ffc` | **否**（逐字节 = 上游原件；作者当前版本，非 WE 副本） | **是** — 作者/上游官方发布渠道 |

**Reading the last two columns.**

- **Modified by us? = No, for all seven.** Every file is redistributed **byte-for-byte** as fetched:
  nothing renamed, converted, subset, recompiled or edited. The SHA-256 column is the proof (re-run
  `sha256sum` on the file and compare); for the OFL fonts this is also what keeps the
  reserved-font-name clause untriggered (we create no derivative).
- **Provenance = the author's / upstream's own release channel, never a Wallpaper Engine install.**
  Concretely: GitHub raw or GitHub release asset (Blackout, Noto Sans, Roboto Mono, Twemoji Mozilla),
  Debian's **upstream tarball** (monofur), FontLibrary's own download zip (Segment7), and the
  **author's own site** bvfonts.com (Spin Cycle 3D). §4.4 gives the byte-comparison against the WE
  copy as **information only** — that copy is never the source of a file. Two of the seven *do* equal
  the WE build (`Blackout 2 AM.ttf`, `monof55.ttf`), which is exactly why this rule is stated
  explicitly: **equality is not provenance** — the SHA-256 and the fetch record are.
- **SPDX column** is per file; the whole-directory expression is
  `OFL-1.1 AND CC-BY-4.0 AND LicenseRef-BVFonts-Freeware-2006` (note above §4.1). For
  `Twemoji.Mozilla.ttf` the file shipped is the **artwork** (the emoji drawings) ⇒ `CC-BY-4.0`; the
  `Apache-2.0` code licence of that upstream repository is **not** asserted for a font binary (no
  code is redistributed here), although both texts still travel in `licenses/`.

Licence texts and notices travel with the fonts in `assets/fonts/licenses/`:

| File | What it is |
|---|---|
| `OFL-Blackout.markdown` | upstream OFL-1.1 for Blackout (with the RFN line) |
| `OFL-Monofur-debian-copyright.txt` | Debian `debian/copyright` for `fonts-monofur`: the OFL-1.1 statement **plus the full OFL text** |
| `monofur-author-OFL-email.txt` | the author's own e-mail (2018-02-11) granting OFL-1.1 for monofur |
| `monofur-monof_tt-notice.txt` | the notice file that ships *inside* the upstream monofur package; its freeware text asks that it be distributed together with the fonts (it is — this file) |
| `OFL-NotoSans.txt` | upstream licence file of noto-fonts |
| `OFL-RobotoMono.txt` | upstream `OFL.txt` of RobotoMono (current licence) |
| `OFL-Segment7.txt` | `OFL.txt` taken out of the FontLibrary Segment7 zip |
| `Twemoji-Mozilla-LICENSE.md` | upstream `LICENSE.md` (code Apache-2.0 + artwork CC-BY-4.0) |
| `Apache-2.0.txt` | full Apache License 2.0 text (for the twemoji-colr build code) |
| `CC-BY-4.0-Twemoji-attribution.txt` | the CC-BY-4.0 attribution statement: credit, licence links, "no modifications" |
| `spincycle-bvfonts-README.txt` | the author's `README.TXT` taken out of his own zip |
| `spincycle-bvfonts-TOU.txt` | dated snapshot of <https://www.bvfonts.com/tou.php> (with page sha256) + the compliance mapping |

### 4.2 Licence compliance, per font

* **OFL-1.1 fonts** (`Blackout 2 AM.ttf`, `monof55.ttf`, `NotoSans-Regular.ttf`,
  `RobotoMono-Regular.ttf`, `Segment7Standard.otf`): the full OFL text is shipped for each of them;
  the fonts are redistributed **unmodified** (byte-for-byte upstream files — see the sha256 column),
  so the reserved-font-name clause is not triggered (we create no derivative and we do not rename
  the font itself); none of them is sold by itself; this project is free software under
  **GPL-3.0-or-later** (see §5) and does not sell the fonts or a font compilation.
* **Twemoji Mozilla**: artwork under CC-BY-4.0 → attributed in
  `licenses/CC-BY-4.0-Twemoji-attribution.txt` (credit + licence link + explicit "no modifications"
  statement, file unmodified); the build code under Apache-2.0 → full licence text shipped.
* **Spin Cycle 3D**: author's own freeware terms → §4.6 below maps every condition to how this
  repository satisfies it, with the evidence.
* **SPDX, per font (machine-readable).** From §4.1's SPDX column:
  `Blackout 2 AM.ttf` = `OFL-1.1` · `monof55.ttf` = `OFL-1.1` · `NotoSans-Regular.ttf` = `OFL-1.1` ·
  `RobotoMono-Regular.ttf` = `OFL-1.1` · `Segment7Standard.otf` = `OFL-1.1` ·
  `Twemoji.Mozilla.ttf` = `CC-BY-4.0` (artwork; its upstream `Apache-2.0` build code is not
  redistributed by shipping the font) · `spincycle_3d_ot.otf` = `LicenseRef-BVFonts-Freeware-2006`
  (locally defined). Bundle expression: `OFL-1.1 AND CC-BY-4.0 AND LicenseRef-BVFonts-Freeware-2006`.
  **None of these replaces the full licence texts** shipped in `assets/fonts/licenses/`.
* **monofur**: ships under OFL-1.1 (author's e-mail + Debian's reviewed `debian/copyright`), and
  additionally carries the upstream freeware notice file, because that notice asks to be
  distributed together with the font. The author also asks to be notified when the font is
  redistributed on a server ("*I would enjoy it if you notify me at unci@unci.de when you use them
  commercially or redistribute them on a server*") — a request, **not** a condition of the licence.

### 4.3 WE reference name → file in this repository

Wallpaper `scene.json` files reference WE's own font **file names**. We keep the upstream file
names (so the sha256 column above can be re-checked against the upstream URL), and map the three
that differ in `demo.html` (`REPO_FONT_ALIASES`):

| Reference in a wallpaper (`fonts/…`) | File here | Why the name differs |
|---|---|---|
| `Monofur-PK7og.ttf` | `monof55.ttf` | upstream/Debian release name is `monof55.ttf` |
| `TwemojiMozilla.ttf` | `Twemoji.Mozilla.ttf` | Mozilla's release name has the dot |
| `8bitOperatorPlus8-Regular.ttf` | `PixelOperator8.ttf` | author renamed the family to **Pixel Operator** in 2018 and re-licensed it **CC0-1.0**; the old-name build is marked *undownloadable* on DaFont — see §4.7 |
| `Blackout 2 AM.ttf`, `NotoSans-Regular.ttf`, `RobotoMono-Regular.ttf`, `Segment7Standard.otf`, `spincycle_3d_ot.otf` | same name | — |

The CSS family the renderer registers is `mpw-<hash of the reference path>`, so the on-disk file
name never reaches the font stack; the family name inside the font (its sfnt `name` table) is
what the user sees in the rendering.

### 4.4 Byte-comparison notes (informational only — not a source of any file)

| File here | sha256 of the copy inside `<WE>/assets/fonts/` | Relation |
|---|---|---|
| `Blackout 2 AM.ttf` | `48e96e2a…cea2d0` (identical) | Same build; WE's embedded "All rights reserved." is stale 2014 metadata, the upstream licence is OFL-1.1. We still take the file from upstream. |
| `monof55.ttf` | `02567677…7b2b44` (identical) | Same build (WE ships the 2000 release under the name `Monofur-PK7og.ttf`). We take it from the Debian upstream tarball. |
| `spincycle_3d_ot.otf` | `41a1e603…cd48c8` (**different**, 44,640 B) | WE ships an older/larger build. Ours is the author's **current official** `Open Type` build (44,228 B), which is exactly why the file had to come from bvfonts.com. |
| `NotoSans-Regular.ttf`, `RobotoMono-Regular.ttf`, `Twemoji.Mozilla.ttf`, `Segment7Standard.otf` | different builds / versions | We took the current upstream releases; sizes and versions differ from the WE-era copies (e.g. upstream Roboto Mono is v3.001 under OFL-1.1, WE's is the 2015 Apache-2.0 build). |

### 4.5 Fonts that are **not** bundled (runtime-only, tier 3)

`Alcubierre.otf`, `Atami-Regular.otf`, `CursedTimerUlil-Aznm.ttf`, `Lazer84.ttf`, `kust.ttf`,
`opensticks.ttf`, `summer85.ttf` — no redistribution grant could be located for any of them after
tracing each back to its author (see `docs/FONT-REDISTRIBUTION-RESEARCH.md` §2.3). They are read
at runtime from the user's own Wallpaper Engine installation; without WE the text layer falls back
to `sans-serif` without error.

`8bitOperatorPlus8-Regular.ttf` is a **special case**: its embedded `name` table says OFL-1.1
(with Reserved Font Name `8-bit Operator+`, © Grand Chaos Productions), i.e. it *would* be
redistributable — but the author's current publishing page could not be reached from this machine,
and the rule above forbids substituting the WE copy. It is therefore **not bundled** and is
recorded as an open item in §4.7.

#### 4.5.1 Re-verified 2026-09-19 (P-127): licence evidence per font + measured corpus impact

> **What is new in this subsection** (re-derived from first-hand files on this machine, not copied
> from the research log): the licence-file ↔ font pairing inside `<WE>/assets/fonts/`, the
> **`name`-table quotes** re-read from each binary, and the **corpus impact** — how many text layers
> and how many containers reference each font across all 98 `.pkg`/`.mpkg` containers. Everything
> here is re-computable with `node tests/font-gap-audit-test.mjs` (frozen expectations live in that
> file's `CENSUS` / `NOT_BUNDLED` / `BUNDLED` tables).

**Which file in `<WE>/assets/fonts/` belongs to which font** (that directory ships 15 fonts +
4 non-font files; the other 11 fonts have **no** accompanying licence file at all):

| Non-font file in `<WE>/assets/fonts/` | Belongs to | The sentence that decides it |
|---|---|---|
| `SIL Open Font License.txt` | `8bitOperatorPlus8-Regular.ttf` | First line: `Copyright (c) 2009 - 2014 Grand Chaos Productions (http://grandchaos9000.deviantart.com), with Reserved Font Name 8-bit Operator+.` — the RFN names exactly this one font; the rest is the OFL-1.1 text |
| `RobotoMono-Regular License.txt` | `RobotoMono-Regular.ttf` (WE's build) | `Apache License / Version 2.0, January 2004` — WE's 2015 build is the **Apache-2.0** one; our bundled copy is the upstream **OFL-1.1** build instead (§4.4) |
| `monof_tt-be11.txt` | `Monofur-PK7og.ttf` | `These fonts are freeware and can be distributed as long as they are together with this text file.` (author's 2000 notice; our `monof55.ttf` travels with the same notice + the Debian OFL record) |
| `twemojimozilla.txt` | `TwemojiMozilla.ttf` | Full text: `by Mozilla licensed under CC-BY-4.0` + the licence URL |

**The 8 fonts that are not bundled** — `name`-table evidence (re-read 2026-09-19) + measured impact:

| Font | `name` table (first-hand) | Redistributable? | Text layers / containers |
|---|---|---|---|
| `8bitOperatorPlus8-Regular.ttf` | ID13 `This font is licensed from Creative Commons (CC-BY-SA 4.0) and SIL Open Font License 1.1.` · ID14 `http://scripts.sil.org/OFL_web` · ID0 `© 2009 - 2014 Grand Chaos Productions.` | **Yes (OFL-1.1)** — but no upstream copy obtainable from this machine (§4.7) | **61 / 17** |
| `Alcubierre.otf` | ID0 `Copyright (c) 2015 by Ellis Design. All rights reserved.` · ID7 trademark | No grant located | 34 / 21 |
| `Atami-Regular.otf` | ID0 `Copyright © 2016 by Andrew Herndon. All rights reserved.` | No grant located | 23 / 10 |
| `CursedTimerUlil-Aznm.ttf` | ID0 `Copyright (c) MMXVIII Por bienestar del los fans of Urban Legend in Limbo. ESTUDIoS HEAVEN CASTRo` | No grant located (doujin derivative work) | 6 / 3 |
| `Lazer84.ttf` | ID0 `Typeface © (your company). 2015. All Rights Reserved` (**unfilled template string**) | No — the free tier grants use, not redistribution | 6 / 3 |
| `opensticks.ttf` | ID0 `Copyright (c) 2014, Apocalypse Laboratories. Free for commercial use.` | No — a **use** grant, not a **distribution** grant | 6 / 3 |
| `kust.ttf` | ID0/ID8/ID9/ID10 `Ieva Mezule, Krisjanis Mezulis © 2015 WildType` (no licence statement anywhere) | No grant located | **0 / 0** — nothing references it |
| `summer85.ttf` | ID0 `Typeface © SUNRISE DIGITAL. <2019>. All Rights Reserved` | No grant located | **0 / 0** — nothing references it |

What this table changes (measured, not assumed):

* `kust.ttf` and `summer85.ttf` are **not a gap at all** — zero text layers reference them, so
  "not bundled" costs nothing.
* The **largest** real gap is `8bitOperatorPlus8-Regular.ttf` (61 layers / 17 containers) and it is
  exactly the one font whose licence *does* permit redistribution — the only missing piece is a
  legitimate copy (author page unreachable; the WE copy is forbidden by the provenance rule). That
  raises the priority of §4.7 without changing its conclusion.
* None of the 8 exists inside the container that references it (measured), so all of them really do
  resolve through tier 3 (the user's own WE install) or tier 4 (`sans-serif`).
* `NotoSans-Regular.ttf` and `TwemojiMozilla.ttf` are bundled yet also have **0** direct corpus
  references: NotoSans is reached only via the `systemfont_*` alias path (see §4.5.2), Twemoji only
  for emoji coverage.

#### 4.5.2 `systemfont_*` aliases (P-127)

Wallpaper `scene.json` text layers may name a **system font alias** instead of a font file
(corpus: `systemfont_arial` 48 layers / 4 containers, `systemfont_consolas` 63 / 3,
`systemfont_comicsans` 4 / 1). These do **not** enter the four-tier chain at all:

* **Browser path** (`demo.html` → `textFontFamily()`): mapped to the real system family name
  (`Arial`, `Consolas`, `Comic Sans MS`, …) with `sans-serif` as the fallback for unknown ids. The
  three ids the corpus uses are all covered. No font file is requested.
* **Offline/Node path** (`elysia/we-renderer/text.js`): before P-127 **every** `systemfont_*` was
  collapsed to `fonts/NotoSans-Regular.ttf` — for the monospace ids that is a **category error**
  (monospace → proportional). P-127 dispatches by category: `systemfont_consolas` /
  `systemfont_couriernew` → `fonts/RobotoMono-Regular.ttf` (already bundled, OFL-1.1); every other
  id keeps NotoSans. **No new font was introduced.**

### 4.6 The two conditional fonts, condition by condition

#### 4.6.1 `spincycle_3d_ot.otf`

**Author: Jess Latham (formerly Blue Vinyl Fonts / BV Fonts) — <https://www.bvfonts.com/>**
Font page: <https://www.bvfonts.com/fonts/details.php?id=44> (Release Date September 18, 2005,
Price: Free) · Terms of Use: <https://www.bvfonts.com/tou.php> ·
FAQ: <https://www.bvfonts.com/faq.php> · download: <https://www.bvfonts.com/fonts/files/spin_cycle_threed.zip>

| # | 逐条义务（作者条款原话 / the author's words） | 我们如何满足（how we satisfy it） | 待律师确认项（pending counsel） |
|---|---|---|---|
| 1 | **Use / redistribution allowed, commercial included**（原文：*"All free fonts at bvfonts.com are freeware. You may use them in personal or commercial work."*；FAQ：*"A license is no longer required to use the freeware for commercial use"*） | 本项目是**免费、GPL-3.0-or-later**的开源渲染器，字体只用于渲染文本层，**不收取任何费用**、不单独售卖字体 | OK — 可核（证据见左列） |
| 2 | **Do not put it on a CD-ROM / compilation disc**（原文：*"Please do not include these fonts on any CD-Roms."*；FAQ：*"DO NOT! put them on a compilation disk or cdrom."*） | 字体作为**本仓库 7 个可解析字体之一**随源码仓库分发，与许可全文同处一目录；**不是**字体合集/字体站/下载站，不在任何实体或售卖介质上，也不提供单独下载页 | OK — 可核（证据见左列）；**边界情形见「未定项 2」** |
| 3 | **Do not resell / remarket / recompile**（原文：*"These fonts are not to be recompiled and sold."*） | 不售卖。文件**逐字节**来自作者 zip 里的 `Open Type/spincycle_3d_ot.otf`：未改名、未转格式、未子集化、未重编译 | OK — 可核（证据见左列） |
| 4 | **Link back**（FAQ：*"Please link me if you do!"*） | 三处回链作者站点：本文件（§4.1、§4.6）、字体旁说明 `assets/fonts/README.md`、渲染器源码注释/日志（`demo.html` 的 `REPO_FONT_ALIASES`/`repoFontUrl` 一带） | OK — 可核（**是否构成可执行义务见「未定项 4」**） |
| 5 | **Take it from the author, not from archives**（原文：*"If you've downloaded a Jess Latham (fka Blue Vinyl Fonts) free font from an archive site and that font is not available here or dafont.com, please ask permission before using it."*；另见 *"It's always a good idea to download the latest version from bvfonts.com"*） | 取件口**只有**作者站点 `bvfonts.com`；**未**从 WE 安装目录取（且**可证不是** WE 那份构建：44,228 B vs WE 44,640 B，sha256 不同） | OK — 可核（证据见左列） |
| 6 | **The author may change the terms**（原文：*"Jess Latham reserves the right to make changes to this license at any time."*） | 条款以**带抓取日期与页面 sha256 的快照**留证，使我们所依据的条件保持可审计（活页面为权威版本） | OK — 可核；**条款可变风险与应对见「未定项 3」** |
**未定项（逐条义务之外、需要律师回答；不并入上表，以免把"已满足"与"待确认"混为一谈）：**

1. **SPDX 标识符本身**：`LicenseRef-BVFonts-Freeware-2006` 是**本地定义**的 `LicenseRef-`（SPDX 允许这种形态，
   但它**不是**注册标识符）。是否改用别的写法 —— **待律师/合规确认**。它出现在 §4.1 的 SPDX 列与本节，
   **只表示"这是作者自定条款"**，不表示已获 SPDX 认可。
2. **"不得放进 CD-ROM / 合集"在"整仓打包分发"下的边界**：我们主张"本仓库不是字体合集"（7 个文件 = 渲染器
   会真正解析的那几个，逐文件许可与来源都在案）。**"一个包含字体的源码仓库"是否落在作者原意里** ——
   **待律师确认**。若律师认为有风险，最小改动是**把该字体从随仓分发里移除**、改为运行期从用户本机 WE 安装
   读取（四级字体链的 tier 3），**代码零改动**（渲染器已实现该链）。
3. **"作者保留随时改条款"的应对**：我们的做法是**快照留证**（抓取日期 + 页面 sha256 + 逐条映射），
   而不是主张"条款已冻结"。若作者日后收紧条款，按 `docs/TAKEDOWN-RESPONSE.md` 的"**先删后议**"执行
   （默认 72 小时内移除）—— 该流程是否足够，**待律师确认**。
4. **回链义务的形态**：作者 FAQ 只有一句 "*Please link me if you do!*"（未写违约后果）。我们做了三处回链
   （本文件 §4.1/§4.6、`assets/fonts/README.md`、`demo.html` 注释/日志）。**"回链"是否构成可执行义务** ——
   **待律师确认**（我们按"即使不是义务也照做"处理，以缩小争议面）。

**monofur** (`monof55.ttf`) carries one author *request* in both of its licensing paths
("*…contact me at unci@tigerden.com if you put them on a server*" / "*…notify me at unci@unci.de
when you use them commercially or redistribute them on a server*"): the font is distributed here
under OFL-1.1 and the request is recorded (author e-mail + Debian `debian/copyright` shipped in
`licenses/`), and the upstream notice file that asks to travel with the font is shipped as
`licenses/monofur-monof_tt-notice.txt`.

### 4.7 `8bitOperatorPlus8-Regular.ttf` → bundled as the author's current **Pixel Operator 8** (CC0-1.0) — resolved 2026-09-18

**The gap** (recorded 2026-09-15): several corpus wallpapers reference WE's built-in name
`fonts/8bitOperatorPlus8-Regular.ttf` (measured 2026-09-18: **61 text layers / 17 containers** — the
single largest font gap). The build shipped inside WE carries an OFL-1.1 statement with RFN
`8-bit Operator+`, © 2009-2014 Grand Chaos Productions, and OFL-1.1 *does* permit redistribution —
but our own rule forbids taking the file out of a Wallpaper Engine installation (§4.2, §4.4), and
the old-name build could not be obtained from the author: deviantart.com timed out from this
machine, the author's GitHub account has no public repos, `dafont.com/8bit-operator.font` 404s, and
fontlibrary returned 0 hits.

**Resolution**: the same typeface lives on under a new name and a new licence — **Pixel Operator**
by **Jayvee Enaguas (HarvettFox96)**, the successor of *8-bit Operator+* (DaFont's own author note:
"Pixel Operator (previously known as the 8-bit Operator)"; update log 2018-10-04-1: *"Changed
licence to Creative Commons Zero (CC0) 1.0"*). We bundle the author's current release
`PixelOperator8.ttf` (DaFont zip, `LICENSE.txt` = CC0 1.0 full text) and map the WE reference name
to it in `demo.html` (`REPO_FONT_ALIASES`) and §4.3.

| Question | Answer |
|---|---|
| Is it the same design? | Same author lineage, **not byte-identical**: 241 glyphs vs the WE copy's 229 (the 2018 update added Esperanto and currency symbols and rearranged some shapes). It is the closest *legally obtainable* substitute; the alternative remains the runtime fallback chain (③ WE install → ④ `sans-serif`). |
| Licence of the bundled file | **CC0 1.0** (public-domain dedication) — redistribution and modification are unrestricted; attribution is not legally required, we credit the author anyway (in-file `name` ID13/ID14 and the table in §4.1). |
| Provenance | DaFont release page of the author (`dafont.com/pixel-operator.font`), zip `dl.dafont.com/dl/?f=pixel_operator`, `PixelOperator8.ttf`, 19,944 B, sha256 `5cccb9ef6cf18977b6e5721d49a1a6e78dd6a6f1c4f69537470f7dc1dc829ffc`, fetched 2026-09-18. **Not** copied from a WE install. |
| What the user sees | Text layers that asked for `8bitOperatorPlus8-Regular.ttf` now render in the pixel face instead of falling back to `sans-serif` — with the (small, documented) glyph differences above. The A/B is `?repofonts=off` (tier ② off → WE tier ③ → `sans-serif`). |

The remaining not-bundled WE fonts (Alcubierre / Atami / CursedTimerUlil / Lazer84 / opensticks,
plus the 0-reference kust / summer85) are unchanged — see §4.5 for the per-font licence findings.

### 4.8 How to add or re-verify a font (recipe)

```bash
cd we-scene-demo/assets/fonts
curl -sSL -o <upstream-file-name> '<upstream URL>'      # author/project source only
sha256sum <upstream-file-name>                          # must match the row in §4.1
node ../text-font-fallback-test.mjs                     # asserts file↔doc↔route↔licence consistency
node ../we-scene-demo-server.mjs 8899 &                 # then: curl -sI localhost:8899/assets/fonts/<file>
```

If the WE reference name differs from the upstream file name, add the mapping to
`REPO_FONT_ALIASES` in `demo.html` **and** to the table in §4.3 and in `assets/fonts/README.md`
(the test checks that all three agree and that the mapped file exists).

---

## 4A. `common*.h` — public-standard declaration, and the honest two-edged fact (2026-09-17)

These are the six shader include files that this project ships in `shaders/`:
`common.h`, `common_blending.h`, `common_blur.h`, `common_composite.h`,
`common_fragment.h`, `common_perspective.h`. They are **our own files** (each carries
`// Original implementation for this project; API-compatible with the shader includes used here.
No third-party code.`), and they are **not** copies of any Wallpaper Engine file — but they were
written **after reading** the WE originals, so they are an **independent implementation**, not a
"clean-room" product (`docs/REIMPLEMENTATION-STATEMENTS.md` §5).

**Declaration (at legal advice; wording may be refined by counsel).**

> 本项目中 `common_*.h` 相关文件所包含的 BT.601 色度权重矩阵、标准色彩变换矩阵等，均为
> ITU-R BT.601 等**公开标准**所定义的公式，属**公有领域的事实性内容**，不构成对任何专有实现的复制。

> The BT.601 chroma-weight matrices, the standard colour-transform matrices and similar material in
> this project's `common_*.h` files are formulas defined by **public standards** (ITU-R BT.601 and
> the like) and are **factual content in the public domain**; they do not constitute copying of any
> proprietary implementation.

### 4A.1 What that declaration is based on (verified locally, reproducible)

- the retained residue against the WE originals was enumerated file by file in
  `../docs/WER-REF-LICENSE-AUDIT.md` §4.3, and falls into four classes: ① function signatures and
  include names (interfaces), ② texture-format ids and material-metadata JSON (format/interface
  data), ③ **public mathematical formulas and constant values**, ④ behavioural constants the
  replacement record had **declared deliberately kept** (`docs/COMMON-HEADERS-REPLACEMENT.md`);
- our replacement files each carry the "No third-party code" line as their first line;
- the WE originals are quarantined **outside** the repository in `../Delete/we-official-shaders/`
  (7 files, `common_vertex.h` included), and **none of them is redistributed**.

### 4A.2 ⚠ The two-edged fact — stated as it is, not softened

**After the re-implementation, the effective lines of our files still coincide with the WE originals
to a high degree.** Measured by `tests/publish-check.mjs` check ③ ("same-name effective-line
overlap", comments/blank lines stripped) whenever the WE asset root is supplied:

| Our file | Effective-line overlap with the same-named WE file | Consequence |
|---|---|---|
| `shaders/common_vertex.h` (**deliberately not shipped**) | **100 %** (187/187) | why it is withheld: after rewriting it was still not an independent product |
| the six shipped `common_*.h` | reported by the same rule; **not 0 %** | still printed on every `--assets` run — as a **REVIEWED** note carrying this section as its evidence reference |

- **One reading**: the residue is exactly the non-copyrightable layer (public formulas, signatures,
  format ids), so high overlap is expected and harmless.
- **The opposite reading**: high overlap is the classic signature of "copied, then re-formatted",
  and no amount of labelling changes that appearance.
- **Both readings are arguable, so we do not decide it here.** The `common.h` weights
  (`vec3(0.11,0.59,0.30)`) and the Gaussian blur coefficients are the sharpest instances: they were
  **deliberately kept** because the pipeline was built against them — a *functional* reason, not a
  copyright conclusion. `common_vertex.h` was withheld precisely because it had no such
  justification (zero references; `BuildTangentSpace` has zero call sites).
- **Status: 待律师确认 / pending counsel** (audit **U-1**, plus `docs/REIMPLEMENTATION-STATEMENTS.md`
  §7 Q3/Q4). Engineering dispositions and records **do not substitute** for that opinion.

### 4A.3 Enforcement

`tests/publish-check.mjs` prints one line per hit together with the evidence reference
(`THIRD-PARTY.md §4A`). This is deliberate: **nothing here is silenced** — see the note-level legend
the script prints with its summary.

---

## 4B. Boundary architecture: MIT plugin ↔ GPL-3.0-or-later renderer (2026-09-17)

The licence pairing of this project works because the two sides are **separated at runtime**, not
merely separated by paperwork. The same fact is the boundary that must not be broken.

| Property | How it holds today | Evidence (reproducible) |
|---|---|---|
| **Different processes / frames** | The renderer is its own HTTP origin (default `http://127.0.0.1:8899/`, overridable in settings); the MIT plugin hosts it inside an `iframe` (`sandbox="allow-scripts allow-pointer-lock"` in strict mode, `… allow-same-origin …` in the legacy/compat mode) | `dsh-mpkg-wallpaper/lib/client.js` — `MPW_SANDBOX_STRICT_ATTR` / `MPW_SANDBOX_LEGACY_ATTR` |
| **Standard Web protocols only** | The wallpaper package is handed over as **one opaque URL** — `?pkgurl=<encoded url>` (also `?pkgpath=`, `?pkgdir=`) — and control/status messages travel by **`postMessage`** (`mpw-cap`, `mpw-ln-key`, the web-shim message family). No shared object graph, no shared module namespace | `server/we-scene-demo-server.mjs` (`/pkgurl` route); `dsh-mpkg-wallpaper/lib/client.js` (`contentWindow.postMessage`, `pkgurl=` URL construction) |
| **Messages are source-checked** | Plugin-side handlers accept a message only when `ev.source === frame.contentWindow` (origin is the secondary test — in strict mode the renderer has the opaque origin `"null"`, so `contentWindow` identity is the primary check) | `dsh-mpkg-wallpaper/lib/client.js` → the `mpwIsSceneFrameMsg` / web-shim source checks |
| **Each side runs alone** | The renderer starts and renders with **no plugin present** (`start-demo.sh` / `server/we-scene-demo-server.mjs`, pages `index.html`, `demo.html`); the plugin loads `.mpkg` wallpapers of all three kinds and degrades to "no scene renderer" when `:8899` is unreachable | `README.md` ("无 WE 安装时的降级行为"); plugin smoke tests |
| **No cross-import of each other's code** | **The plugin never imports or reads renderer code**: `grep -rnE "we-scene-demo" dsh-mpkg-wallpaper/lib/*.js` filtered to import/require ⇒ **0 hits** (only comments and doc strings mention it). **The renderer imports no GPL code from the plugin** | the grep above; the plugin is MIT and stays MIT |
| **What is *not* a boundary violation** | The renderer's **server** imports the plugin's **MIT** package parser (`parsePkg` / `readPkgEntry` from `dsh-mpkg-wallpaper/lib/pkg-extract.js`, §7). That is a **MIT → GPL-3.0-or-later** use of a **separate, MIT-licensed package** — a permitted one-way flow, **not** "importing the other side's code" in the GPL sense. The forbidden direction (GPL code entering the MIT side) has **zero** instances | `server/pack-dir.mjs`, `server/we-scene-demo-server.mjs`; `tests/publish-check.mjs` check ⑤② forbids vendoring the plugin here |

**Contract in one line** (frozen on the plugin side): the plugin decides the sandbox mode and injects
a scene-scoped token; the renderer only reads URL parameters and reports capability back —
`docs/RENDERER-SANDBOX-CONTRACT.md` §1–§2.

### 4B.1 ⚠ Boundary-failure condition (named by counsel)

> **若未来以 Tauri 等打包分发，渲染器必须作为独立 sidecar 进程，不得编译进同一二进制。**
>
> **If this project is ever distributed as a packaged desktop app (Tauri or similar), the renderer
> MUST remain a separate sidecar process. It must NOT be compiled into the same binary as the MIT
> plugin.**

Why: a single binary statically links the GPL-3.0-or-later renderer into the MIT-licensed host, and
the "different processes, standard protocols, no shared code" separation above disappears — and that
separation is the *only* reason the MIT/GPL pairing is defensible here.

**Practical rule for any future packaging work:**

1. ship the renderer as its own executable/process (sidecar), driven over the same HTTP +
   `postMessage` contract as today (today's wiring already is a local HTTP origin, so this is a
   packaging constraint, not a redesign);
2. a "sidecar" whose code is compiled into the host binary, or a "single-file app" that inlines the
   renderer's `.mjs`/bundle, **breaks the boundary** and must be treated as a licence change (the
   whole packaged work becomes GPL-3.0-or-later, and the MIT side can no longer stay MIT);
3. any packaging decision therefore needs a licence review **before** implementation, with the
   outcome recorded in `docs/COPYING-RULES.md` §3 (protocol boundary) and in this section.

---

## 5. This repository's own licence — GPL-3.0-or-later  (P-89, 2026-09-16)

The renderer's own code (everything not listed in §1–§4 above) is licensed under the
**GNU General Public License, version 3 or (at your option) any later version**.
Full text: `LICENSE` (verbatim GNU GPL v3, 35,147 bytes, with the copyright notice and the
"either version 3 … or any later version" wording appended after it — the licence terms
themselves are unmodified). SPDX: `GPL-3.0-or-later`.

**Nothing in §1–§4 is relicensed by this**: the MIT, ISC, OFL-1.1, Apache-2.0 and CC-BY-4.0
notices above remain in force for the material they cover, and the MIT notices must keep
travelling with any redistribution of this repository.

## 6. webwallgl  (MIT © oneincase) — **P-90: FXAA shader ported in**

  Upstream:  https://github.com/oneincase/webwallgl
  Licence:   MIT
  Copyright: Copyright (c) 2026 oneincase <462534624@qq.com>
  Commit:    `fdfc578a577d0e680a9cfe2cf2e3e825d3cd2372` (2026-09-15, version **1.3.23**,
             "feat: 渲染质量档位 1.3.23 + 透视层 3D 倾斜 + 背景纹理合成三修" — the same
             commit that introduced both `renderer/src/quality.ts` and the `FXAA_FRAG` shader)
  SPDX:      MIT
  Ledger:    `docs/COPYING-RULES.md` §4, entry **#6** (2026-09-16)

### 6.1 What was ported (exact, one file → one file)

| Upstream path | Commit | What we took | Where it landed here |
|---|---|---|---|
| `renderer/vendor/we-scene/render/renderer-glsl.js` — `FXAA_FRAG` (lines 452–489 upstream) | `fdfc578` | the **FXAA fragment shader GLSL**, verbatim | `core/we-scene-bundle.js`, constant **`FXAA_FS`** |

**Byte-level verification** (`FXAA_FS` vs upstream `FXAA_FRAG`, ignoring comments, blank lines and
indentation): **identical — 38/38 GLSL lines, no token differs.** The algorithm constants are the
upstream ones unchanged (`SPAN_MAX = 8.0`, `REDUCE_MUL = 1.0/8.0`, `REDUCE_MIN = 1.0/128.0`,
`LUMA = vec3(0.299, 0.587, 0.114)`), and the output alpha is `1.0` as upstream.

What is **ours, not upstream** (so it is not covered by this attribution):

- the **vertex** stage — we reuse this repository's existing `BLOOM_VS` full-screen-triangle-pair
  shader instead of upstream's own quad;
- the **pass orchestration** (`runAAPass`, the read-back texture, the frame-token idempotence
  guard, the `?aa=` tier parsing, `resolveAaMode`, the MSAA fallback policy) — written here;
- the surrounding `?q` / `?pp` tiers and the `setQuality` hot-update API — written here against
  the *semantics* documented in `docs/WEBWALLGL-UPSTREAM-STUDY.md` §5 and `PATCHES.md` P-90.
  **No other webwallgl file was copied.** In particular `renderer/src/quality.ts` was **not**
  copied: we agreed with it on the tier *meanings* (which are facts about the WE client's
  options, not protected expression) and wrote our own pure functions.

### 6.2 MIT Licence (verbatim, upstream `LICENSE` at `fdfc578`)

```
MIT License

Copyright (c) 2026 oneincase <462534624@qq.com>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```

### 6.3 Direction of the licence flow

MIT → GPL-3.0-or-later is a **permitted one-way flow** (`docs/COPYING-RULES.md` §2.1). The
ported `FXAA_FS` is distributed here under this repository's GPL-3.0-or-later, but the MIT notice
above **remains in force for that shader** and must keep travelling with any redistribution.
Nothing flows back: this repository's GPL code must never enter `oneincase/webwallgl` or the
MIT-licensed plugin.

### 6.4 Upstream **source checkout** is not vendored — but the upstream **static build IS redistributed** under `demo/`

Two different things, both stated here so the record is unambiguous:

* **Not vendored (source checkout).** The webwallgl source checkout used for the comparisons
  (`vendor-ref/webwallgl/`) lives **outside** the repository and is excluded from the published
  tree. No upstream *source* file is redistributed.
* **Redistributed (static build), MIT notice travelling with it.** `demo/**` is a **redistributed
  static build of WebWallGL's web test bench, patched by this project** — upstream build artifacts
  (`demo/assets/renderer-*.js`, `demo/assets/bench-*.js`, `demo/assets/bench-*.css`,
  `demo/index.html`, `demo/renderer/index.html`, `demo/default-wallpaper/index.html`,
  `demo/icons/*`, `demo/manifest.webmanifest`, `demo/sw.js`, whose branding strings still read
  "WebWallGL") are redistributed **unchanged**, together with this project's run-time patch
  `demo/bench-patch.js` (our code, GPL-3.0-or-later; it does not modify the minified artifacts).
  The MIT notice required by the licence above **ships inside the directory, in two equivalent
  copies**: `demo/LICENSE-webwallgl-MIT.txt` (written by the repository line: verbatim MIT text +
  attribution + a statement of what is upstream and what is ours) and `demo/LICENSE-webwallgl`
  (the same licence under this repository's `<LICENSE>-<upstream>` naming habit; P-93 added it,
  and neither file may be deleted). `demo/` contains no wallpaper package, preview image, audio,
  video or Workshop content — its default scene is this repository's own synthetic sample
  (`samples/sample-synthetic/`), and the page states that on screen.
  The redistributed build is also reachable online as the Pages demo: `/demo/` is the canonical
  entry, and the build additionally stages the same files under `/WEwebLoader/` (the site path name,
  P-127) because the minified bundle hard-codes that prefix (`build-pages.mjs`, `docs/ONLINE-DEMO.md` §2.1).
  The previous path `/wallpaper-engine-webgl/` is kept alive by tiny `noindex` redirect pages only —
  no second copy of the redistributed build is published there.
* **Also reproduced in our own code.** The shader shown in §6.1 (`FXAA_FS`) is a verbatim port into
  `core/we-scene-bundle.js`.

MIT → GPL-3.0-or-later is a permitted one-way flow (`docs/COPYING-RULES.md` §2.1), so
redistributing the MIT build inside this GPL repository is fine **provided the MIT notice keeps
travelling with it** — which is what the two `demo/LICENSE-webwallgl*` files do.
`publish-check.mjs` check ④ enforces this automatically: any file path matching `webwallgl` must
come with a `LICENSE`/`COPYING` file containing the word "MIT".

---

## 7. dsh-mpkg-wallpaper  (MIT) — imported dependency, kept MIT on purpose

  Upstream:  https://github.com/XHR666/dsh-mpkg-wallpaper
  Licence:   MIT (no GPL text anywhere in that package — this is asserted by
             `publish-check.mjs` check ②)
  Copyright: Copyright (c) 2026 dsh-mpkg-wallpaper contributors

The renderer **imports** the plugin's package parser (e.g. `server/we-scene-demo-server.mjs` imports
`parsePkg` / `readPkgEntry` from `dsh-mpkg-wallpaper/lib/pkg-extract.js`). The plugin stays
MIT-licensed; MIT → GPL is a permitted one-way flow, and this repository's GPL does **not**
propagate back into the plugin. When redistributing this renderer you must carry the plugin's
MIT notice along with it; the reverse direction (GPL code flowing into the MIT plugin) is
forbidden — see `docs/COPYING-RULES.md`.

---

## 8. Reference / behavioural-comparison register — no code copied, **with one disclosed exception**

`README.md` §7 lists, one project at a time, everything this project referenced. This section is
the same register kept next to the licence notices, because two of the entries are
**licence-incompatible** with this repository's GPL-3.0-or-later and one is **unlicensed**
(all rights reserved). Nothing in this section is redistributed here: all upstream checkouts live **outside**
the repository (`../lwe-ref/`, `../wer-ref/`, `../we-layerd-ref/`, `../vendor-ref/`).
The handling ladder for unlicensed / licence-incompatible upstreams (L1 spec-first independent
re-implementation → L2 read the metadata → L3 own equivalent implementation → L4 licence-compatible
look-alike → L5 isolated, deletable block) is `docs/COPYING-RULES.md` **§9**.
**Terminology (2026-09-17, per legal advice):** what this project does is an **independent
implementation** — **not** a "clean-room rewrite". A clean-room claim requires that the implementer
never saw the original; in every case recorded here **the implementer had seen it**, so the
clean-room label is withdrawn. Full disclosure, case by case, in
`docs/REIMPLEMENTATION-STATEMENTS.md`; the operating rule is `docs/COPYING-RULES.md` **§5.1**.

| Upstream | Licence | What was referenced | Code copied? |
|---|---|---|---|
| [Almamu/linux-wallpaperengine](https://github.com/Almamu/linux-wallpaperengine) | **GPL-3.0-only** | `.tex` container format basis (`TEXV0005`+`TEXI0001`+`TEXB0001~0004`), V3-layout fallback, `ObjectParser.cpp:770` default length, `CParticle.cpp:767-778` velocity-random initializer, control-point flags | **No** (no ledger entry in `docs/COPYING-RULES.md` §4 ⇒ never borrowed) |
| [Aromatic05/wallpaper-engine-renderer](https://github.com/Aromatic05/wallpaper-engine-renderer) | **GPL-2.0-only** | ⚠ **Two point-like same-origin fragments were found by our own audit** (`../docs/WER-REF-LICENSE-AUDIT.md` §3.4): (a) `normalizeImageAlpha` (then `core/we-scene-bundle.js:951-956`) ↔ upstream `WPImageObject.cpp:59-63` — judged a **line-for-line translation** (same branch order, same magic numbers `1`/`100`, same clamp); (b) the alignment offset (then `core/we-scene-bundle.js:4430-4441`) ↔ upstream `WPImageAlignment.hpp:24-36` — judged a **same-origin rewrite, not a clean-room product** (identical token→axis→direction table, same substring dispatch, same `center` short-circuit). Two weaker forms were also found and **have since been fixed (P-95)**: the parallax `mouse_vec` formula that used to be written out as an upstream expression in a comment is now derived in **our own coordinate system** (the upstream file/line citation is gone), and the `__makeNoopVideoTexture` name in `elysia/scene-scripts.js` — which was `wer-ref`'s **private** name, misleadingly described there as "official" — has been removed (the comment now describes it as the WE script API `getVideoTexture` no-op fallback, and the implementation, being the API-contract-only minimum, is unchanged) | **Not verbatim** (no comment / error-string / constant-table copies; ~2 functions, ~11 lines, point-like — not a paragraph- or file-level port; 0 `import`/`require`/`readFile` of that tree). **✅ Both same-origin fragments have since been re-implemented from a written specification (P-95, 2026-09-16) — an independent implementation, **not** a clean-room rewrite** (the implementer had read the original; see `docs/REIMPLEMENTATION-STATEMENTS.md` §3/§4), following the five-step process of `docs/COPYING-RULES.md` §5.1: a written behaviour spec (`docs/IMAGE-ALPHA-ALIGN-SPEC.md`) → an implementation based only on that spec (`coerceImageAlphaMode` + `classifyAlphaDomain`/`saturateUnitInterval` with named bounds; `alignmentOffsetForToken` + `readAlignmentAxisSigns` + `ALIGNMENT_HALF_SHIFTS`, where token glyphs and offsets are fully decoupled into a sign-pair lookup) → provably different naming/branching/constants/structures/comments → a spec-only test (`clean-room-alpha-align-test.mjs`, **1008 pass / 0 fail**, including "bit-identical to the pre-rewrite implementation" and six real-package corpus regressions). The old `normalizeImageAlpha` and the pre-rewrite alignment-offset identifier (quoted verbatim in the audit, §3.4 fragment 2) no longer exist. **Bookkeeping (now closed):** the rewrite is registered as `PATCHES.md` **P-95**, and the source comments were corrected to cite **P-95** as well (P-91 in the changelog is the separate *distribution-shape* entry) — and `../docs/WER-REF-LICENSE-AUDIT.md` §3.4 now carries a **"✅ post-hoc addendum"** documenting the remediation while leaving the original findings standing. **Still open:** the **legal characterisation** (audit §7 **U-1**, whether this ever amounted to a GPL-2.0-only derivative, needs a lawyer; **U-5**, whether the list is exhaustive), the residual upstream-expression citation at `core/we-scene-bundle.js:7233` has also been removed. GPL-2.0-only remains **bidirectionally incompatible** with this repository's GPL-3.0-or-later, which is why the rewrite was required. |
| [catsout/wallpaper-scene-renderer](https://github.com/catsout/wallpaper-scene-renderer) | **GPL-2.0-only** (archived) | Archived fork parent of `wer-ref` (same LICENSE blob); read only as a third-party cross-check on "is the official behaviour what we think it is" — only its **behavioural conclusions** are cited | **No** (no local checkout) |
| [waywallen/waywallen](https://github.com/waywallen/waywallen) | **MIT** | Architecture precedent only: "permissive host + copyleft renderer spawned as a separate process" (upstream `plugin.toml.in`, `[renderers.wescene-renderer]`) | **No** — the recorded MIT permission to borrow was never exercised |
| [waywallen/open-wallpaper-engine](https://github.com/waywallen/open-wallpaper-engine) | **GPL-2.0-only** | Zero contact: appears only in licence-compatibility analysis and in `wer-ref`'s own migration note (upstream-to-upstream lineage) | **No** |
| [aqnya/unmpkg](https://github.com/aqnya/unmpkg) | **GPL-3.0** | `.mpkg` binary **format** only | **No** — the 38-line script that named it as its format source was **deleted 2026-09-16** (lineage could not be excluded). Whether "format only" is provable **cannot be confirmed on this machine** |
| [notscuffed/repkg](https://github.com/notscuffed/repkg) | **MIT** — `Copyright (c) 2019 notscuffed` (**settled 2026-09-17**; earlier records in this repo said "GPL", which was a mis-entry — see `docs/COPYING-RULES.md` §6 + §9.10) | `.tex` decoding conventions (RG88 as `(rgb=G, a=R)`, ImageSharp `Rgba32` channel agreement, greyscale = 2nd channel, alpha = 1st) | **No (verbatim)** — but `core/we-scene-bundle.js` states that BC1/BC2/BC3 are "**line-by-line aligned with RePKG's LibSquish port**", which is stronger than "format reference". With the licence now settled as **MIT**, even a true line-by-line port would be a **permitted MIT → GPL-3.0-or-later flow** (§2.1) and would only require an entry in `docs/COPYING-RULES.md` §4 plus the MIT notice; **no such copy is vendored today** (no local checkout). The 267-line script that named it as a source was deleted 2026-09-16 |
| [Aromatic05/we-layerd](https://github.com/Aromatic05/we-layerd) | **NONE — no LICENSE file ⇒ all rights reserved.** Verified 2026-09-17: GitHub API `"license": null`; upstream root tree has **no `LICENSE`/`COPYING`/`NOTICE`**; `Cargo.toml` (root **and** all four member crates) has **no `license` / `license-file` / `repository`** field; the project's **own packing metadata says so itself** — `package/archlinux/PKGBUILD:9` = `license=('custom:unlicensed')`, `package/fedora/we-layerd.spec:8` = `License: LicenseRef-Unlicensed` | One behavioural cross-check ("this feature has zero implementation there"). **Note (2026-09-17)**: its `.gitmodules` pins `third_party/wallpaper-engine-renderer` → `Aromatic05/wallpaper-engine-renderer` @ `89dfcd86de2dc0ae537bc136046c5ed05733e7b7` — **exactly this workspace's `wer-ref/` checkout, i.e. GPL-2.0-only**; so even "write it yourself after reading their implementation" must avoid that submodule | **No** — full evidence table in `docs/COPYING-RULES.md` §9.8 |

**Not referenced at all** (present only in licence-compatibility studies — do not read them as
credits): [NixaXI/AnisPaper](https://github.com/NixaXI/AnisPaper) — GPL-3.0 (its `LICENSE` says bare
GPL-3.0 while six self-descriptions say or-later; **treat conservatively as GPL-3.0-only**, status
**undetermined**) — our records state "no code was borrowed from it this round", and there is no
local checkout.

**Wallpaper Engine itself** is proprietary (Valve / Steam Subscriber Agreement). No WE asset is
redistributed here; the six internal shader includes are self-written API-compatible replacements
(see `../docs/WER-REF-LICENSE-AUDIT.md` §4). Record-keeping note (**closed, 2026-09-16, P-95**):
the cleanup record used to say seven rewritten headers while six exist. `common_vertex.h` is
**deliberately not shipped** and is **not** a missing file — it has zero references in this pipeline
(`BuildTangentSpace` has zero call sites) and, after rewriting, its effective lines still coincided
100 % with WE's own file, so it is not a clean-room product. Both the WE original
(`Delete/we-official-shaders/common_vertex.h`, `7bc1bc8a…`) and the withheld replacement
(`Delete/we-official-shaders-leftover/common_vertex.h`, `f02b7694…`) are preserved in the
out-of-repo evidence archive, and `../docs/COMMON-HEADERS-REPLACEMENT.md` §1.1 now records the
scope as **six files** with the reproduction commands.

---

## 9. webwallgl  (MIT © oneincase) — **P-93: HLSL→GLSL translator vendored**

  Upstream:  https://github.com/oneincase/webwallgl
  Licence:   MIT
  Copyright: Copyright (c) 2026 oneincase <462534624@qq.com>
  Commit:    `d6dd5bc31d` (2026-09-21, upstream `origin/main`；两个 vendored 转译器文件于 2026-09-22 更新到此版本)
  SPDX:      MIT
  Ledger:    `docs/COPYING-RULES.md` §4, entry **#8** (2026-09-16)
  Local copy: `vendor/hlsl2glsl/README.md` (per-file blob / sha256 / byte counts)

### 9.1 What was vendored (exact, byte-for-byte, two files + upstream LICENSE)

| Upstream path | Upstream blob | Bytes / lines | sha256 (our copy) | Lands at |
|---|---|---|---|---|
| `renderer/vendor/we-scene/render/hlsl2glsl.js` | `66efe02d2f25c2369decee0f92ed655a5855715b` | 93,531 / 1658 | `574fa82372bccc78efb12db958e31dc315bf9df2eac302be9e8982482293bc8f` | `vendor/hlsl2glsl/hlsl2glsl.js` |
| `renderer/vendor/we-scene/render/hlsl-preprocessor.js` | `5544c1359a61c2ee5dd137b6ec1fa46b1a1ae65b` | 14,920 / 418 | `ced8a2ceaea4e0137dfc185b248529eaf8051ae61ad8f632fdc5084c941ec907` | `vendor/hlsl2glsl/hlsl-preprocessor.js` |
| `LICENSE` (repo root) | — | 1,085 / 21 | `857432ca4f48930e6079aca25164c27b791576ee2a7d3e3c9d6a92a089fe4948` | `vendor/hlsl2glsl/LICENSE` |

**Byte-level verification**: our copies are **verbatim** — `git diff origin/main -- <the two paths>` in the
upstream checkout (`../vendor-ref/webwallgl/`) is **empty**, and the sha256 triple above matches the
copies shipped here. **Not one character was changed**; no reformatting, no comment stripping.

### 9.2 What was deliberately **NOT** vendored

| Upstream file | Why not |
|---|---|
| `render/headers.ts` (the `WE_SHADER_HEADERS` table) | the translator takes the include resolver as its **4th argument** (`hlsl2glsl(src, stage, combos, includeResolver, siblingSrc)`) — this repository passes **its own** `common*.h` (see `docs/COMMON-HEADERS-REPLACEMENT.md`), so no upstream header table is copied |
| `renderer/src/quality.ts` | already disclosed separately in §6: tier *semantics* aligned, implementation written here |
| `renderer.js`, `renderer-glsl.js`, `shaders/**` | unrelated to this vendoring; the `FXAA_FRAG` shader is a **separate** entry (§6) |
| the rest of `renderer/vendor/we-scene/**` | not needed; keeping the vendored surface minimal is the point |

### 9.3 MIT Licence (verbatim, upstream `LICENSE` at `fdfc578`)

The full MIT text is reproduced in §6.2 above and travels **in-file** as
`vendor/hlsl2glsl/LICENSE` (byte-identical to upstream). It applies to the two vendored files.

### 9.4 Direction of the licence flow, and what the vendoring is *for*

MIT → GPL-3.0-or-later is a **permitted one-way flow** (`docs/COPYING-RULES.md` §2.1). The vendored
translator is distributed here under this repository's GPL-3.0-or-later while the MIT notice above
**remains in force for those two files** and must keep travelling with any redistribution.

It is used by exactly one consumer today: the coverage gate `hlsl2glsl-coverage-test.mjs`, which turns the
number in `docs/HLSL2GLSL-COVERAGE.md` §0 (112/114 = 98.2%) into an assertion that can go red
(self-proof: `MPW_H2G_MIN_RATIO=0.999 node hlsl2glsl-coverage-test.mjs` ⇒ rc=1). **The renderer's own
shader path does not call it yet** — wiring it into `core/we-scene-bundle.js` is an open item (PATCHES.md P-93,
"未做"). No webwallgl code beyond these two files (and the §6 FXAA shader) is present in this repository.

---

## 10. 参考资料（未复制代码）—— 本机副本位置与用途 / Reference-only checkouts (no code copied)

下面两个目录是**只读的行为对照副本**，位于仓库**之外**（工作区根 = 本仓库的**父目录**，
`.gitignore.public` 不涉及、任何发布产物都不包含），本仓库**从未** import / `require` / `readFile`
它们（`grep -rn -E "(import|require|readFile|readFileSync|createReadStream)[^\n]{0,80}(wer-ref|we-layerd-ref)"` ⇒ 0 命中；2026-09-17 复核 `we-layerd` 全仓共 **26 个文件 / 37 行**命中，**全部是注释与文档文字**，无一处代码读取）。

| 本机副本 | 上游 | 许可 | 用途（**仅此一项**） | 边界 |
|---|---|---|---|---|
| `wer-ref/` | [Aromatic05/wallpaper-engine-renderer](https://github.com/Aromatic05/wallpaper-engine-renderer)（`catsout/wallpaper-scene-renderer` 的 fork） | **GPL-2.0-only**（逐字节同一份 v2 文本，blob `d159169d…`） | **行为对照**：读它的**行为结论**（语义、默认值、边界、字段含义），与本实现逐项对照 | **严禁复制代码/注释/错误文案/常量组织**；**不得进入任何发布产物**；与本仓库 GPL-3.0-or-later **双向不兼容**。血缘自查：`../docs/WER-REF-LICENSE-AUDIT.md`（逐字复制 0 处、段落级 0 处；2 处点状同源已**按规格独立实现**（P-95；"独立实现"而非"洁净室"，因实现者接触过原件，见 `docs/REIMPLEMENTATION-STATEMENTS.md`），登记于 `PATCHES.md` **P-95**，源码注释已同步为 P-95） |
| `we-layerd-ref/` | [Aromatic05/we-layerd](https://github.com/Aromatic05/we-layerd) | ⚠ **无任何许可**（上游 `/license` = HTTP 404；本机 `LICENSE*`/`COPYING*` = 0 个；**上游自己的打包元数据自认 `custom:unlicensed` / `LicenseRef-Unlicensed`**，证据表见 `docs/COPYING-RULES.md` §9.8）⇒ **保留所有权利**，**比 GPL 更严** | **行为对照**：只用来核对"某特性在那里是零实现"这类**行为事实** | **严禁复制代码**；**不得进入任何发布产物**。上游依赖树许可**已展开、U-3 已结案（2026-09-17）**：其渲染核心是 `.gitmodules` 捆进来的 **GPL-2.0-only** 子模块（`Aromatic05/wallpaper-engine-renderer` @ `89dfcd86…`，= 本工作区 `wer-ref/`）⇒ 连"照它的实现自写"都要避开该子模块；处置判定 = **L2 完成、保持零引入**（`docs/COPYING-RULES.md` §9.8） |

**登记口径**：可借表两行已补进 `docs/COPYING-RULES.md` §2.3（`wer-ref` = GPL-2.0-only ❌ 不可借；
`we-layerd-ref` = 无许可 ❌ 不可借），两行都写明"**仅行为对照 / 独立实现（按规格，不主张洁净室）；严禁复制代码；不得进入任何发布产物**"。
任何文档/注释提到这两个来源时，**同一句**必须带中性标注（第三方参考实现 + GPL-2.0-only/无许可 + 仅行为对照 +
未取代码），不得再称其为"官方"或"真值源"。
**处置阶梯**：这两类上游（GPL-2.0-only / 无许可）按照 `docs/COPYING-RULES.md` **§9** 的 L1–L5 阶梯处理 ——
L1 按规格独立实现（`wer-ref` 的 2 处点状同源 = 已落地的 L1 样本；**称"独立实现"而非"洁净室"**，见 §5.1）→ L2 查上游元数据（**任何"无许可"判定之前必须先查**）
→ L3 自写等价实现（含增量重写）→ L4 换许可兼容的类似项目 → L5 隔离块（单独成块 + 一键禁用 + 可整体删除 + 公开说明
"作者要求即删"，见 §9.6/§9.11）。**当前本仓库没有任何 L5 块。**
---

## 11. webwallgl  (MIT © oneincase) — **P-102: 帧几何契约按规格重写进 `core/web-frame-geometry.mjs`**

  Upstream:  https://github.com/oneincase/webwallgl
  Licence:   MIT
  Copyright: Copyright (c) 2026 oneincase <462534624@qq.com>
  Commit:    `b61e8910ae0a176288aed99ce9a93a13ea07df57` (2026-09-15, version **1.3.16**)
  SPDX:      MIT
  Ledger:    `docs/COPYING-RULES.md` §4, entry **#9** (2026-09-16)
  Spec:      `docs/WEB-FRAME-GEOMETRY-SPEC.md`（先写规格、再按规格实现）

### 11.1 What was referenced (behaviour contract only — no code copied)

| Upstream path | What we aligned on | Lands at |
|---|---|---|
| `renderer/src/web.ts` — `webPointerToClient` (lines 366–383) | 窗口坐标 → **帧内 client 像素**的换算口径：只认 client 空间、除以"显示盒/内部视口"的缩放系数、非有限值与零尺寸一律丢弃 | `core/web-frame-geometry.mjs` → `frameClientPoint()` |
| `renderer/src/web.ts` — `webCoverViewport` (lines 577–593) | 覆盖式视口：视口取**内容比例**、溢出的一边**居中裁掉**、比例差在容差内则不处理 | `core/web-frame-geometry.mjs` → `coverViewport()` |
| `renderer/src/web.ts` — `measureWebLetterbox` / `WEB_ASPECT_*` (lines 561–635) | 内容比例**只认内在尺寸**（元数据未到不拿占位盒当设计比例）、比例限幅（越界视为量错） | `core/web-frame-geometry.mjs` → `contentAspectOf()` |

### 11.2 Independent-implementation evidence (implementation is not a translation)

Differences from the upstream implementation (naming / parameter shape / mode set / constant organisation /
boundary handling / fallback switch) are enumerated as the independence criteria in
`docs/WEB-FRAME-GEOMETRY-SPEC.md` §6 and machine-asserted by `tests/web-frame-geometry-test.mjs` T4d/T4e:

- **Naming**: `webPointerToClient` / `webCoverViewport` / `measureWebLetterbox` → `frameClientPoint` /
  `coverViewport` / `contentAspectOf` (+ `frameVisibleRect`, `normalizeFrameFit`, `frameGeomModeFromQuery`
  — these three have no upstream counterpart at all).
- **Parameter shape**: upstream takes three positional tuples; ours takes an event object plus two plain
  box objects (a `DOMRect` and a literal work equally).
- **Mode set**: upstream handles only `cover`; ours has an explicit three-state `normalizeFrameFit`
  (`cover`/`contain`/`stretch`) with unknown values falling back to `cover`.
- **Constant organisation**: upstream keeps aspect eps/min/max inline in `web.ts`; ours exports them as a
  named table (`FRAME_ASPECT_EPS` / `FRAME_ASPECT_MIN` / `FRAME_ASPECT_MAX`) documented in spec §2.2/§3.
- **Hit-testing**: upstream keeps `elementFromPoint` dispatch inside its frame shim; **this module does not
  dispatch events at all** (the renderer does not own DOM events) — that machinery lives in the MIT plugin
  (`dsh-mpkg-wallpaper/lib/web-wallpaper.js`) and is documented separately.
- **Fallback switch**: `?frame=legacy` (parsed by `frameGeomModeFromQuery`) — upstream has none.

Also, note what was **not** ported: the upstream letterbox *measurement* walk over `video,img` candidates
(its "is the page showing black bars" heuristic) stays upstream — it needs a live frame document, which is
the host's business, not the renderer's. Only the pure ratio/limit rules were taken.

### 11.3 Licence text

The MIT licence text of the upstream project is reproduced in §9.3 above (same upstream repository and
same copyright holder); no additional licence file is required for this entry because **no upstream file
was copied** — `git ls-files vendor/` lists only the P-93 vendored pair.

---

## 12. webwallgl  (MIT © oneincase) — **P-103: 128 元频段数组契约按规格重写进 `core/audio-band-array.mjs`**

  Upstream:  https://github.com/oneincase/webwallgl
  Licence:   MIT
  Copyright: Copyright (c) 2026 oneincase <462534624@qq.com>
  Commit:    `b61e8910ae0a176288aed99ce9a93a13ea07df57` (2026-09-15, version **1.3.16**)
  SPDX:      MIT
  Ledger:    `docs/COPYING-RULES.md` §4, entry **#10** (2026-09-16)
  Spec:      `docs/AUDIO-BAND-SPEC.md`（先写规格、再按规格实现）

### 12.1 What was referenced (behaviour contract only — no code copied)

| Upstream path | What we aligned on | Lands at |
|---|---|---|
| `renderer/src/web.ts` — `packWebAudioArray` / `packWebAudioArrayInto` (lines 63–87) | 频段数组的**槽位契约**：左 0..63 + 右 64..127、长度不足补 0、长度固定 128 | `core/audio-band-array.mjs` → `packBands()` |
| `renderer/src/web.ts` — `shapeWebAudioBand` / `WEB_SIM_AUDIO_GAIN` / `WEB_SIM_AUDIO_GAMMA` (lines 106–134) | "频谱要尖"的观感与实现路线：`min(1, pow(x, γ) × gain)`，γ=1.8 / gain=1.8 | `core/audio-band-array.mjs` → `shapeBand()` + `AUDIO_BAND_GAMMA` / `AUDIO_BAND_GAIN` |
| `renderer/src/web.ts` — 注入源 driver 分支 (lines 168–228) | 真实（已归一化）频谱**不套 γ**、只钳位这一口径 | `packBands(..., { clampOnly: true })` |

### 12.2 Independent-implementation evidence (implementation is not a translation)

Differences are enumerated in `docs/AUDIO-BAND-SPEC.md` §5 and machine-asserted by
`tests/audio-band-array-test.mjs` T4b:

- **Naming**: `packWebAudioArray` / `packWebAudioArrayInto` / `shapeWebAudioBand` / `WEB_SIM_AUDIO_*`
  → `packBands` / `shapeBand` / `simulatedBands` / `simulatedBandArray` / `bandStats` / `AUDIO_BAND_*`.
- **Packing shape**: upstream ships two functions plus a module-level singleton pump buffer; ours is a
  single function with an optional `out` argument — **no hidden module state at all**.
- **Curve constants**: upstream exports them and reads them directly inside the shaper; ours additionally
  accepts `opts.gamma` / `opts.gain` overrides (identity curve for golden tests), defaults fixed in spec §2.3.
- **Real-source convention**: upstream expresses "don't apply γ to a real spectrum" via a separate driver
  branch; ours expresses it as one explicit `clampOnly` flag on the same entry point.
- **Simulated source**: upstream reuses a stateful, dt-driven scene-side simulator; ours is a **pure
  function of `(t, seed)`** — no internal state, no dt, replayable and directly assertable.
- **Diagnostics**: `bandStats()` (silent / peak / peakAt / mean / nonzero) has no upstream counterpart.

What was **not** ported: the upstream pump scheduling (`WEB_AUDIO_PUMP_HZ` rAF loop) and its bridge/live
driver selection policy — those belong to the host that owns the audio source, not to the renderer.

### 12.3 Licence text

The MIT licence text of the upstream project is reproduced in §9.3 above (same upstream repository and
same copyright holder); no additional licence file is required for this entry because **no upstream file
was copied** — `git ls-files vendor/` lists only the P-93 vendored pair.

---

## 13. Lucide icons (ISC © Lucide Contributors; Feather portions MIT) — **inline geometry in the demo bench picker**

  Upstream:  https://github.com/lucide-icons/lucide
  Package:   `lucide-static@0.545.0` (npm) — files `icons/<name>.svg`
  Licence:   **ISC** (portions derived from Feather are MIT)
  Copyright: portions of Lucide are held by Cole Bemis 2013-2023 as part of Feather (MIT);
             all other copyright (c) for Lucide is held by Lucide Contributors 2025
  SPDX:      ISC (and MIT for the Feather-derived portions)
  Ledger:    `docs/COPYING-RULES.md` §4, entry **#11** (2026-09-17)
  Scope:     **7 icons registered, geometry only, inlined** — 6 of them rendered by the picker,
             1 (`search`) kept as a spare; no vendored files, no dependency, no icon font

### 13.1 What is used, and where

`demo/bench-patch.js` (the single physical source of the patched bench, shared by the `:8901` static
bench and the vite host) inlines the geometry of the icons used by the folder/file picker:

| Icon | Lands at | Upstream file | Verified |
|---|---|---|---|
| `folder`, `file`, `house`, `check`, `x` | `DIR_ICONS` in `demo/bench-patch.js` | `lucide-static@0.545.0/icons/<name>.svg` | 2026-09-17 — every `d` / `cx,cy,r` compared **byte-for-byte** against that version's published file |
| `arrow-up` (rendered: “up one level”), `search` (registered spare, **not** rendered — the filter box is a plain `input[type=search]`) | same table | this project's own `docs/SVG-ICONS.md` (user-provided; same Lucide/Feather geometry) | copied as given by the user; not counted as a separate upstream entry |

Only the child elements of each upstream `<svg>` are used (`path` / `circle`), built through
`document.createElementNS`. The class names (`lucide lucide-*`) are dropped; `viewBox="0 0 24 24"`,
`fill="none"`, `stroke="currentColor"`, `stroke-width="2"` and round caps/joins are kept, so the icons
inherit the text colour and work in both themes.

### 13.2 Licence text

The full upstream licence — ISC **and** the MIT text for the Feather-derived portions — is redistributed
verbatim in `demo/LICENSE-lucide-ISC.txt` (ISC requires the copyright notice and the permission notice to
appear in all copies). Per-icon provenance table: `docs/ICONS-NEEDED.md`.
**Nothing else from Lucide is used**: no import, no dependency entry, no external stylesheet and no icon
font — the inline geometry is the only surface.

---

## 14. webwallgl  (MIT © oneincase) — **P-136: 鼠标尾迹按用户指令照抄上游**

  Upstream:  https://github.com/oneincase/webwallgl
  Licence:   MIT
  Copyright: Copyright (c) 2026 oneincase <462534624@qq.com>
  Commit:    `b61e8910ae0a176288aed99ce9a93a13ea07df57` —— 本机 checkout 的 HEAD，**本节所有
             `file:line` 都以它为准**（`particles.js` 5597fec4→c541f72f、
             `scene-mount.ts` b3421a9f→af823024 在 `fdfc578`→`b61e891` 之间都改过，
             行号**不可**跨版本引用）。
             其中 `renderer/vendor/we-scene/render/pointer.js` 的 blob 在 `fdfc578a577d0e680a9cfe2cf2e3e825d3cd2372`
             （1.3.23）与 `b61e891` 上**相同**（`c3ddfe91372c7006123ed6f374443625516bc322`）
             ⇒ §14.2 第 1 行的整文件照抄对两个版本都成立。
  SPDX:      MIT
  Local copy: `demo/LICENSE-webwallgl-MIT.txt`（MIT 全文，随仓库；与 §6.2 逐字相同）
  Ledger:    `docs/COPYING-RULES.md` §4, entry **#12** (2026-09-20)
  门禁:      `tests/pointer-trail-copy-test.mjs`（44 断言 + RED-IF-REVERTED）

### 14.1 这一节为什么存在（纪律变更，必须显式记账）

本仓库此前对 webwallgl 的纪律是**只引行为结论、不复制代码**（见 §8 / §10）：
实现按规格独立书写，引用处以 `file:line` 标注行为来源。§6（FXAA shader）与 §9（HLSL→GLSL 翻译器）
是两处既有的、**逐字**的例外，且都只涉及单个不依赖运行时状态的单元。

**P-136 改变了这条纪律**，依据是用户的直接指示（逐字）：

> 「你直接把 oneincase 跟鼠标尾迹有关的代码，你看看直接复制过来就算了 ——
> 你给的这几张图片看不到实质性的内容」

即：用户要的是**照抄上游与鼠标尾迹相关的实现**，不是"按行为契约自己重写"。据此本节登记：

> **这份代码是照抄，不是独立实现。** 下列文件里的相应代码块来自上游 oneincase/webwallgl
> （MIT © 2026 oneincase），**保留上游原有注释**，仅按 §14.3 的对照表做了**机械改写与本地接线**；
> 凡做了适配的行都在源码注释里逐行标出，没有一处是"照行为重写"。

### 14.2 复制了哪些文件 / 函数、行号范围，落到哪里

| # | 上游 `file:line` | 上游单元 | 落到我们的 | 处置 |
|---|---|---|---|---|
| 1 | `renderer/vendor/we-scene/render/pointer.js:1-320` | 整文件（`createPointerSource`：u/v 归一、screenX/Y、`wx/wy`、`lastU/V/…` 帧快照、`pushExternal`、`pushExternalLeave`、`beginFrame`、`syncWorld`、`normalizedDelta`、`dispose`） | **新文件** `core/we-pointer-source.mjs` | **逐字节照抄**（文件头加本仓库横幅，正文一个字节未改） |
| 2 | `renderer/vendor/we-scene/render/particles.js:663-672` | `syncLayerTransform()` 的 originX/Y/Z、scaleX/Y、angleZ | `core/we-particle-pointer.mjs` → `syncLayerTransform()` | 除 **angleZ 的单位**（上游是度、本仓库 scene.json 是弧度，见 §14.3 A-1）外逐字 |
| 3 | `renderer/vendor/we-scene/render/particles.js:686-697` | `setPointer(worldX, worldY)`（世界 → 局部：减 origin、反旋转、除 scale） | 同上 → `setPointer()` | 逐字（`this.` → `sys.`；落点字段 `this.pointer` → `sys.pointerLocal`，见 §14.3 B-1） |
| 4 | `renderer/vendor/we-scene/render/particles.js:830-851` | 发射期 `mapAround` 块（绕控制点按 `count` 等分圆轮流投放 + 作者初速） | 同上 → `mapSequenceAroundControlPoint()` | 位置段（830-845）逐字（仅把 `p.x = …` 赋值形改成返回值）；初速段（846-850）**未照抄**，见 §14.3 C-1 |
| 5 | `renderer/vendor/we-scene/render/particles.js:1010-1024` | `vortex` 块（切向加速 = `(−dy, dx)/dist · speed · dt`，圆心 = `_cpPos`） | 同上 → `vortexSwirl()` | 逐字（仅把 `p.vx += …` 改成返回值）；上游 1019-1020 的音频门控由本仓库算子层的 `audioK` 承担 |
| 6 | `renderer/vendor/we-scene/render/particles.js:1154-1163` | `_cpPos(id)`（控制点当前位置；`lockToPointer` ⇒ `pointer + cp.offset`，否则 `cp.offset`） | 同上 → `cpPos()` / `cpWorld()` | 逐字（`this.` → `sys.`、`this.controlPoints` → `sys.localControlPoints`） |
| 7 | `renderer/src/scene-mount.ts:655-676` | 建实例时创建**单一**指针源（`createPointerSource` + `viewport`） | `core/we-scene-bundle.js`（`__ptrSource`） | 接线照抄；`target` 传假对象关掉上游自己的 DOM 监听，理由见 §14.3 D-1 |
| 8 | `renderer/src/scene-mount.ts:1670-1676` | **每帧**在 `advance()` 前把活指针推进粒子系统（`ps.setPointer(wx, py)`），指针**从不参与**构造 | `core/we-scene-bundle.js`（`pushPointerFrame(sys, __ptrNow)`）+ `core/we-particle-pointer.mjs` → `pushPointerFrame()` | 语义照抄（这一段是"尾迹能不能看见"的成因，见 §14.4） |

**没有复制的东西**（任务纪律：不复制与鼠标尾迹无关的大段）：上游 `particles.js` 的其余约 1400 行
（shader 装配、rope/ropetrail 几何、材质/贴图、`_step`、其余 40 余个 operator/initializer）、
`renderer/src/**` 的其余部分（`shell.ts` / `web.ts` / `quality.ts` / `main.ts` 等）、
`pointer.js` 之外的任何 `renderer/vendor/**` 文件。`packages/we-core/` 下**没有**放入任何上游代码
（那个包要按 MIT 独立分发，纪律不同 —— 见 §4B）。

### 14.3 逐行「照抄 / 适配」对照表（改了什么必须写在这里）

| 记号 | 位置 | 上游原文 | 我们的写法 | 为什么 |
|---|---|---|---|---|
| A-1 | `particles.js:672` | `this.angleZ = ((la[2] \|\| 0) * Math.PI) / 180` | `sys.angleZ = -(la[2] \|\| 0)` | 上游拿到的 `layer.angles` 是**度**；本仓库 `scene.json` 的 `angles` 是**弧度**（①(P-21-ATTACH) 语料实测 π/π/2，bundle 直收、不再 ×π/180）。符号取成与 `spawnParticle` 的 `cos(-angle)` 同一手性 |
| B-1 | `particles.js:692-695` | `this.pointer = { x: …, y: … }` | `sys.pointerLocal = { … }` | `sys.pointer` 已被 `tests/pointer-leave-test.mjs` 钉成「**世界设计坐标**（y 向下）」；改用上游的局部空间会让 A1b/A2b/A3b/A3c/P4b 五条断言变红。按任务纪律「上游行为与既有断言冲突 ⇒ 先报告、不擅自改断言」，两条语义并存（冲突台账见 P-136） |
| B-2 | `particles.js:1156/1159` | `this.controlPoints` / `this.pointer` | `sys.localControlPoints` / `sys.pointerLocal` | 同 B-1；`localControlPoints` 是专供照抄单元的上游形状副本（本仓库的 `controlPoints` 是 raw def 数组，别处按作者空间直接读，不能就地改） |
| C-1 | `particles.js:847` | `const k = Math.random()` | 调用方仍吃系统自己的 `rng()` | 上游这里用**非确定性**的 `Math.random()`，与本仓库"每次渲染可复现、门禁逐位比对两次运行"的要求冲突。**位置投放（830-845）不含随机数，逐字照抄不受影响**；只有"三轴共用同一个随机数"的算式被保留 |
| D-1 | `pointer.js:199-210` | `createPointerSource` 自行挂 `mousemove/mousedown/mouseup/blur` + `document.mouseleave` | 传 `target: { __noDom: true }`，DOM 监听仍由 bundle 的 `__hookPointer()` 装 | 上游"离开"只清按键、保留位置与 `has`（`pushExternalLeave` 的文档语义）；本仓库 P-118/P-121 钉的是「离开 ⇒ 无指针 ⇒ 停发」，且断言点名要 `pointermove/pointerdown/pointerleave/pointerout` 四个画布监听。两套语义**冲突**，故只取上游源的状态容器与 `syncWorld`/`beginFrame`，不取它的监听策略（冲突台账见 P-136） |
| E-1 | `particles.js:1010-1024` | `k = clamp((d−inner)/(outer−inner))` | 同一式子（照抄）；本仓库 `?pops=legacy` 分支仍是旧的 wer-ref 口径 `(d−inner)/(outer−inner+0.1)` | 官方档改成上游口径后，`③-c-1` 的容差 0.5 覆盖 0.299 的差（实测 150.00 vs 150.30 px/s²），既有断言不回归 |

### 14.4 照抄之后**数字**变了什么（这就是"为什么现在能看见了"）

上游把指针当**每帧推进的活输入**（§14.2 第 8 行），指针因此**从不进入**粒子系统的构造/缓存签名，
系统只在时间轴上**增量**前进 —— "光标走过的路径"被留在已存活粒子的坐标里，那就是尾迹。

本仓库此前（P-69 起）把指针坐标写进了粒子缓存签名 `__sig`：指针一动签名就变 ⇒ **每帧**整系统从
`t=0` 重放，且重放全程只用**当前**这一个坐标 ⇒ 历史被抹平，花瓣永远糊在光标上。真包
`dd/3554161528` `objects[27]` = id 389 `cherry blossoms on cursor`
（`particles/workshop/2093672045/Cherry_Blossoms_2.json`）在指针右移 40px/帧 × 30 帧下实测：

| 指标 | 改前（指针进签名） | 改后（照抄上游） | 上游同参对拍 |
|---|---|---|---|
| 顶点流世界包围盒 x 跨度 | **67 px** | **1175 px** | 1149 px |
| 存活粒子距指针最远 | **39.6 px** | **1164.3 px** | 1149 px |
| 每帧仿真步数 | **400**（全历史重放） | **1** | — |
| 每帧粒子更新次数 | ≈ **61,000** | **0**（增量） | — |
| 顶点流 u 跨度（帧 UV） | 1/13 | 1/13（未回归） | — |

数值出处：`tests/pointer-trail-copy-test.mjs`（mock-GL 顶点流 + 上游 `ParticleSystem` 同参对拍）。

## 15. webwallgl  (MIT © oneincase) — **P-144: 粒子 `children`（子系 / 拖尾）三块照抄 + 一处语义移植**

  Upstream:  https://github.com/oneincase/webwallgl
  Licence:   MIT
  Copyright: Copyright (c) 2026 oneincase <462534624@qq.com>
  Commit:    `b61e8910ae0a176288aed99ce9a93a13ea07df57` —— 本机 checkout
             `references/vendor-ref/webwallgl`（仓库外）的 HEAD，**与 §14 同一个 commit**，
             本节所有 `file:line` 都以它为准（写本节时已逐条 `awk`/`grep -n` 回读核对，
             不是照抄 §14 的引注）。
  SPDX:      MIT
  Local copy: `demo/LICENSE-webwallgl-MIT.txt`（MIT 全文，随仓库；与 §6.2/§14 逐字相同）
  Ledger:    `docs/COPYING-RULES.md` §4, entry **#13**（2026-09-19）
  门禁:      `tests/particle-children-test.mjs`（61 断言 + 6 组 RED-IF-REVERTED；其中 ⑧-a/⑧-b
             就是"这一节与 §4 #13 台账必须存在"的机器断言）

### 15.1 这一节为什么存在

§14.1 记录的**纪律变更**（用户直接指示"直接复制过来就算了"，而不是"按行为契约自己重写"）
在 P-144 继续适用：用户对粒子 `children`（子系/拖尾）家族给的是同一类指示 ——
**照上游实现接上**，因为这条是语料最大单项（76 个父层 / 21 个包 / 149 条子系），
按行为重写一遍的收益远小于"接线正确"的收益。

据此本节登记：

> **这三块是照抄，不是独立实现。** 下列函数来自上游 oneincase/webwallgl
> （MIT © 2026 oneincase），**保留上游原有注释**，仅按 §15.3 的对照表做了**机械改写**
> （`this.` → `sys.`、成员方法 → 自由函数）；凡做了语义适配的行都在源码注释里逐行标出。
> 上游那个 TypeScript 的**接线层**（`scene-mount.ts`）没有照抄代码，只移植了它的
> **结构与语义**（见 §15.4），实现是本仓库自己写的。

### 15.2 复制了哪些函数（逐字，上游 JS → 本仓库 JS）

| # | 上游 `file:line` | 上游单元 | 落到我们的 | 处置 |
|---|---|---|---|---|
| 1 | `renderer/vendor/we-scene/render/particles.js:698-707` | `attachFollow(parent, mode, offset)`（子级挂到父系统：记父/模式/offset，并**立刻** `_syncFollow()` 一次） | `core/we-particle-pointer.mjs` → `attachFollow()` | **逐字**：`this.` → `sys.`、`this._syncFollow()` → `syncFollow(sys)`。上游那句"否则首帧 …Matrix 33 列叠成一坨"的注释**保留** |
| 2 | `renderer/vendor/we-scene/render/particles.js:709-713` | `leaderParticle()`（环形缓冲里第一个 `alive` 槽） | 同上 → `leaderParticle()` | 循环体**逐字**；只有"池从哪来"改了：上游 `this.pool`（定长环形缓冲）→ 本仓库 `sys.particles`（紧凑数组，死亡即 `splice`）⇒ 返回"最早出生的活粒子"而非"环里第一个活槽"，见 §15.3 H-1 |
| 3 | `renderer/vendor/we-scene/render/particles.js:724-743` | `_syncFollow()`（`eventfollow` 跟父粒子、否则跟父系统 origin；`children.origin` 走 `localToWorld`） | 同上 → `syncFollow()` | **逐字**（`this.` → `sys.`、`parent.leaderParticle()` → `leaderParticle(parent)`、`parent.localToWorld(x,y)` → `localToWorld(parent,[x,y,z])`），**但 `mode === 'particle'` 那一行有一处必要的坐标口径适配**，见 §15.3 I-2；上游"children.origin 是父系统局部坐标，必须走 localToWorld"与"2974757317 层 scale=1.5、43 列 × 60px"两段注释**保留** |

**没有复制的东西**（与 §14 同一纪律：不复制与本项无关的大段）：上游 `particles.js` 的其余约 1900 行
（`spawn`/`_step`/rope·ropetrail 几何/材质贴图/其余 40 余个 operator·initializer/渲染装配）、
`renderer/vendor/**` 的其余任何文件、`renderer/src/**` 的任何一行代码。本仓库的
`syncFollowOrigin()` / `spawnParticleAt()` / `prepareParticleChildSys()` / `renderParticleChildren()` /
`particleChildAnchorWorld()` / `applyChildControlPointBase()` / `parseParticleChildren()` 全部是
**本仓库自己写的**（`syncFollowOrigin` 的注释里也明写"**不是照抄**"）。`packages/we-core/` 里
**没有**放入任何上游代码。

### 15.3 逐行「照抄 / 适配」对照表（改了什么必须写在这里）

| 记号 | 位置 | 上游原文 | 我们的写法 | 为什么 |
|---|---|---|---|---|
| G-1 | `particles.js:701-707` | `this._followParent` / `this._followMode` / `this._followOffset` / `this._syncFollow()` | `sys._followParent` / `sys._followMode` / `sys._followOffset` / `syncFollow(sys)` | 纯机械改写：本仓库的粒子系统是**普通对象**不是 class，方法一律自由函数（与 §14.2 第 2/3/6 行同一套改法） |
| H-1 | `particles.js:710-712` | `const pool = this.pool` | `const pool = sys.particles \|\| []` | 上游是**定长环形缓冲**（槽位带 `alive`），本仓库是**紧凑数组**（死亡即 `splice`，数组里全是活的）。循环体一字未动，但语义从"环里第一个活槽"变成"**最早出生的活粒子**"（发射序） |
| I-1 | `particles.js:730-736` | `const w = parent.localToWorld(host.x + off[0], host.y + off[1])` 后直接写 `this.originX/Y = w[0]/w[1]` | 同一句照抄，**另外**由本仓库独有的适配层 `syncFollowOrigin(sys)` 把 `originX/Y/Z` 镜像进 `sys.origin[]` 并重跑 `syncLayerTransform` | 本仓库每颗粒子的 `pos` 在**出生那一刻**就写成绝对世界坐标（`spawnParticle`：`wx = sys.origin[0] + …`），模拟原点是数组 `sys.origin` 而不是上游的 `originX/originY`。照抄块只写 `originX/Y` ⇒ 必须再镜像一次，否则原点"写了个没人读的字段"。两种口径在"原点不动"时**逐位等价**，在 `eventfollow` 下观感同构（新粒子在新位置出生、老粒子留在原地 = 拖尾） |
| I-2 | `particles.js:733-735` | `const host = parent.leaderParticle()` → `parent.localToWorld(host.x + off[0], host.y + off[1])` | `sys.originX = host.pos[0] + (w[0] − parent.originX)`；`sys.originY = host.pos[1] + (w[1] − parent.originY)`（`w = localToWorld(parent, off)`） | **父粒子坐标口径**：上游 `host.x/host.y` 是**父系局部**坐标（渲染期才乘父系变换），本仓库 `p.pos` 出生时就是**绝对世界**坐标。两边逐字同构的写法是 `localToWorld(parent, host_local + off)`；把 `host_local` 换成世界坐标后等价于 `host_world + (localToWorld(parent, off) − parent.origin)` ⇒ 只多这一处减法（去掉 origin 平移、只留 R·S 的偏移向量），算式与上游一致 |
| I-3 | `particles.js:724-743` | 从 `this` 读 `_followParent`/`_followMode`/`_followOffset` | 从 `sys` 读同名字段 | 同 G-1 |

> 三块照抄的落点是**同一个文件** `core/we-particle-pointer.mjs`，紧跟在 §14 照抄的块 A–F 之后
> （块号 **G / H / I**）；该文件头部的"来源行号表"已同步登记这三块，并写明 G-1/H-1/I-1/I-2 四条适配。

### 15.4 只移植**结构/语义**、未复制代码的上游部分（TypeScript 接线层）

上游 `renderer/src/scene-mount.ts:1449-1559` 的 `buildParticleSystem(...)` 是"递归建子系"的接线层。
**它不是 JS、也没有被逐行翻译**；本仓库只在 `core/we-scene-bundle.js` 的 `renderParticleChildren()` 里
对齐了它的**三条语义**，并在源码注释里逐条 `file:line` 标注：

| 语义 | 上游 `file:line` | 我们的落点 | 处置 |
|---|---|---|---|
| 嵌套深度守卫 | `scene-mount.ts:1458` `if (depth > 3) return null;` | `renderParticleChildren()` 的 `depth >= 3` 分支（记 `partStat.children.depthCapped` 并打一行日志） | 语义相同（数值上限 3 一致）；实现自写（本仓库不返回 null 而是"本层不再展开、其余照画"） |
| `followMode` 判定 | `scene-mount.ts:1511-1515`：`eventfollow` ⇒ `'particle'`；缺失或 `static` ⇒ `'origin'`；其余 ⇒ `null` | `prepareParticleChildSys()`：`eventfollow` 走 `attachFollow(child, parent, 'particle', spec.origin)`；`static`/事件类的锚点在建层时算一次 | 语义相同。⚠ 上游把 `eventspawn`/`eventdeath` 归到 `followMode = null`（**不挂父**）；本仓库这两类**也不挂**（只在父粒子事件位置各吐一发），一致 |
| 子系图层变换的合成 | `scene-mount.ts:1524-1541`：`scale` 逐轴相乘、`angles` 逐轴相加、`origin` 走 `ps.localToWorld(...)` | `renderParticleChildren()` 里 `childLayer` 的 `scale`/`angles`/`origin`（origin 由 `particleChildAnchorWorld()` 走 `localToWorld`） | 语义相同、实现自写（我们是"伪层 + 复用父层那条绘制通路"，上游是"建好再挂"） |
| 子系继承父层 `instanceoverride` | `scene-mount.ts:1542-1548`（`ch.instanceoverride \|\| override`） | `childLayer.instanceoverride = spec.instanceoverride \|\| layer.instanceoverride \|\| null` | 语义相同 |

### 15.5 照抄 + 接线之后**数字**变了什么

真包 `dd/3554161528` `objects[22]` = id 4569「萤火虫」（`children: [{type:"eventfollow",
name:"particles/presets/firefliestrail.json", maxcount:20, scale:"1.5 1.5 1"}]`），mock-GL 忠实顶点流，
`t0=20s` 起 240 帧：

| 指标 | 改前（`?children=legacy`） | 改后（默认 official） |
|---|---|---|
| 子系存活粒子数 | **0**（子系系统根本不建，`-1`） | **7** |
| 子系顶点 quad 数 | **0** | **7** |
| 每帧粒子更新次数 / 仿真步数 | 5 / 1 | **12 / 2** |
| 父系顶点流 sha256 | `f8634777fcd88f67…` | `f8634777fcd88f67…`（**两档相同**） |
| 整帧顶点流 sha256 | `608425553b440470…` | `3d820f0a0e130de0…` |
| 子粒子到最近父粒子的距离 | — | n=7，min 4.2 / p50 19.9 / max 39.9 px（贴着父粒子飞 = 拖尾） |

全语料同族扫描（98 个包容器 / 232 个粒子层 / 149 条子系）：改前 **0 条**产出
（`git show HEAD:core/we-scene-bundle.js` 里 `children` 字面出现 **0** 次 ⇒ 功能不存在），
改后 **139 条**产出（`static` 74/74、`eventfollow` 37/37、`eventspawn` 8/8、`eventdeath` 20/30），
剩下 10 条**全是作者自己写了 `probability: 0`**（`fireworkshitdistort`），不产出才是正确行为。

数值出处：`tests/particle-children-test.mjs`（④ 真包 mock-GL + ⑥ 同族扫描 + ⑤ 逐位回退 + ⑦ 6 组变异自证）。


## 16. webwallgl  (MIT © oneincase) — **P-149: 粒子 `overbright` 按规格独立实现**

**这一节声明的是"按规格独立实现"，不是"照抄"。** 上游 `oneincase/webwallgl`
(`https://github.com/oneincase/webwallgl`, MIT © 2026 oneincase) 的 `overbright` 契约只有 **4 行**，
本仓库**没有**复制其文件、注释、命名或常量组织，只按下面这张**逐行引文表**（取值契约 `particles.js:590-593`、消费点 `particles.js:1300`）重写了这一条语义；
落点代码里的每一行都是自写（函数名 `particleOverbrightFactor`、参数形态、兜底顺序、
`?overbright=legacy` 开关、`particleStats.overbright` 记账均为本仓库独有）。
MIT 全文随仓见 **§6.2**（同一上游，不重复粘贴）。

### 16.1 上游出处（可 `git show` 原样复核）

| 引文（上游原样，MIT） | `file:line` | 上文/下文 |
|---|---|---|
| `const rawOb = cv ? cv.ui_editor_properties_overbright : undefined` | `renderer/vendor/we-scene/render/particles.js:590` | `cv = pass && pass.constantshadervalues`（同文件 `:589`） |
| `const ob = Number(rawOb)` | `:591` | — |
| `this.overbright = rawOb == null || !Number.isFinite(ob) ? 1 : Math.max(0, ob)` | `:592` | 同段注释明写"`Number(null)=0`：键缺失时必须显式落缺省 1" |
| `const bright = (this._ov.brightness || 1) * (this.overbright ?? 1)` | `:1300` | `_ov` = `instanceoverride` |
| `data[k++] = (a.r + b.r) * 0.5 * bright`（三通道同式；alpha `(a.alpha + b.alpha) * 0.5` **不乘**） | `:1341-1344` | rope 分支；证明"只乘 RGB、不动 alpha" |

commit：**`19c5fab`**（上游 1.3.x 的 overbright 修复提交；行号用 `git show 19c5fab:<path> | grep -n` 复核）。
本地检出 `references/vendor-ref/webwallgl/`（**在仓库外、不入发布物**，见 `:611-612`）。

### 16.2 落点（本仓库，全部自写）

| 落点 | 内容 | 与上游的关系 |
|---|---|---|
| `core/we-scene-bundle.js::particleOverbrightFactor(pass)`（导出，纯函数） | 取值契约：`raw == null \|\| !Number.isFinite(n)` ⇒ 1、负数 ⇒ `Math.max(0,·)`、**不设上界** | **语义相同、实现自写**（上游写在实例构造里，我们抽成可直测的纯函数；上游没有 `legacy` 档与记账） |
| `core/we-scene-bundle.js` 粒子色段（`vis.push([p, sz, a, colorR, …])` 之前） | `obf` 取用 + 兜底（非有限数 ⇒ 1、负数 ⇒ 0）；三通道 `* obf`（钳位只作用于逐粒子基色） | **语义相同、实现自写**（上游是逐实例 `bright` 乘在 r/g/b 上；我们的两条上屏路径 = `u_Color` 上提 + `a_Color` 顶点缓冲） |
| `core/we-scene-bundle.js::renderParticleChildren` 的 `childLayer` | `__particleOverbright: res.overbright`（子系吃自己的材质因子、不继承父层） | 上游在实例构造时各自取自己的 pass ⇒ **语义相同** |
| `demo.html` 粒子材质段 + `resolveChildDefs` | 两处调 `lib.particleOverbrightFactor(pass)` | 上游在 `renderer/src/**` 的解析层取 pass ⇒ 语义相同，落点不同 |
| `?overbright=legacy` | 恒 1 = 逐位回到"键被忽略"的旧画面 | **本仓库独有**（上游无回退开关） |

### 16.3 与上游的**行为差异**（必须写明）

1. **上界**：上游没有上界，我们也没有（语料有 `5×`）。唯一的钳位 `Math.min(1,·)` 只作用于
   **逐粒子基色**（本仓库 P-126 的既有口径），因子在钳之后乘 ⇒ `overbright=5` 真的出 5。
2. **`instanceoverride.brightness` 未接**：上游 `:1300` 是 `brightness × overbright` 两个因子相乘，
   本仓库 `instanceoverride` 没有 `brightness` 字段 ⇒ 本项只落 `overbright`，**未**顺手扩字段。
3. **脏值口径**：上游 `Number.isFinite(ob) ? Math.max(0, ob) : 1` 与我们逐字同义（`"2"` 这类数值字符串两边都吃）。
