No description
Find a file
goober e922a995ee feat: real artwork, readable stats, and a build you can read at a glance
Six things, all found by using the deployed page.

**The icons were never images.** `everness.info/...` answers 200 with
`content-type: image/webp` for paths that do not exist, returning the SPA's
HTML shell, so all 72 downloaded "icons" were HTML documents named .webp and
every character silently rendered as initials. The assets live on
`api.everness.info/data/assets` instead, and the fetcher now verifies RIFF/WEBP
magic bytes and fails the build rather than warning. 95 files: 23 avatars at
256px, 23 portraits, 49 Arc icons.

**Stats read as the game writes them.** `prune-gamedata` harvests every
`{ id_stats, name, bShowPercent }` pair out of the mirror, so "CRIT Rate" is
the game's own wording rather than someone's typing, and values render as 12.5%
instead of 0.125. Ids the mirror never names are filled in by
`domain/statlabels.ts`, each with its reason and marked derived in the UI.
Two of everness's own percent flags are overridden: it marks `HealUp` and
`DamageUpNatureBase` flat while shipping them as fractions in exactly the range
of the bonuses it does mark as percentages, which printed "Healing Bonus 0.1".

**The build is legible without hovering.** Each piece carries its legend number
and colour, and the list beside the board states shape, type, level and every
stat outright; the cartridge sits in its own box below, since it occupies no
cells. Hover-only tooltips meant the information existed but could not be
scanned or compared. Shapes are drawn from the same cell lists the solver packs
with, so an icon cannot drift from its geometry.

**Owner groups have numbers, not hashes.** "group 0000000088" told the player
nothing; groups are now numbered stably and an unnamed one links to where it
gets named.

Also: import/export moved to its own Data tab with a library summary and the
to-do list, and the layout is denser throughout.

152 tests, tsc clean, both build targets clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 13:35:25 +03:00
data-src feat: NTE gear optimizer web app 2026-08-21 11:38:17 +03:00
public/img feat: real artwork, readable stats, and a build you can read at a glance 2026-08-21 13:35:25 +03:00
src feat: real artwork, readable stats, and a build you can read at a glance 2026-08-21 13:35:25 +03:00
tests feat: real artwork, readable stats, and a build you can read at a glance 2026-08-21 13:35:25 +03:00
tools feat: real artwork, readable stats, and a build you can read at a glance 2026-08-21 13:35:25 +03:00
.gitignore feat: NTE gear optimizer web app 2026-08-21 11:38:17 +03:00
.nvmrc feat: NTE gear optimizer web app 2026-08-21 11:38:17 +03:00
index.html fix: reachable undo, nameable groups, and no favicon request 2026-08-21 12:30:56 +03:00
package-lock.json feat: NTE gear optimizer web app 2026-08-21 11:38:17 +03:00
package.json chore: one-command deploy to nte.goober.moe 2026-08-21 12:43:28 +03:00
README.md chore: one-command deploy to nte.goober.moe 2026-08-21 12:43:28 +03:00
tsconfig.app.json feat: NTE gear optimizer web app 2026-08-21 11:38:17 +03:00
tsconfig.json feat: NTE gear optimizer web app 2026-08-21 11:38:17 +03:00
tsconfig.node.json feat: NTE gear optimizer web app 2026-08-21 11:38:17 +03:00
vite.config.ts feat: NTE gear optimizer web app 2026-08-21 11:38:17 +03:00
vite.single.config.ts feat: NTE gear optimizer web app 2026-08-21 11:38:17 +03:00
vitest.config.ts feat: NTE gear optimizer web app 2026-08-21 11:38:17 +03:00

nte-optimizer

Gear optimizer for Neverness to Everness. Static site, no server, no account.

It imports the JSON that nte-exporter writes during a normal capture run, and recommends what to equip on whom — drawn onto each character's console grid.

Running it

npm ci
npm run dev            # development
npm run build          # dist/index.html  — the normal build
npm run build:single   # dist/nte.html    — one file, works over file://
npm test

dist/ is committed on purpose. In three years, when npm ci fails on some transitive native module, python3 -m http.server -d dist will still work. The source build is the convenience; the committed output is the artifact.

Layout

path what lives there
data-src/ raw everness mirrors, Arc classifications, guide data. Committed, not shipped.
tools/ build-time scripts. Output goes to src/generated/, also committed.
src/domain/ pure. No DOM, no IndexedDB, no imports from ui/, db/ or state/.
src/solver/ same rule. Chunked generators, so they run in a worker or inline unchanged.
src/db/ schema, migrations, import, persistence adapters.
src/ui/ React. Four tabs and a hash router.

The domain/ and solver/ rule is what makes them worker-safe, Node-testable and diffable against the Python they were ported from. Keep it.

Two things that are easy to get wrong

Ported rules were expensive to derive and break in ways that still look plausible. Before touching anything in domain/, read ~/Apps/nte-research/WIREFORMAT.md and the "facts worth not re-deriving" section of RESUME.md. Builds are 6, 7 or 8 modules and never always 7; the console trait is not always CRIT DMG or Type III; a transposed board renders correctly on a symmetric grid.

The model has measured gaps, and the app is built to show them rather than hide them. Set bonus values are unknown and contribute nothing. The base-stat multiplier has never been read at level 80. A proven build is optimal for its packing and cartridge, not globally. Every one of those is surfaced in the UI on purpose — if you find yourself replacing one with a plausible number, don't.

Degraded modes

Opening dist/nte.html from the filesystem gives the page an opaque origin, so IndexedDB, workers, fetch and module scripts are all unavailable. The app handles this behind two interfaces it never sees through — PersistenceAdapter (IndexedDB → localStorage → memory) and SolverHost (worker → inline) — and shows a banner when storage is not durable. Artwork is not carried in that build.

Deploying

npm run deploy builds both targets and publishes to https://nte.goober.moe.

The homelab has no rsync, so the tree goes over as a tar stream, and the destination is wiped first: a stale content-hashed chunk left behind would be served forever, since /assets/* is cached as immutable. Caddy serves /mnt/docker/appdata/caddy/sites/nte.goober.moe as /srv/nte.goober.moe via a read-only bind mount, which is why a deploy needs no container restart — but adding a new site does, and recreating that container briefly drops every other site behind Caddy.

https://nte.goober.moe/nte.html is the single-file offline build, shipped alongside the app.

dist/ is not in git. The deploy builds from source.