Commit graph

353 commits

Author SHA1 Message Date
Erik
29f8bec046 fix(frontend): only show weapon-type chips when item type is Weapons
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 08:48:27 +02:00
Erik
d6fa0fa9b0 feat(frontend): multi-select weapon-type-by-skill filter + auto OD column
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-15 08:44:00 +02:00
Erik
b4bee24773 feat(inventory-go): weapon_types multi-select filter by skill (melee/missile/war/void)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-15 08:41:43 +02:00
Erik
29d400f86a docs: spec + plan for weapon-type-by-skill multi-select filter + OD column
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 08:38:18 +02:00
Erik
d5febaabda build(frontend): deploy weapon OD rating
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 08:23:07 +02:00
Erik
1dd95819c7 feat(frontend): weapon OD rating filter, column, and detail row
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 08:22:54 +02:00
Erik
fb6b54f9bd feat(inventory-go): min_od/max_od search filters + od sort
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-15 08:05:03 +02:00
Erik
3c0a8d9de8 feat(inventory-go): POST /admin/backfill-od recompute endpoint
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-15 08:04:00 +02:00
Erik
e07a9459cd feat(inventory-go): store od_rating at ingest + schema column
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-15 08:03:57 +02:00
Erik
9b2f28d942 fix(inventory-go): add Prodigal Spirit Drinker effect; document dmgMod key substitution; test wand mastery fallback
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-15 08:00:22 +02:00
Erik
99f394d1ef docs(spec): OD — add missing Prodigal Spirit Drinker effect, document dmgMod key substitution
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 07:58:36 +02:00
Erik
f75a236f54 fix(inventory-go): OD variance orientation per UB source; exact SpellInfo change/bonus semantics; missile golden test
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-15 07:46:07 +02:00
Erik
16077633c5 docs: correct OD spec/plan — variance orientation per UB source, SpellInfo change/bonus semantics
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 07:43:26 +02:00
Erik
231e6e799f feat(inventory-go): Mag-Tools OD weapon rating calculation (od.go)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-15 07:39:49 +02:00
Erik
f92d158fd0 docs(plan): weapon OD rating implementation plan
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 07:29:10 +02:00
Erik
10f8d135e5 docs(spec): weapon OD rating — compute at ingest, filter in search
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 07:26:32 +02:00
Erik
71f235c8b1 feat(frontend): inventory item-state filter — Any/Equipped/Not equipped radios, moved under Characters
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 06:59:22 +02:00
Erik
4e1b9f4a1f build(frontend): deploy hardened inventory search
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 06:43:07 +02:00
Erik
edbf7086a6 fix(frontend): harden inventory search — q= validation, error boundary, stable selection, URL throttle
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-15 06:41:54 +02:00
Erik
ae24cad458 build(frontend): deploy inventory search redesign
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 06:31:25 +02:00
Erik
66ee4bd82b feat(frontend)!: replace inventory.html with /?view=inventory (set analysis dropped)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 06:30:59 +02:00
Erik
559fd6596a feat(frontend): inventory item detail panel
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-15 06:26:55 +02:00
Erik
4735aa72e9 feat(frontend): inventory chips row + results table (sort, columns, paging, keyboard)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-15 06:26:38 +02:00
Erik
0533e2b9ee feat(frontend): inventory filter sidebar (characters, type, slots, cantrips, ratings, sets, state, reqs)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-15 06:22:10 +02:00
Erik
9a9b33a528 feat(frontend): /?view=inventory route + inventory page scaffold + dark CSS
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-15 06:16:12 +02:00
Erik
6fd87a3751 feat(frontend): useInventorySearch hook (debounced abortable search, URL state)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-15 06:09:47 +02:00
Erik
5ddcecaece feat(frontend): inventory search types + filter/column constants
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-15 06:09:22 +02:00
Erik
93982ac3dd docs(plan): inventory search redesign implementation plan
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 06:06:41 +02:00
Erik
edf76f50db docs(spec): inventory search redesign — React full-page view + approved mockup
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 05:57:10 +02:00
Erik
7096f30566 feat(inventory-go): wire spell filters into /search/items; harden empty-input guards
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-14 23:53:38 +02:00
Erik
4edf94f416 feat(inventory-go): build EXISTS conditions for spell filter params
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-14 23:44:51 +02:00
Erik
b0a6b494bf feat(inventory-go): spell name -> ID matchers for search spell filters
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-14 23:37:28 +02:00
Erik
bd9c1ba556 docs(plan): inventory spell filters implementation plan; spec: reuse existing slots UI
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 23:32:53 +02:00
Erik
aa3139b586 docs(spec): inventory search spell filters + jewelry-type selection
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 23:26:29 +02:00
Erik
5f7b4dcd55 fix(tracker-go): key login rate limiter on X-Real-IP, not spoofable XFF
nginx's X-Forwarded-For is $proxy_add_x_forwarded_for, which only appends
to whatever the client sends — a forged leftmost hop passed straight
through, letting an attacker rotate spoofed values to dodge the 5s login
cooldown. X-Real-IP is set by nginx to $remote_addr on every proxied
location and can't be forged by the client, so key on that instead.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-04 22:43:43 +02:00
Erik
db534ea389 fix(inventory-go): restore GET /inventory/{name} (live Inv window was empty)
The Go cutover omitted get_character_inventory; the React InventoryWindow
fetches GET /inventory/{name} and got 404 -> empty. Port the endpoint:
per-character items with placement (current_wielded_location/container_id/
items_capacity), mana (current_mana/max_mana from original_json IntValues),
icon overlays, and join-table combat/req/enh/rating stats; material-prefixed
name. Returns {character_name,item_count,items}.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 21:44:16 +02:00
Erik
4bc51a1f48 feat(suitbuilder): Select All / Clear All toggle for Legendary Wards
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 21:24:52 +02:00
Erik
09bde83325 feat(suitbuilder): CD0/CD1/CD2 allowed-tier checkboxes (replace dead crit min/max)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 20:36:45 +02:00
Erik
75a735d589 feat(suitbuilder): apply CD-tier filter in loadItems (before domination)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 20:35:20 +02:00
Erik
7155055072 feat(suitbuilder): CD-tier filter helpers + tests; gate inventory-go build on go test
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 20:35:20 +02:00
Erik
593e99894f feat(suitbuilder): add allowed_crit_damage constraint field
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 20:35:20 +02:00
Erik
dfdfd41882 docs: implementation plan for suitbuilder CD-tier filter
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 20:30:26 +02:00
Erik
f7fd6415a9 docs: design for suitbuilder CD-tier filter (CD0/CD1/CD2 toggles)
Per-search filter selecting which crit-damage tiers are allowed on armor.
Default (all allowed) is byte-identical to current behavior; "prefer highest
allowed tier" falls out of existing scoring. Go-only (live solver); Python
copy left frozen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 20:14:54 +02:00
Erik
9911edbfa8 docs: Go is production — rewrite README, update CLAUDE.md, gitignore .env
- README: Go-backend architecture, build/run via the compose override stack,
  WS/payload/auth/DB contracts, the branch layout (master = Go, python-legacy).
- CLAUDE.md: Project Overview + Components reflect the Go services; a "Go services
  — build, deploy, gotchas" section (string coercion, typeless telemetry, the
  trinket dedup, rollback); Deploying + Suitbuilder point at the Go paths. The
  behavioral contracts (WS/auth/DB/routes) are kept — Go honors them; file refs to
  main.py/inventory-service mark the legacy source.
- .gitignore: ignore .env / .env.bak-* (public repo; .env.example stays tracked).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 19:46:50 +02:00
Erik
5ade47dc64 feat: Go backend production cutover — website layer, ingest forwarding, alerts, live fixes
Completes the Go backend so it can fully replace Python in production:

tracker-go website layer (serves the unchanged frontend):
- static file serving + SPA fallback + /icons (website.go)
- login/logout with itsdangerous cookie ISSUING (bcrypt, Python-interop) and the
  /me handler (auth.go issueSessionCookie + website.go)
- admin user CRUD (website_admin.go) and the issue-board write side (website_issues.go)
- request-scoped user context + requireAdmin (auth.go)

cutover ingest (gated off during the parallel run, required for a clean cutover):
- inventory forwarding: full_inventory -> /process-inventory, inventory_delta ->
  item POST/DELETE, per-character serialized, fire-and-forget (inventory_forward.go)
- death/idle Discord alerts via DISCORD_ACLOG_WEBHOOK (aclog.go)
- SKIP_SCHEMA_INIT so write mode against the prod DBs runs no DDL (tracker-go + inventory-go)

two bugs found live and fixed:
- coerceNum: the plugin sends kills_per_hour/deaths/total_deaths/prismatic_taper_count
  as STRINGS; pydantic coerced them, Go's number helpers wrote null/0 (reads.go/ingest.go)
- telemetry is broadcast TYPELESS so the browser ignores it and uses the /live poll;
  broadcasting it typed flapped the per-player counters 0<->value (ingest.go stripType)

docker-compose.cutover.yml: reversible override flipping the Go services to write
mode against the production DBs and repointing the Discord bot at the Go /ws/live.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 19:46:40 +02:00
Erik
776076b981 chore(go-services): point dereth-tracker-go at inventory-go (full Go /go/ stack)
The /go/ read stack is now fully Go end-to-end: browser -> nginx /go/ -> Go
tracker (dereth-tracker-go) -> Go inventory (inventory-go) -> read-only prod
DBs. Previously the Go tracker proxied inventory calls to the Python service;
inventory-go is validated byte-identical (search, sets, suitbuilder) so the
whole parallel stack is now Go. Production is untouched (additive override).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 14:15:59 +02:00
Erik
57f53ff36b feat(inventory-go): port the suitbuilder solver (/suitbuilder/search) — validated
Full Go port of suitbuilder.py's ConstraintSatisfactionSolver (the LIVE solver
behind the suitbuilder UI; main.py's /optimize/suits is legacy/unused):

- suit_model.go: CoverageMask + reductions, SuitItem/ItemBucket/SuitState,
  SpellBitmapIndex, ScoringWeights, SearchConstraints, CompletedSuit.to_dict,
  ItemPreFilter, set name<->id maps. Every sort carries (character_name, name)
  tiebreakers for deterministic results.
- suit_solver.go: the 5-phase pipeline — load_items (fed in-process by the Go
  /search/items), create_buckets (+multi-slot/generic-jewelry expansion),
  apply_reduction_options, sort_buckets, and the depth-first recursive_search
  with both Mag-SuitBuilder pruning rules, can_add_item constraints (set limits,
  jewelry spell contribution, strict spell mode), scoring, and finalize.
- suit_http.go: POST /suitbuilder/search (SSE: phase/log/suit/progress/complete),
  GET /suitbuilder/characters, GET /suitbuilder/sets.
- search.go: refactor handleSearchItems -> shared runSearch (the solver reuses
  it so both see identical rows); emit slot_name (get_sophisticated_slot_options
  + translate_equipment_slot); fix the trinket slot_names clause to exclude
  %bracelet% (matches Python).
- slotname.go: the EquipMask-based slot translation, loaded from the enum DB.

Validation: 9/9 scenarios stream byte-identical suits vs the Python service on
production data (no-spell, multi-character, locked slots with/without spells,
spell constraints, alternate set pairs, primary-only). ~45x faster than Python.

Three subtle bugs found and fixed during validation:
- slot_name is load-bearing, not display: jewelry's computed_slot_name is empty,
  so load_items falls back to slot_name to bucket rings/neck/wrists/trinket.
- Python scoring uses floor division (total_armor // 100); total_armor goes
  negative (non-armor items carry armor_level -1) so Go's truncation was +1 off.
- the trinket fetch must exclude bracelets or they duplicate the Wrist buckets.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 14:03:59 +02:00
Erik
2473b80519 feat(inventory-go): search spell_names/spells enrichment + shirt/pants filters
Adds the remaining search-result enrichment that the suitbuilder solver (and the
item-detail UI) depend on, validated byte-exact against the Python service on
production data:

- Load the SpellTable (spells.values, 6266 entries) from the enum DB and port
  translate_spell (id -> {id,name,description,school,difficulty,duration,mana,
  family}, Unknown_Spell_<id> fallback, "" defaults).
- Emit `spells` (full dicts) and `spell_names` from the ordered passive Spells
  array (original_json->'Spells', array order + duplicates preserved), exactly
  as enrich_db_item/extract_item_properties do — NOT from item_spells. Only set
  when the item has spells. A jsonb_typeof guard keeps non-array Spells safe.
- Add the shirt_only / pants_only / underwear_only filters as CTE-body WHERE
  injections (coverage-bit logic on key 218103821), mirroring main.py.

Validation (char Plant Enjoyer, all chars): spell_names 0 mismatches (8 spell
items), spells[].name 0 mismatches, shirt_only/pants_only item sets identical
(0 only-py / 0 only-go). Normal-search total_count still matches Python.

Note: for shirt/pants/slot filters Python's total_count is inconsistent with its
own items (separate count CTE lacks the injection); Go uses one CTE so the count
is self-consistent. Deliberately not replicating that Python bug.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 12:57:20 +02:00
Erik
c49b81c237 feat(go-services): inventory-go Phase C — ingestion (validated, isolated DB)
Wires the validated item-processor into the ingestion endpoints, writing to an
isolated inventory-go-db (never production):
- schema.go: faithful 7-table replica of inventory-service/database.py.
- ingest.go: /process-inventory (full replace), POST/DELETE single item, with the
  exact delete-then-insert flow, dynamic INSERT builder (quotes reserved "unique"),
  spell union (is_active), and item_raw_data verbatim. enhancements always inserts.
- compose: isolated inventory-go-db (postgres:14, 127.0.0.1:5435) + read-write
  inventory-go-shadow (:8773) that owns it; schema init on boot.

Validated by ingesting a recently-ingested character's items (from production's
original_json) into the shadow DB and diffing vs production: byte-identical —
items 243, combat 243, enhancements 243, ratings 6, requirements 19, spells 52
all match; 0 per-column mismatches across 243 items.

Finding: older production normalized rows can be STALE (predate the code reading
Decal keys 218103832/218103835); Go matches the CURRENT Python code, so validate
ingestion against recently-ingested characters.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 12:42:26 +02:00
Erik
b90b52c515 feat(go-services): inventory-go item-processor (extract_item_properties) — exact
Ports the core item processing: raw item JSON -> normalized columns for all 7
tables, with the exact per-table sentinel->NULL rules, material/item_set string
translation, the Spells/ActiveSpells union (is_active), and compute_base_values
(the spell_effects buff-reversal for base_armor_level/base_max_damage/
base_attack_bonus/etc., with the data embedded and the 167772170-vs-167772172
attack-bonus id discrepancy preserved). loadEnums now also loads MaterialType.
A loopback POST /debug/process returns the normalized columns for validation.

Validated against production's STORED rows (read-only, no writes): 0 mismatches
across 200 items for every sampled column of items, item_enhancements (incl.
translated material + set), item_combat_stats (incl. base_* values), and
item_ratings.

This unlocks ingestion (the processor produces the rows) and the remaining
search-response enrichment (spells/weapon/mana from the same extractor).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 12:23:38 +02:00