feat: NTE gear optimizer web app
Client-side optimizer for Neverness to Everness console builds. Imports a gear export from nte-history-exporter, keeps it in IndexedDB, and solves for the best module placement per character or across a team. - domain/: pure ports of the Python research scripts (shapes, cartridges, board tiling, stats, scoring, set bonuses, Arc effects, sheet prediction). No DOM, no storage imports, so they stay worker-safe and Node-testable. - solver/: single-character solve with branch and bound, plus a three-phase team solve (portfolio, leximin, column generation) over disjoint item sets. Runs in a Worker when one can be constructed, inline otherwise. - db/: three storage tiers selected by use rather than feature detection, fail-closed import validation, and undo that restores both sides of a steal. - ui/: React 19 views for items, characters and teams; the board is drawn as the deliverable rather than a score. - Two build targets: a hosted bundle and a single self-contained nte.html with no external requests. Values the model cannot measure are reported as unavailable rather than guessed: cartridge set bonuses, the level 80 base-stat multiplier, and the distinction between "optimal for this packing" and globally optimal. 145 tests, tsc clean, both build targets clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
commit
5ce255fa6f
157 changed files with 92600 additions and 0 deletions
29
tests/router.test.ts
Normal file
29
tests/router.test.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { hashFor, parseHash, TABS } from "../src/ui/router.ts";
|
||||
|
||||
describe("hash routing", () => {
|
||||
it("round-trips every tab", () => {
|
||||
for (const tab of TABS) {
|
||||
expect(parseHash(hashFor(tab))).toEqual({ tab, id: null });
|
||||
}
|
||||
});
|
||||
|
||||
it("carries a character id, encoded", () => {
|
||||
const hash = hashFor("characters", "Chiichan073");
|
||||
expect(parseHash(hash)).toEqual({ tab: "characters", id: "Chiichan073" });
|
||||
// A name with a slash must not read as a third path segment.
|
||||
expect(parseHash(hashFor("characters", "a/b"))).toEqual({ tab: "characters", id: "a/b" });
|
||||
});
|
||||
|
||||
it("falls back to characters for anything unrecognised", () => {
|
||||
expect(parseHash("").tab).toBe("characters");
|
||||
expect(parseHash("#/nonsense").tab).toBe("characters");
|
||||
expect(parseHash("#").tab).toBe("characters");
|
||||
});
|
||||
|
||||
it("accepts a hash with or without the leading slash", () => {
|
||||
expect(parseHash("#team").tab).toBe("team");
|
||||
expect(parseHash("#/team").tab).toBe("team");
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue