Files
EpicNext-Cms/src/lib
openhands 43742e8d99 fix(catalog): decode WebP bundle textures so furniture icons resolve again
Every write path normalises a bundle's texture to WebP Lossless, so the
catalog icon was being read back with a PNG-only decoder. decodePng throws
on anything that is not a PNG, the callers caught that and returned null,
and the user-visible result was "no icon (not in source or bundle)" for
every furniture whose source does not serve a standalone icon.

Measured against the production asset tree, all 18,505 bundles were WebP;
icon extraction succeeded on 0 of them. src/lib/services/imager/
decode-texture.ts keeps PNG on the dependency-free decoder and routes WebP
through sharp, which is already a dependency and already encodes these
textures. extractFurniIconPng and getPetIconPng become async; the five
call sites (upload, clone import, furni import, icon repair and both icon
routes) already awaited their surrounding work.

Same root cause, second bug: the spritesheet frame key. Converters disagree
on packing — some keep a trailing ".png", and some lowercase the whole key
while leaving the bundle name mixed-case, so "LTD_fashionistaf" looks up
frame "LTD_fashionistaf_LTD_fashionistaf_icon_a" and never finds
"ltd_fashionistaf_ltd_fashionistaf_icon_a". Any mixed-case classname could
therefore never match, which is most of the catalogue. findFrame tries the
two exact spellings, then falls back to one case-insensitive pass.
Extraction now succeeds on 18,483 of 18,505 bundles (99.88%); the 22
remainder are data, not code — 9 bundles ship no icon asset, 11 do not
parse.

Third: three catalogue icons exist only as .gif while catalogueIconUrl
hardcoded .png, so the picker offered icons that could only ever 404, and
291 icons that ship as both formats were listed twice. The API now dedupes
per id and the two renderers retry with .gif before falling back to the
placeholder, matching what catalog-image-picker already did. Verified live:
/gamedata/.../icon_1542.png returns 404 while icon_1542.gif returns 200.

Separately, close the last hole in the memory cap. Every script in
package.json routes through scripts/with-memory-cap.sh, but invoking the
builder directly — from a terminal, an IDE or an agent — skipped the
wrapper and ran unbounded, on a host with no swap where the OOM killer
picks its victim across the whole machine. next.config.ts now refuses a
production build that the wrapper has not marked, before anything
allocates. next dev and next start are deliberately unaffected.

README gains a Memory-capped commands section covering the per-script
ceilings, the backends and the ulimit -v trap, and its stale version and
script tables are corrected.
2026-10-11 17:01:37 +02:00
..
2026-07-13 21:57:41 +02:00
2026-08-25 22:26:05 +02:00
2026-07-13 21:57:41 +02:00
2026-07-13 21:57:41 +02:00
2026-07-13 21:57:41 +02:00
2026-08-12 14:34:29 +02:00
2026-07-13 21:57:41 +02:00
2026-07-13 21:57:41 +02:00
2026-07-13 21:57:41 +02:00
2026-07-13 21:57:41 +02:00
2026-07-13 21:57:41 +02:00