# samples/ — bundled sample scenes (**no real wallpapers**)

> ## This repository distributes **no real wallpapers**
>
> `samples/wallpapers/` — four real Steam Workshop packages (`3327063360`, `3544152633`,
> `3554161528`, `3719111841`; 13 files, 198 MB) and its `MANIFEST.md` — used to be committed here.
> It has been **deleted**: it is third-party artwork and the author asked that it not be packaged
> into the repository. Record: `../PATCHES.md` **P-87**. `samples/` shrank from **198 MB → 130 KB**
> (file bytes: 207 293 667 B → 96 431 B, of which 67 462 B are the generated fixtures).
>
> If you are reading an older checkout that still contains that directory, **do not publish it** —
> and do not re-add it (see "Bring your own corpus" below).

Every file under `samples/` is **generated by [`../tests/make-sample.mjs`](../tests/make-sample.mjs)** from
arithmetic. Nothing here is a Wallpaper Engine Workshop download, a WE bundled default project, a
font, a photo, or any other third-party work — which is the whole point: the real packages on the
development machine are all somebody else's copyrighted artwork and must not ship.

## What ships here

| Path | What it is | Load it with |
|---|---|---|
| `sample-synthetic/scene.pkg` | 33 299 B `PKGV0022` container (5 layers) | `?id=sample-synthetic`, or `?pkgpath=<abs>/samples/sample-synthetic/scene.pkg` |
| `sample-synthetic/project.json` | the workshop-style sibling (`scene.pkg` + `project.json`) | read by the server for `?id=sample-synthetic` (props panel) |
| `sample-synthetic-src/**` | the same 8 files loose (packing fixture) | `node server/pack-dir.mjs samples/sample-synthetic-src /tmp/x.mpkg`, or `/pkgdir?d=<abs>/samples/sample-synthetic-src` |

`?id=sample-synthetic` resolves through the server's scene lookup (`<scene root>/<id>/scene.pkg` →
`<repo>/samples/<id>/scene.pkg`), and `<repo>/samples` is in the default `MPW_ALLOW_DIRS`, so both
`?pkgpath=` and `/pkgdir` work on a fresh checkout with **no environment variables set**.

## Bring your own corpus (the real wallpapers live *outside* the repository)

The author's test corpus (`allwallpaper/dd/<id>/`) is deliberately **not** in the repository, and
neither is anything downloaded from Steam. To render a real wallpaper, point the server at *your*
copy — nothing is fetched for you:

```bash
# 1) get the package yourself (Steam → Wallpaper Engine → Workshop, app 431960), or export an .mpkg
# 2) id-style loading needs a directory per wallpaper: <root>/<id>/scene.pkg (+ optional project.json)
MPW_SCENE_ROOT=/path/to/your/corpus node server/we-scene-demo-server.mjs     # then ?id=<dir name>
# 3) or load one container directly, no corpus layout needed
#    ?pkgpath=/abs/path/scene.pkg   (server-side path; must sit under MPW_ALLOW_DIRS)
#    ?pkgurl=https://…/scene.pkg    (server proxy, bypasses CORS)
#    ?pkgurl=http://127.0.0.1:8899/pkgdir?d=/abs/workshop/dir   (loose directory, packed on the fly)
```

The server prints the effective root at startup
(`[scene] 场景根 MPW_SCENE_ROOT=…`) and, when it does not exist, says so explicitly —
`?id=` then answers 404 instead of pretending an empty directory is a scene.

**Copyright**: every real package is somebody else's artwork (usually a Steam Workshop item).
It is fine to render your own copy locally; it is **not** fine to commit or redistribute it, and
this repository does not.

## Provenance

| Path | Kind | Original author | Source URL | License | Third-party content | Redistributed with permission |
|---|---|---|---|---|---|---|
| `sample-synthetic/scene.pkg` | WE `PKGV0022` container (single-file input for the renderer) | this repo — generated by `tools/make-sample.mjs` | none (generated locally) | repo license (root `LICENSE` — still to be added; `RELEASE-PLAN.md` decision D3 picks it) | **none** | n/a — no permission needed, we own it |
| `sample-synthetic/project.json` | WE workshop metadata (sibling of `scene.pkg`) | this repo — generated | none | repo license | **none** | n/a |
| `sample-synthetic-src/**` | loose source of the same 8 files (`scene.json`, `models/`, `materials/`) | this repo — generated | none | repo license | **none** | n/a |
| `materials/*.tex` (inside both) | WE `TEXV0005`/`TEXI0001`/`TEXB0001` RGBA8888 textures | this repo — procedural pixels (`texBackground`, `texOrb`) | none | repo license | **none** | n/a |

No attribution obligations exist for this sample: there is no third-party authorship in it.
The two `models/util/*.json` files referenced by `scene.json` (`composelayer`, `solidlayer`) are
renderer built-ins (`core/we-scene-bundle.js` `BUILTIN_MODELS`) and are deliberately **not** packaged —
real WE packages do not pack them either.

## Regenerate

```bash
node tests/make-sample.mjs            # writes samples/** — deterministic, same bytes every run
node tests/make-sample.mjs --prod-verify   # + re-read scene.pkg with the PRODUCTION parser (sha256 per entry)
node tests/make-sample.mjs --out /tmp/s    # write somewhere else
```

Deterministic: no timestamps, no randomness, entries sorted by path, fixed JSON formatting.
`scene.pkg` = **33 299 B (32.5 KB)**, sha256 `cceb7b94ead60885df022a317fbe7a1c76760f4512de55c85554474514ba5d16`;
the generated fixtures under `samples/` are **67 462 bytes** of files in total (plus this README).

### Both loading paths have a fixture

* **Single package file** — `?pkgpath=<abs>/samples/sample-synthetic/scene.pkg` or `?pkgurl=<url>`
  (the same bytes can be copied to `<slug>.mpkg`; the container format is identical).
  `<repo>/samples` is part of the default `MPW_ALLOW_DIRS`, so this works with no env vars.
* **Workshop-style directory** — `samples/sample-synthetic/` holds exactly what a real WE workshop
  folder holds: `scene.pkg` + `project.json`. It is reachable as `?id=sample-synthetic` **even when
  a real corpus is configured** (scene lookup = `<scene root>/<id>/scene.pkg` → `<repo>/samples/<id>/scene.pkg`).
* **Directory source** (`/pkgdir` → `server/pack-dir.mjs` `packDir`, loose files packed on the fly) —
  `samples/sample-synthetic-src/` is that fixture:
  `node server/pack-dir.mjs samples/sample-synthetic-src /tmp/x.mpkg` produces bytes **identical** to
  `samples/sample-synthetic/scene.pkg` (both writers sort entries by path and store them raw).

## Without a real corpus — what still works, what degrades

A fresh clone has **no real wallpaper and no WE installation**, and that is supported:

| Thing | Behaviour with no corpus |
|---|---|
| `http://127.0.0.1:8899/` (no `?id=`) | the default id `3554161528` is not bundled any more → the page **says so** and renders the bundled `sample-synthetic` instead (`?id=` explicitly given ⇒ no substitution, it errors) |
| `?id=sample-synthetic` | works (repo-local sample root, see above) |
| `?id=<real id>` / `?pkgpath=` / `?pkgurl=` | works as soon as *you* provide that package (see "Bring your own corpus") |
| props panel, `general.properties` | empty/`null` when no `project.json` can be found (pre-existing graceful path: `visible:{user:{condition:…}}` falls back to visible) |
| `/weassist/…` (WE particle presets / material fallbacks) | skipped; the renderer only uses what is inside the package (missing WE assets no longer 500s the server — fixed in P-87) |
| `bash run-all-tests.sh` | corpus-bound items **SKIP instead of failing** (`media-host`, `props-panel`, `mdla-walk`, `animation-badframe`, `audio-real-pkg`, `jpeg-decode`, and `parity-check` when `reports/` has no device data). Several others still PASS with their real-package assertions simply unreachable (`text-switches`, `canvas-size`, `text-font-fallback`, `camera-pose`, `meshsize`, `hdr-predicate`, `script-tick`, `time-variation`, `fullscreen-recenter`, `package-matrix`, `panel-smoke`…). Every static/syntax/mock-GL item runs normally. **Known pre-existing gap**: `project-json` (P-85) asserts on 6 real packages and fails without them — two of those six (`3326873240`, `3660962877`) were never bundled here, so this predates the wallpaper removal and is *not* caused by it |

## Verify

```bash
node --check tests/make-sample.mjs
node tests/make-sample.mjs --prod-verify   # parsePkg + readPkgEntry, sha256 per entry
node preview.mjs sample-synthetic /tmp/sample.png 256 144   # CPU reference render (256×144)
# ^ with today's hard-coded SCENE_ROOT in preview.mjs, the working form is:
node preview.mjs ../../we-scene-demo/samples/sample-synthetic /tmp/sample.png 256 144
```

Expected: 8 entries, all `flags=0`, every entry's sha256 identical to the loose source file;
both `.tex` decode as `RGBA8888` 80×45 / 56×56; `preview.mjs` prints `drawn layers: 4` and writes
the PNG with no error (the container layer is never drawn; the text layer is drawn as its colour
box because the CPU preview has no text rasteriser).

## What is in the scene

`scene.json` — 5 objects, design canvas 1920×1080 (`general.orthogonalprojection`):

| id | name | kind | notes |
|---|---|---|---|
| 1 | `background` | texture layer (`models/background.json` → `materials/background.tex`) | procedural navy→indigo gradient + glow, 1920×1080 |
| 2 | `motion` | compose group (`models/util/composelayer.json`) | never drawn; children inherit its transform |
| 3 | `accent` | solid colour layer (`models/util/solidlayer.json`, `solid: true`, `color` bound to the `accentColor` property) | animated `origin.c1`: bar sweeps y 780 → 300 → 780 over 150 frames |
| 4 | `orb` | texture layer (`models/orb.json`), `parent: 2` | animated `origin.c0` (x 560 → 1360 → 560, 120 frames) and `scale` (1 → 1.14 → 1); procedural glow sprite |
| 5 | `label` | text layer (no image) | `text`/`pointsize`/`visible` bound to the `labelText`/`labelSize`/`labelVisible` properties declared in `project.json` |

Both animations loop seamlessly (the renderer's `animValueAt` wraps at the last keyframe time).
Proven visually with the CPU reference: `drawn layers: 4`, orb at its t=0 position.

## Conventions and caveats (read before editing the sample)

* **Origin keyframes are y-down.** Static `origin` is stored editor y-up (WE convention) and
  `parseScene` flips it once (`render y = 1080 − authored y`). The renderer, however, applies
  `origin.animation.c1` **without** that flip (`core/we-scene-bundle.js` `compositeLayer`:
  `oy = animValueAt(c1, time)`), so this sample authors `c1` in the flipped space and every
  frame-0 keyframe equals the static value. That keeps `preview.mjs` (static) and the WebGL
  renderer (t=0) pixel-aligned. Change one side only and they diverge.
* **Keyframes replace, they do not add.** `animValueAt` overrides the authored `origin`/`scale`
  completely, so the first keyframe of each channel must equal the authored value.
* **Text layers**: `demo.html` rasterises the real string with canvas (`rasterizeTextLayer`);
  `preview.mjs` draws the layer's colour box instead. The empty `font: ""` deliberately selects the
  platform default font — no font file is shipped.
* **Textures**: one mip level, `TEXB0001`, format 0 (RGBA8888). That is the simplest container both
  decoders accept (`pkg-extract.js` `decodeTex` and the bundle's `parseTex`/`decodeMip0`), and the
  renderer only ever samples mip0.
* **No `preview` key** in `project.json`: this sample ships no preview image, and pointing the key
  at a missing file would be worse than omitting it. Generate one with `preview.mjs` if the gallery
  needs it.
* Layer names avoid the renderer's UI-hiding regex (`applyRenderConfig`, `hideUI`) and no solid
  layer has a parent, so `hideBars` does not hide the accent bar either.
* `animationlayers` / MDLA character animation is intentionally out of scope: this sample has no
  puppet model, only property (`animation`) keyframes.
