Stats were bare numbers with no visual hint what they represent.
Now each stat has the same emoji prefix as v1:
⚔️ session kills, 🏆 total kills, KPH suffix
💎 rares (session/total), 📊 KPR suffix
🕑 online time, 💀 deaths, 🔮 prismatic tapers
Meta state pill still color-coded (green=active, gray=idle).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Rebuilds the v1 map-centric experience in React:
Layout:
- 400px sidebar on left, interactive map on right (flex, 100vh)
- Exact same proportions and dark theme as v1
Sidebar (top→bottom):
- Header with active player count + Dashboard toggle button
- Server status dot (Coldeve online/offline with pulse)
- Aggregate counters: Rares (gold), Server KPH (blue glow), Kills (red)
- 6 sort buttons (Name, KPH, S.Kills, S.Rares, T.Kills, KPR)
- Player name filter
- Scrollable player list with per-row:
- Name + coordinates
- HP/Stamina/Mana vital bars (red/orange/blue gradients)
- Session kills, total kills, KPH
- Session rares, total rares, VTank meta state pill
- Online time, deaths, prismatic tapers
- Color-coded left border per player
Map:
- dereth.png with CSS transform pan (drag) + zoom (wheel, 1.1x factor, max 20x)
- Player dots (6px circles, color-matched to sidebar)
- Hover tooltip (name, coords, kph, kills)
- World coordinate display at cursor position
- Fit-to-window on first load
View toggle: Map View ↔ Dashboard with localStorage persistence.
All v1 CSS ported under ml-* prefix, scoped via map-layout.css.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Below the character cards grid, adds four tabbed analytics sections:
Combat Tab (Recharts):
- Kills per hour horizontal bar chart (all characters, sorted)
- Total damage session bar chart
- Damage by element pie chart (aggregated across all characters)
Rares Tab:
- Summary cards: total rares, total kills, drop rate (1 in N)
- Recent rare drops timeline (from WebSocket events)
- Rares per character lifetime bar chart
Map Tab:
- Dereth map (dereth_highres.png) with SVG overlay
- Character position dots (green=hunting, yellow=other)
- Hover to see character name + coordinates
- Responsive, maintains aspect ratio
Inventory Tab:
- Cross-character item search with debounced input
- Results table: character, item, type, material, set, workmanship
- Powered by existing /search/items API
All tabs lazy-rendered (only active tab mounts). Horizontal scroll
tab bar on mobile. Dark theme consistent with cards.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
vt_state values from plugins include "Hunt", "combat", "Default",
"turn_in_quests" etc. Previously only "combat" showed as green badge,
everything else was "Idle". Now Hunt shows green, unknown states show
their actual name.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New modern dashboard at /v2 running alongside the existing UI at /.
Same backend, same APIs, same WebSocket — zero backend changes.
Stack: React 19 + Vite + TypeScript + Recharts
Source: frontend/ — build output: static/v2/
Phase 1 delivers:
- Character overview cards in a responsive CSS Grid
- Live HP/Stamina/Mana bars via WebSocket vitals
- Kills/hr, total kills, deaths, session uptime
- VTank state badge (Combat/Nav/Idle)
- Location coordinates
- Click to expand: combat stats, prismatic count, CPU/RAM
- Global stats header: active chars, total kills, total rares, server health
- WebSocket hook with auto-reconnect
- HTTP poll fallback for initial load + server health
- Mobile responsive (single column on narrow screens)
- Dark theme matching the MosswartOverlord palette
Build: cd frontend && npm run build
Access: /v2 (served by existing NoCacheStaticFiles mount)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The element breakdown grid previously only showed damage RECEIVED
(defense) in the Mel/Msl and Magic columns, which was mostly empty
for characters who evade/resist everything. Now shows both:
- Given M/M + Given Mag: damage dealt by element (offense)
- Recv M/M + Recv Mag: damage taken by element (defense)
This makes the element breakdown immediately useful — you can see
that you're dealing Slash damage via melee, for example.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Peers who unsubscribed or disconnected from vital sharing were lingering
forever in the Vital Sharing browser window because nothing ever deleted
them from the server-side state or told the browser to drop them.
Backend:
- share_unsubscribe now pops the character from _vital_sharing_peer_state
(not just flips connected=false) and broadcasts a share_peer_removed
envelope to browser clients.
- On real plugin disconnect, do the same: pop the state entry and
broadcast share_peer_removed so the NetworkUI updates immediately.
Frontend:
- New removeVitalSharingPeer(name) deletes from the local
vitalSharingPeers dict and re-renders.
- socket.onmessage now routes share_peer_removed to it.
- refreshVitalSharingPeers() reconciles against the server's list and
prunes any local entries the server no longer knows about, catching
any race where the broadcast was missed.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
_broadcast_share_to_plugin_clients was discarding a character from
_vital_sharing_subscribers whenever a single send_json hit the 1-second
timeout or raised any exception. Under heavy load this permanently
dropped clients from the subscriber set even though their WebSocket was
still fully connected — the user had to toggle vital sharing off and on
to get peer updates flowing again.
Now we log the send failure but leave the subscriber intact. Actual
eviction still happens on real WebSocket disconnect via the finally
block in the plugin receive loop.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Previously the NetworkUI window used #c44/#4c4/#46c which rendered
stamina as green. Updated to the gradients used in .vital-fill.* in
style.css (#ff4444, #ffaa00, #4488ff) so the network UI matches the
player sidebar.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Accepts new share_subscribe / share_unsubscribe / share_* WebSocket
messages from MM plugin clients and fans them out to other opted-in
plugin clients (excluding origin) and to browser clients for the
NetworkUI window.
- main.py: _vital_sharing_subscribers set, _vital_sharing_peer_state
snapshot, _broadcast_share_to_plugin_clients relay, disconnect
cleanup, GET /vital-sharing/peers endpoint.
- static/index.html: new sidebar link for Vital Sharing window.
- static/script.js: showVitalSharingWindow with live HP/STA/MANA bars,
per-peer status dot/tags/position, 5s /vital-sharing/peers poll, and
share_* routing through the existing browser WebSocket handler.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace Nginx basic auth with proper user accounts:
- Session cookies via itsdangerous (30-day expiry, httponly, secure)
- Password hashing with bcrypt via passlib
- Login page with AC-themed UI
- Admin page for user management (CRUD)
- AuthMiddleware exempts plugin WS and browser WS endpoints
- Issues/comments author auto-populated from session
- Sidebar shows logged-in username, admin link, and logout
- Seed users: erik (admin), alex, lundberg
- SECRET_KEY env var for cookie signing
Root "/" path doesn't match .html ending. Checking the response's
content-type header catches index.html served via html=True and is
more robust for any URL that returns HTML/JS/CSS/JSON.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The StaticFiles path is empty for root "/" which served index.html via
html=True. Include root/directory paths in the no-cache match.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
FastAPI StaticFiles sets Last-Modified/ETag but no Cache-Control, so
browsers use heuristic caching and can serve stale code after git pull.
Subclass StaticFiles to add 'Cache-Control: no-cache, must-revalidate'
on .html/.js/.css/.json files. ETag/Last-Modified still work, so
revalidation returns efficient 304 Not Modified when unchanged.
Other assets (images, fonts, tile textures) are unaffected and cache
normally.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- New PATCH /issues/{id} endpoint to toggle resolved flag
- Add resolved:false to new issues
- Frontend: click "✓ Resolve" marks issue green with strikethrough
- Resolved issues show "↺ Reopen" and "🗑 Delete" buttons
- Delete requires confirmation
- Sort: unresolved first, then resolved
- Issues persist until explicitly deleted
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Was stored at /app/openissues.json (ephemeral container filesystem).
Moved to /app/static/openissues.json which is bind-mounted to the
host at ./static/, persisting across container rebuilds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add buttons as 6th grid row instead of extra element below grid
- Reduce stat pill padding and font size (4px→2px, 0.75rem→0.7rem)
- Reduce grid gap (6px→4px row, 12px→8px col) and item padding
- Unify all 5 button styles (Chat/Stats/Inventory/Char/Radar) with
shared compact sizing, individual colors preserved
- flex-wrap on buttons container as safety for narrow sidebars
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Version: CalVer + git hash shown in top-right corner of main page.
Built via Docker ARG BUILD_VERSION at build time. Served via /api-version.
Issues Board: shared notepad window for tracking issues with plugin,
overlord, nav files, macros. Stored in openissues.json on server.
- GET/POST/DELETE /issues endpoints
- Draggable window matching Chat/Radar pattern
- Category tags (plugin, overlord, nav, macro, other) with colors
- Add/resolve issues through the UI
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract spell effect mappings from Dictionaries.cs into spell_effects.json.
During item ingestion, compute_base_values() reverses active enchantment
effects to get true base stats:
- base_armor_level: armor without Impenetrability buffs
- base_max_damage: damage without Blood Drinker buffs
- base_attack_bonus: attack without Heart Seeker buffs
- base_melee_defense_bonus: defense without Defender buffs
- base_elemental_damage_vs_monsters: elemental without Spirit Drinker
- base_mana_conversion_bonus: mana conv without Hermetic Link
New columns in ItemCombatStats, exposed in search CTEs.
Frontend: Base Armor and Base Dmg columns (hidden by default, toggle on).
Requires ALTER TABLE migration before deploy.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sort listener was on .th-label span only, making clickable area too
small. Moved back to full .sortable th element. The × hide button
already uses stopPropagation so it won't trigger sort.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Some unidentified items have IntValues[218103835] = 67108882 which is
a bitmask/flags value, not weapon speed. Cap at 100 in extraction and
filter >100 in frontend display. AC weapon speed is typically 0-50.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace separate checkbox bar with inline × buttons in each column
header. Click × to hide a column. Hidden columns appear in a bar
above the table with + buttons to restore them. Sort by clicking
the column label text. Persisted to localStorage.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Unclosed string on line 570 prevented entire inventory.js from loading,
breaking character list and all search functionality.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Backend: add weapon_time and melee_defense_bonus to search CTE.
Frontend: show Speed, Attack Bonus (+%), Melee Def (+%) columns
visible by default. Material/Workmanship hidden by default.
Attack bonus and melee defense shown as percentage offset from 1.0.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New columns: Max Damage, Attack Bonus, Material, Workmanship.
All columns now driven by RESULT_COLUMNS config array.
Column visibility toggles bar above results — checkboxes to
show/hide any column, persisted to localStorage.
Coverage column hidden by default.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The rd table is only available inside the CTE, not in the outer WHERE.
Use EXISTS subquery against item_raw_data for skill-based weapon type
filtering (same pattern as spell_contains fix).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Backend: new weapon_type query parameter on /search/items.
Uses skill ID from IntValues[218103840] for melee types (Heavy=44,
Light=45, Finesse=46, TwoHanded=41) and name matching for missile
sub-types (bow, crossbow, thrown). Caster = ObjectClass 31.
Frontend: dropdown appears when "Weapons" radio selected, hidden otherwise.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
DECAL ObjectClass enum: MeleeWeapon=1, MissileWeapon=9, WandStaffOrb=31.
The weapon_only filter was using Food=6, Money=7, Misc=8 — completely
wrong classes. Also removed max_damage>0 requirement so weapons show
even before combat stats are populated.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Bug 1: Weapon stats (max_damage, attack_bonus, variance, damage_type,
weapon_time, weapon_skill) were read from top-level MyWorldObject fields
which are -1 by default. Now extracted from IntValues/DoubleValues keys
where the plugin actually stores them (218103842, 167772170, etc.).
Bug 2: spell_contains and has_spell filters used direct sp.spell_id
reference which fails in the count query CTE (sp table not available
in outer SELECT). Changed to EXISTS subqueries matching the pattern
already used by legendary_cantrips filter.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add Weapons and Clothing equipment type radio options
- Add Weapon/Combat Stats filter card: damage, attack bonus, crit resist, tinks
- Add Item Properties filter card: material, level req, workmanship, value, burden
- Add Item State filter card: spell text search, bonded, attuned, rare checkboxes
- Add Status column (Equipped/Inventory) to results table
- Remove dead Slot View button and section (no JS handlers existed)
- Update clearAllFields() for all new inputs
- All changes frontend-only — suitbuilder and backend API untouched
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two issues caused objects to float relative to dungeon map:
1. rotAngle used (heading + PI) but canvas uses (PI - heading)
- these differ: sin(x+PI) = -sin(x) vs sin(PI-x) = sin(x)
2. Object dy was negated, but 180° heading offset already handles N/S
- double N/S flip caused objects to drift with heading changes
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
UB applies HeadingToQuaternion(heading - 180) for dungeon rendering.
The layer has Y increasing downward, and the 180° offset flips N/S.
Reverted coordinates to Version 1 (X mirror, Y direct) which had
correct tile connections, and added the 180° heading offset that UB
uses to fix N/S orientation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Version 1 (X mirror, Y direct) had correct tile connections but N/S
flipped. Previous attempts to fix N/S also changed rotation handling
which broke tiles. This commit reverts tile processing (no flip),
reverts rotation (exact UB values), and only adds Y negation to
Version 1's working coordinates. No other changes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
UB's tiles were authored for a mirrored-X rendering space. Instead of
mirroring coordinates (which breaks positioning), flip each tile image
horizontally during pre-processing and negate the cell rotation angles
to compensate. Cell and object positions remain direct (no mirroring).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
UB mirrors both cell positions and player position within layer space,
so relative offset is direct (mirrors cancel out). Removed X negation.
Added float tolerance for rotation quaternion W component comparison
(was using strict equality which fails on floating point values).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Negate Y delta for dungeon tiles and objects so north points up
(AC Y+ = north, canvas Y+ = down)
- Wrap early COM calls in try/catch to prevent RPC_E_SERVERFAULT
during state transitions (portal loading, etc.)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
UB uses mirrored X: x = -(cell.X - playerX), direct Y: y = cell.Y - playerY.
Applied same transform to tile rendering, object positioning, and
entity list distance calculations in dungeon mode.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Process each tile through canvas pixel manipulation:
- White pixels → transparent (same as UB's MakeTransparent)
- Remap UB's 5 source colors (walls, inner walls, ramps, floors, stairs)
to readable display colors on dark background
- Black outlines made semi-transparent
- Current floor at 85% opacity, other floors at 12%
- Non-current floors drawn first, current floor on top
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
361 of 614 tile files have .bmp extension but are actually PNG.
Now detects magic bytes and sets correct MIME type per tile.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract 614 UB tile BMPs into dungeon_tiles.json (287KB base64 bundle).
Frontend loads tiles once, then draws them rotated per-cell using
environment IDs. Falls back to colored rectangles if tiles not loaded.
Current floor at 70% opacity, other floors at 15%.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Backend: dungeon_map event handler with permanent in-memory cache
by landblock ID, request_dungeon_map for late-joining browsers
- Frontend: render dungeon cells as colored rectangles when in dungeon,
multi-level Z support (current floor bright, others dimmed),
automatic overworld/dungeon switching based on is_dungeon flag,
raw physics coordinate positioning for dungeon objects
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Draws a rotated/scaled slice of the overworld map behind radar dots.
Map clips to the radar circle, rotates with heading-up mode, and
renders at 40% opacity so dots remain visible on top.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>