Phase 11: real block textures for 2D tiles

Layers texture-averaged colors on top of palette.rs's hand-picked table as a
fallback (color_for_textured), not a hard replacement — biome-tinted/animated
blocks (grass top, leaves, water, lava) deliberately keep the hand-picked
color since averaging their raw jar textures would be wrong, not just
imprecise.

Vanilla: worker/src/textures.rs downloads Mojang's official client jar
directly from launchermeta/piston-meta/piston-data (same endpoints the real
launcher uses, gated behind ACCEPT_MINECRAFT_EULA=true, off by default,
mirrors BlueMap's accept-download) and averages every block texture, cached
to disk so it's not re-downloaded every restart.

Modded: ingests block_registry/block_textures messages from the mod (new
Postgres tables + MinIO storage in api/src/textures.ts) — sent once per
connection over the existing WS gateway.

texturepacks/<name>/ (Dynmap-style flat PNGs) lets an operator override the
vanilla defaults worker-wide via TEXTURE_PACK. A per-server texturePack
admin column/API exists for the same purpose, but render-time per-server
resolution (of both the admin selection and the ingested modded textures) is
explicitly deferred — the worker still applies one process-wide palette; true
per-server resolution needs the render pipeline to thread a server-scoped
palette through the batch/GPU-dispatch path, judged too big a change for this
phase. Documented in README.

Docker was unavailable in this dev environment for the usual integration-test
verification; bunx tsc --noEmit was used as a fallback static check instead
(clean except 2 pre-existing unrelated errors in markers.test.ts).
This commit is contained in:
2026-08-09 23:21:31 +02:00
parent 1a0ccd8b17
commit f8216b6e77
22 changed files with 1086 additions and 27 deletions
+45
View File
@@ -58,6 +58,51 @@ stack isn't on the same LAN. `MINIO_SECRET_KEY` is a real credential and is deli
committed — set it in an untracked `./api/.env` / `./worker/.env` (docker-compose layers those on
top of the tracked `.env.example`, see `docker-compose.yml`).
### Real block textures (Phase 11)
`worker/src/palette.rs`'s hand-picked color table is now a *fallback*, not the only source of
2D-tile colors. Set `ACCEPT_MINECRAFT_EULA=true` (`worker/.env.example`, off by default — mirrors
BlueMap's `accept-download`) and the worker downloads Minecraft's official client jar directly
from Mojang's own public `launchermeta`/`piston-meta`/`piston-data` endpoints (same source the
real launcher uses — no redistribution, so no licensing issue) on first startup, averages every
`assets/minecraft/textures/block(s)/*.png` into a representative color, and caches the result to
disk (`TEXTURE_CACHE_DIR`, default `./cache`) so it isn't re-downloaded every restart.
`MC_TEXTURE_VERSION` (default `1.12.2`) picks which version's jar to pull from — this project's
priority targets (1.7.10/1.12.2) share the pre-1.13 `textures/blocks/` (plural) naming, which
`worker/src/textures.rs` checks alongside the modern `textures/block/` path.
A handful of blocks (grass block top, leaves, water, lava) are deliberately *excluded* from the
texture-averaged path (`worker/src/block_names.rs`'s doc comment) and keep their hand-picked
color: their real textures are either biome-tinted at runtime (grayscale in the raw file) or
animated/transparent frame strips, so averaging the raw asset would produce a wrong color, not
just an approximate one.
**`texturepacks/<name>/`** (flat `*.png` files, mirrors Dynmap) lets an operator override the
downloaded defaults — set `TEXTURE_PACK=<name>` and its colors are layered on top of the vanilla
palette at startup. Each server also has an admin-configurable `texturePack` field (`/admin`,
`servers.texture_pack` — same shape as `waypointFormat`) recording *which* pack an operator wants
per server — **but render-time application is currently worker-wide only, via `TEXTURE_PACK`, not
yet resolved per-server from that column.** True per-server resolution needs the render pipeline
to thread a server-scoped palette through `main.rs`'s batch loop instead of one process-wide
`OnceLock` (`worker/src/render/mod.rs`) — a real architectural change, deferred rather than rushed.
**Modded blocks**: Forge doesn't split client/server jars, so a modded server's own classpath
already has every loaded mod's texture assets, just unused server-side. The `forge-1_12_2` mod
leaf (Enigmatica 2, this project's primary target) extracts them once at startup
(`BlockAssetExtractor`, best-effort: guesses each block's texture by its registry-name path
segment, not a real blockstate/model JSON resolution — that's Phase 12's job) and ships two new
WS messages after connecting: `block_registry` (numeric id -> registry name, needed since a
numeric `blockId` alone is meaningless without the mod list that assigned it) and `block_textures`
(the extracted PNGs, batched). The api stores both (`api/src/textures.ts`: registry rows in a new
`block_registry` table, texture PNGs in the shared MinIO bucket with pointer rows in
`block_textures`) — **but, like the `texturepacks/` case above, nothing reads these tables into
the render pipeline yet.** This is a deliberate two-step boundary: "ingest and store" (done, real,
tested) vs. "resolve into a per-server palette at render time" (the same deferred piece as
`texturepacks/` above, and naturally solved together). `forge-1_7_10`/`neoforge-26_1` don't
implement extraction yet either — `BackendConnection#sendBlockRegistry`/`#sendBlockTextures` are
on the shared interface (so any leaf can adopt them later with no protocol change), but only the
1.12.2 leaf calls them so far, matching this phase's Enigmatica-2-focused verification target.
## Running
```