Storage verified sustainable: per-account points hard-capped at 20,000 with
dedup (<=1/15min, 24h heartbeat); on-disk store is gzipped (live: 12,347
accounts / 149k points / 3.2 MB). Going public does not change what is stored
(same data collected) and the read path is cheap (in-memory series + O(snapshot
files) backfill lookups, no file parse per request).
- /power-series: remove the mod 404 gate -> public (Power/Level/Gold are public
leaderboard values already shown in the lookup). mod_key kept as no-op param.
- index.html: move the power-series pieces out of the MOD:START/END regions so
they are served to everyone (ai-power-panel container, loadPowerSeries/
powerSeriesHide calls, power_series.js include). Other mod features untouched.
Verified as a non-mod locally: script loads, endpoint 200, graph renders.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
forge_api.py:
- Split Nightmare Arena into its own action (separate legendary counts, no rate)
- Dwarf-Multi: /player & /player-detail accept names= to union multiple accounts
- Arena/Nightmare leaderboard: "Most legendarys" sort; luckiest/unluckiest now
legendary-per-attempt ratio (no floor, >=1 always beats 0)
forge_stats.js:
- Sub-tabs restructured to Leaderboard (default) + Totals; Item Tables moved under
Totals as "Dwarf" (first); Combine Accounts moved into the lookup
- Combined-account dwarf items are clickable for level distribution
- Nightmare shows legendarys like arena; sort options gated per drop action
app.py:
- VIP backend + Space-side sidecar (vip_lookup.js reads world_10 VIPLeaderboard via
authenticated lookup account), /vip-status + /ingest-vip
index.html:
- VIP badge (no star), Thorns/Freeze formula cards, forge_stats.js cache bump
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Client (static/analytics.js): smTrack('cat:name') sammelt Events und schickt
sie gebuendelt per navigator.sendBeacon('/track') -> feuern-und-vergessen,
blockiert nie den Render-/Interaktionspfad. Nichts Personenbezogenes (kein
Name/IP/UA). Hooks: Tab-Wechsel, Optimize (market/nomarket), Leaderboard-Suche,
Account-/Room-/Clan-/Forge-Lookup, welche Forge-Stat-Details geoeffnet werden.
Server (app.py): /track zaehlt In-Memory pro UTC-Tag + flusht gedrosselt
(<=1x/60s) als winziges JSON nach /data/usage_stats.json; Kategorie-Allowlist
+ Zeichensatz/Laenge + Tages-Kardinalitaetsdeckel + 4KB-Body-Cap als
Flood-Schutz. /usage-admin (MOD-only, wie power-series-admin): stats/raw/csv/
clear. Heisse Pfade (Leaderboard/Stream/Lookups) unberuehrt.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Badge reads plain "BANNED"; the reason stays available via hover tooltip
and click popover (cursor stays a pointer).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Shows WHY a banned account was banned, on top of our own ban detection
(is_banned stays authoritative; the reason only supplements it).
- app.py: /ban-reason endpoint loads ban_reasons.json from the forge HF
dataset (cached, TTL) and matches username + aliases (id-bound, like the
forge/power-series technique), since the public ban-logs channel has no
account ids.
- static/index.html: BANNED badge now shows the reason as a hover tooltip
and a click popover (XSS-escaped), loaded once per username.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
13 days ago
power-series: y-axis floor at 0 + full history (incl. <1 Qnt) for whitelisted names
- Chart: clamp the y-domain minimum to 0 (power/level/gold can never be negative;
the 8% bottom padding produced labels like "-612qnt").
- Backend: SM_POWER_SERIES_FULL_NAMES (default "oettaM") bypasses the 1e18 floor
in the recorder and the Discord import, so those accounts keep ALL points incl.
sub-1-Qnt history. On name-normalization collision (a tiny name-twin resolving
to the same id as the real account) the richer series (more points) wins instead
of last-write, so a small twin can't overwrite the real history.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
15 days ago
power-series: Discord bot-stats history import (src=3), past-dedup + admin/stats endpoint
- /power-series-import-discord (POST, ingest-key gated): bulk-imports historical
player stats parsed from the Discord #bot-commands channel as a NEW uncertain
point source (src=3), bound to account id (name->id via leaderboard). Strict
validation: if ANY historical level for an account exceeds its current level
(name maps to a different/reset account) the ENTIRE account's messages are
discarded. Stored in a separate /data/power_series_discord.json (own lock),
so the hot exact recorder is never touched and re-imports are idempotent.
- /power-series read now merges these (precedence: exact > discord > backfill)
and reports discord_count.
- /power-series-admin (mod-gated): action=stats (sizes/distribution), dedup
(collapse past identical consecutive points in the exact series, keeping run
endpoints), clear_discord (clean re-import).
- Cached the O(N) leaderboard level-index across import chunks (5min TTL).
- Frontend: src=3 rendered as a distinct amber diamond with legen…
Power-series: keep tooltip on-screen for top/edge points
The hover tooltip is anchored above the point, so for a point near the
top of the chart it ran off the top edge and clipped the date line. Now
it flips below the point when the point sits in the upper part of the
chart, and its horizontal center is clamped so the wide date line never
runs off the left/right edge of the panel.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Power-series: fix level dips, gear in snapshots, smaller points
- Level dips: level can never decrease, so a point with a lower level is
stale/wrong (e.g. an hourly snapshot taken right after a restart, before
ingest refreshes A's leaderboard -> old values). The recorder now skips a
point whose level is below the account's last point, and the endpoint
filters any level regression out of the merged series. Removes the dips.
- Snapshots now carry the active sword/shield (type+level): the leaderboard
row stores gear from the scan/lookup, and hourly/daily snapshots append it
as [lvl,pow,gold,sword_type,sword_level,shield_type,shield_level]. Accounts
without gear stay the old 3-element shape (no file bloat); the d1/d7 delta
readers only use indices 0-2, so they're unaffected. Backfill reads the
gear -> coarse snapshot points now show the weapon too.
- Chart data points rendered smaller (r 2.8 -> 1.7).
Verified: stale low-level point dropped (recorder + endpoint); a 7-element
snapshot yields gear in the backfill, a 3-element one yields…
Power-series: store + show active sword/shield level
Each exact point now stores the active sword and shield as type-code +
item level (was type only): stored format
[t, level, power, gold, sword_type, sword_level, shield_type, shield_level].
A normalizer keeps old 6-element points readable (level -> unknown), and
backfill/snapshot points still carry no gear (the leaderboard snapshots
only store level/power/gold — unavoidable). The chart tooltip shows e.g.
"Last Horizon Lv5" / "Final Bastion Lv5".
/ingest-leaderboard now accepts optional sword/shield type+level so the
scanner push can include gear (it already has it from the inventory
lookups it does for power/gold); without them the point stores -1.
Verified end-to-end: lookup and gear-carrying ingest both record type+
level; tooltip renders the level badge.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Power/Level/Gold history chart in player lookup (mod-only)
Frontend for the per-scan power/level/gold time-series: a collapsible
SVG line chart in the player-lookup tab, above the forge/action stats.
Metric toggle (power/level/gold), range filter (day/week/month/all-time),
mouse-drag zoom, hover tooltip with the active sword/shield, exact points
filled vs coarse leaderboard-snapshot backfill hollow. All UI text in
English; all timestamps rendered in UTC and labelled.
- static/power_series.js: self-contained chart module (loads on lookup).
- /power-series now merges the leaderboard snapshots (hourly ~28h +
daily ~9d) as coarse backfill with the exact series, dedup per hour,
exact wins; adds sword_names/shield_names + src marker per point.
Mod-only: the panel, its <script> and the lookup hook are wrapped in
MOD:START/END so the public page is built without any trace, and
/power-series requires the sm_mod cookie (or ?mod_key=), else 404.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
17 days ago
Lookup head portrait: in-game-accurate helmets/hair, fixed & centered
Head portrait now reuses the full-body placement pipeline (RivalBoss
skeleton + _rvDress equip-template helmet + _m3dApplyHair incl. VIP),
so helmets and hair sit exactly as in-game, then clips the body away
below the chin so only the head shows, with the old soft orthographic
lighting.
- Frame on the head itself (chin -> skull top), references fixed and
independent of hair/helmet -> head always same size & centred;
hair/helmet render on top without moving or resizing it.
- Disable the character anim -> deterministic bind pose (fixes the
frame-0 vs bind-pose drift that shifted the head/clip line).
- Helmet dropdown now re-renders the head preview live when "show
helmet" is on.
Cosmetics tab: integrate None (clear-x) and the sword level into each
gear field instead of separate boxes; stop marking "Lucifer Skin
Color" as VIP (only the three Transparent skins are VIP).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
21 days ago
Mob preview cards: show the sorted stat as an extra bar when it's not already shown
When the mob gallery is sorted by a bar-able stat other than health/damage/respawn
(Speed, Body radius, Attack range, Attack delay, Spawn count), that stat now appears
as a small bar under Respawn on each preview card; no extra bar otherwise.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
22 days ago
Mob images: per-mob cache-bust so a single re-baked mob updates without a global version bump
_aiMobImage now uses an optional image-config.json mob_versions[name] stamp (the tuner bumps it on re-bake) instead of only the global SM_IMG_VER, so re-baking one mob shows up on the next page reload. Falls back to SM_IMG_VER when no per-mob version is present.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
24 days ago
Game Info: mob/armor art, live Rival render, spawn counts, revamped gallery UI
- Bake 28 mob + 96 armor models to PNG and load the images instead of 3D (image-config.json + SM_IMG_VER cache-bust); images stored via Git LFS
- The Rival (daily Asgard boss): rendered live in-browser with its real skin (whole body), face, armor, sword & shield via the game's setRivalBossSkin loadout, framed with tuned orientation, labeled "The Rival (username)". Hourly server-side Colyseus join (rival_lookup.js) + /rival-info endpoint; falls back to "?" when no live data.
- Mob detail stats: add spawn-count bar
- Gallery controls: large icon category tiles with item counts + custom styled sort dropdown (native select kept as source of truth)
- Catalog: carry speed / enemyBodyRadius / attackDistance so mob stats are available
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds the 5th Gear-Gallery sub-tab "Ores" (raw -> smelted -> enchant flow
per ore) and states the confirmed mechanics:
- Raw ore: ore damage = your normal damage / 10; drop chance per hit ~=
(damage/10) / block HP (share of HP that hit removes), drops on hits not
only on kill; looting/spawn/sell context.
- Freeze: 0.25x move speed (75% slower), +1s per level, no damage/power.
- Fortune: +5% of base per level (not +5% flat), all chance-based NPCs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds a "Power/hit" row to the account-lookup Player Details grid,
positioned second-to-last (right before Hits/sec). It shows the same
personal-boss-power-per-hit value already computed for the grey Boss
Power sub-text (bpPerSec / accountHps), just made prominent as
requested. Frontend-only, additive; hidden when no power is loaded.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
feat(export): include all gear enchants (sword freeze, shield thorns + fortune)
The account export only reported the optimizer-relevant enchants (sword
flame/looting, shield looting). Mods asked for the full set. The server lookup
(/live-lookup) already returns sword_freeze, shield_thorns and shield_fortune —
only the frontend opt-list account model dropped them.
Thread them through as hidden, optimizer-irrelevant pass-through fields:
accHTML hidden inputs (iswfr/ishth/ishfo), applyDiscordAccountData fills them
from the lookup response, getAccountsSnapshot + removeAcc snapshot read them,
doLoad restores them on save/load, and buildAccountsExport now emits
sword.freeze and shield.thorns/shield.fortune. Purely additive — no change to
the optimizer (it ignores these) or to existing export consumers. Verified in
the browser: export now yields sword{flame,looting,freeze} and
shield{looting,thorns,fortune}. Export contract test extended (177 passed; the
1 failure is the pre-existing unrelated frontend tabs test).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Three moderator-only leaderboard tools (all gated behind the existing MOD:START
regions, so regular users see none of it):
- Wildcard filter search: a '*' in the query (e.g. `niceface*`, `*face`, `*face*`)
lists ALL matching accounts as a ranked list, each at its TRUE leaderboard position
-- for spotting alts fast. Backend filter mode in _sm_leaderboard_snapshot
(allow_wildcard=is_mod), frontend renders row.rank and skips the live re-sort/
re-number so ranks stay put.
- Case-sensitive toggle ("Case sensitive" checkbox): off by default; on distinguishes
capitalisation impostors (zywoo vs ZywOo). New _sm_lb_display_names so case matching
respects the display name only, not the lowercase username_key.
- Favorites watchlist: a star per row + a collapsible "Watchlist" panel at the top
showing favorited players with their live rank/level/power/gold (and the d/w change
chips when a timeframe is selected). Stored per-browser (localStorage); backed by a
new mod-only /leaderbo…
Room lookup: temporary maintenance disable with a clean notice
The shared lookup account (oettaMRoomLookup) needs level 5000 for the mine world
(not 3.5k like the last boss world), so high-world rooms reject the join (code 4011)
until it's leveled. Disable the feature meanwhile behind SM_ROOM_LOOKUP_DISABLED
(defaults to a maintenance message now; re-enable by setting it to ""). The endpoint
returns 503 + the message, and the room tab renders it as a calm 🛠️ maintenance
notice instead of an "Error:" line. Verified locally: 503 body carries the message;
the tab shows the notice.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mob-kill leaderboard now visible: 3rd sub-tab in the Leaderboards tab (Player /
Forge & Actions / Mob Kills). Pick any of the 29 enemies -> ranked accounts by kills
of that mob, styled like the player leaderboard, with a total-kills counter on top.
Backed by the cached per-enemy count arrays already deployed. Data is now populated
(~39k accounts with kill data after a full pusher cycle, top killers correct).
Files: static/mob_leaderboard.js (new), static/forge_stats.js (sub-tab wiring),
static/index.html (include + cache-bust bumps).
Suggestions: 'planned' posts are now commentable too (were locked like the finished
statuses). open + planned stay open for non-mod comments; implemented/declined/closed
stay locked. Enforced on both sides: feedback_comment endpoint and feedback_tab.js
composer gate.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add Suggestions tab: Discord-style board with live updates + mod moderation
A community suggestions board (tab "Suggestions"):
- Overview lists suggestions; clicking one opens the discussion beside it, with a
"Back" button to the full-width overview. Long bodies are truncated in the list.
- "Agree" upvote pill on posts (one per browser via a localStorage uid). Threaded
comments with Enter-to-send / Shift+Enter for a newline; the composer is pinned and
the message area scrolls, so posting never scroll-jumps.
- Ordering: Open, then Planned, then a grayed Implemented/Declined/Closed at the bottom.
- Moderation (right-click, mods only via the existing SM_MOD_KEY cookie): delete a
suggestion, set its status, or delete a comment.
- Live for everyone on the tab via SSE (/feedback/stream, in-process pub/sub like the
leaderboard): new posts, comments, votes, status changes and deletes apply in place.
Backend: SQLite under /data on the Space (survives restarts and rebuilds), next to
app.py locally. Endpoints under /feedback/*; mod actions enforced server-side…
Leaderboard totals: reorder tiles, fold combined-level into the Total XP tile
Order is now Power - Gold - Lv{100/1000}+ Accounts - Accounts Total - Total XP.
The "all that XP on one account = level N" line moves from a standalone row into
the Total XP tile's sub-line (present but subordinate, matching the other tiles).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Leaderboard totals: accurate 100+/1000+ count, add Total XP, drop Players Online
- Account count now shows the enumerated cache count (accounts_min_level), not the
game API's low estimate (qualified_min_level). Levels can't drop, so every tracked
account is still at that level -> the enumerated count is authoritative (only new,
not-yet-scanned accounts could make it slightly low). Fixes "table shows more than
the header count".
- Replaced the "Players Online" (CCU) tile with a "Total XP" tile: sum of
level_base_xp(level)=level**4 over the min-level set (1000+ public, 100+ mod).
- Added a line: "One account with all that XP combined would be level X"
(level_from_total_xp of the summed XP).
- Reworded the totals note to reflect that the count is accurate, not partial.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replaces the browser-side room join (now blocked by the site's anti-bot
layer: no-Origin guard + Cloudflare 1020) with a server-side lookup. A Node
sidecar (colyseus.js 0.15 + ws) joins by share code using a dedicated
account and decodes the room roster, returning {username, inv} that the
existing room_tab renderers consume unchanged. The Origin header is injected
via a globalThis.WebSocket wrapper before colyseus.js loads.
Backend GET /room-lookup serializes joins (one shared account), caches 10s,
keeps the account token in memory only, and re-logins from secrets
(SM_ROOM_LOOKUP_USER/PASS) on a dead token. Dockerfile adds Node + installs
the sidecar deps at build.
Developer-authorized (Emre Sahin, 2026-09-15).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reworked the forge lucky/unlucky to match how players think about it,
all computed per-player in forge_api at query time (no DB rebuild):
- Per-action LUCK = percent over/under expected, shown ONLY for dwarf,
elf, pet_enhance (there the odds are clean; dwarf/elf via the logged
luck% incl. Fortune, pet via per-pet rate). smelt/enchant/downgrade
get no luck chip -- their odds depend on free/paid & player level
which the logs don't record. arena shows only the legendary count.
- VALUE per action (dwarf/elf/pet) + the OVERALL "lucky/unlucky" is now
value-weighted over dwarf+elf+pet: realized vs expected value, input
always lost, output only on success (dwarf -> item L+1, elf -> the
legendary L1, pet -> the XPet). Elf is included. Weighted so a failed
expensive elf/L9 legendary dominates and cheap noise doesn't.
- New forge_prices.json (bundled price snapshot) + ELF/PET recipes let
the API price the value swings without a price service. Global bucket
rates cached from…
Two fixes to the forge lucky/unlucky calculation:
1) Per-action luck for smelt/enchant/downgrade/pet was expected =
attempts x GLOBAL rate, but success depends on the bucket (smelt:
ore 20-70%, enchant: type, ...). A Coal-heavy smelter looked "+50
lucky" purely from ore mix. Now bucket-standardized (like dwarf's
per-rarity/level), in build_stats.py -- pure selection bias removed.
2) The combined score pooled dwarf/elf/pet/arena as an equal statistical
z-score, ignoring stakes. Now VALUE-weighted: per event contribution =
stake x (outcome - p), pooled Z = sum / sqrt(sum stake^2 p(1-p)).
Stake = coins at risk: dwarf=item@level, elf=10x input@L5-epic,
pet=10x normal-pet, arena=avg legendary value. A failed L9 legendary
or burned elf dominates; cheap smelts are noise.
forge_api is backward-compatible: falls back to the old count-based
overall when the DB lacks the new vluck_num/vluck_var columns, so the
forge tab keeps working until the rebuilt DB is uploaded.…
The "XP to Lv N+1" row showed the REMAINING xp to level up ((L+1)^4 - current
total xp), which depends on how far you are into the level. The game instead
shows the level's full XP cost ((L+1)^4 - L^4), progress-independent. For a
level-78152 account the tool showed 0.26qt while the game shows 1.91qt.
The L^4 formula itself is correct (verified against 30+ live accounts, level
40..124k: every account's experience sits in [L^4, (L+1)^4)). Only the
displayed quantity was the wrong one. The level projections (levels/day, time
target) already use cumulative xp correctly and are unaffected.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Leaderboard: accurate restore-source label + honest 24h delta window
- cache_restored_from now reflects the ACTUAL source of the current boot
("data" on a normal /data load) instead of leaving a stale "bootstrap" that
falsely implied a reset on every subsequent load.
- The 1-day delta tooltip now reports the real window age during ramp-up
(e.g. "over the last 17 hours") instead of always claiming 24h, matching how
the weekly delta already reports its age. Converges to 24h once a full day of
hourly snapshots exists. (mod-only view)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fixes the "very slow" leaderboard at ~1.6M level-100+ rows and makes the
1-day delta a true rolling 24h window.
Speed:
- Sorted views are recomputed in a background maintenance thread as new data
arrives (debounced ~12s), not per request; the request path only slices a
ready view from cache. Warm page loads drop to a few ms.
- The expensive build now runs OUTSIDE the leaderboard lock (under the lock only
a fast reference snapshot), so pushes/search/lookups no longer stall for
seconds while >1M rows sort.
- Cached views hold lightweight raw references, not full row copies (~13MB vs
~1GB per view at 1.6M rows), so warming many views is RAM-safe; display copies
are built only for the shown page. This also makes each rebuild ~5-10x faster.
- Only public default views are pre-warmed; the big mod (100+) views warm on
demand and self-expire after 15 min idle.
Rolling 24h:
- Hourly gzipped snapshots (~28h kept) feed d1 = current - snapshot nearest to
now-24h, refreshed hourly…
The public site now shows ONLY: name, level, power, gold, updated — sorted
by power/gold/level. Everything mod-specific is physically removed from the
served HTML/JS/CSS via server-side region stripping (<!--MOD-->//*MOD*/), so
non-mods get no timeframe field, no "show all", no per-timeframe deltas, no
former-name line, no Player-ID row — and no trace those exist.
- index route serves a stripped public build or the full mod build by cookie.
- /leaderboard-summary is now mod-aware: mods get Total Power/Gold + account
counts over ALL level-100+ accounts (labelled 100+); public stays 1000+.
- Summary cache is keyed per min_level so the two views don't collide.
Verified: public HTML has zero mod markers/handlers, both builds' inline JS
parse, summary/leaderboard return min_level 1000 (public) vs 100 (mod cookie).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mod view: fully server-side via hidden cookie, no client-side trace
Non-mods must not even know the mod view exists, so all mod hints are removed from the
public HTML/JS. /?mod=<token> now sets an HttpOnly cookie server-side and redirects to a
clean /; /leaderboard decides 100+ vs 1000+ from that cookie. Wrong token just serves the
normal page (no error, no hint). /?mod=off clears it. Test stub gets RedirectResponse.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Leaderboard: snapshot caching + separate store/public/mod levels + hidden mod view
- Cache the built+sorted row list per (sort,dir,measure,min_level) for a few seconds,
so the page loads instantly instead of rebuilding/sorting all rows every request
(essential once the store holds >1M level-100+ accounts). Bypassed under pytest.
- SM_LEADERBOARD_STORE_MIN_LEVEL: what gets stored (e.g. 100); public view stays
SM_LEADERBOARD_MIN_LEVEL (1000). Snapshot takes a min_level param.
- Hidden moderator view: /leaderboard?mod_key=<SM_MOD_KEY> shows down to the store min
(level 100+); wrong/no key = public 1000+. Frontend reads ?mod=<token> from the URL.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Leaderboard 'In 1 week': use oldest snapshot within 7 days during ramp-up
Instead of waiting a full week (or falling back to Total), the 1-week timeframe
now uses the oldest available snapshot up to 7 days back: 3 days of history -> a
3-day delta, etc., until a real 7-day snapshot exists (then normal). Delta tooltip
reflects the actual span (_LB_D7_AGE).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Leaderboard: timeframe sort, cleaner deltas, collapsed top-N, no player ID
- Sort by measure: Total / gained in 1 day / gained in 1 week (backend + 3rd dropdown)
- Deltas follow the timeframe (one per column, label in header); Total shows none
- 'Show all' toggle (default off) reveals both 1d+1w deltas everywhere; you always
see the filtered timeframe's delta, and can opt into all changes
- Top-N averages collapsed into a subtle expandable section (was a blue box)
- Removed the truncated player ID under leaderboard names
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ban badge: show only when banned; hide otherwise (no 'not banned'/dead)
The BANNED badge came in late because it rode on /live-rank, which re-runs a
full GetPlayerInfo lookup AND computes the total player count for the percentile
(cold: ~10-22s). New /live-ban endpoint does only the level-targeted, cached
rank search and returns is_banned/is_dead/level_rank — no second lookup, no
count. The frontend fires it in parallel with the main lookup, so the badge
appears in ~1-2s instead of after the full rank resolution. /live-rank still
runs to fill the rank percentile and re-confirm the status.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Account Info: move ban badge to the card's top-right corner
Positioned the BANNED / not-banned badge absolutely in the top-right corner of
the lookup hero instead of stacking it under the username.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Account Info: show ban status (BANNED / not banned) from getPlayersPagination
getPlayersPagination already carries isBanned/isDead per account; the rank
resolution (_sm_attach_level_ranks_unlocked, used by /live-lookup and /live-rank)
now surfaces is_banned/is_dead on the lookup result and the leaderboard row.
The Account Info profile shows a badge: red "⛔ BANNED", green "✔ not banned"
(with a "· dead" note when isDead), or hidden when the account isn't found in
the ranking (status unknown). Helps the mods check whether a suspicious account
they found via the leaderboards is already banned.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Leaderboard: daily/weekly power/gold/level diffs (green up, red down)
Store a compact daily snapshot of the leaderboard cache ({key:[level,power,gold]})
next to the cache on persistent /data (lb_history/, kept ~9 days, one per day via
the existing cache-save path). The /leaderboard rows for the shown page are then
enriched with d1_/d7_ deltas vs the snapshot from ~1 day and ~7 days ago, and the
frontend shows them as small colored chips (▲ green gain / ▼ red loss) under level,
power and gold, for both 1d and 1w.
No history exists retroactively, so the chips fill in over the coming days as
snapshots accumulate. Live SSE/patch updates keep the current value (now in a
.lb-val span) without wiping the diff chips.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fix XP-to-next-level jumping to 0 on manual level change
The Account Info "XP to Lv N" row read the stale d.total_experience from the
last lookup. On a manual level change, aiLevelManual() clears the ai-total-xp
field (and flags manual='1'), so the row now reads that field instead: empty
means the level was typed manually, so the level threshold (L^4) is the
baseline and the row shows a full level's worth ((L+1)^4 - L^4) instead of 0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Account Info:
- Player Details now shows XP needed for the next level (threshold L^4),
updating live when the level field is edited manually.
Upgrade Lab:
- "And better" checkbox for Dwarfting & Merging and Elf Legendary, plus an
"And more" checkbox for the Pet Enhancer: on = at least the target
(level-or-higher / count-or-more), off = the odds for exactly that target.
- Materials-needed card ("all owned = 0") gains a source-level selector:
choose which owned level to count (Lv1..target); target level = target count.
- Default sword is now Soulkeeper's Blade instead of Solbrand.
Optimize tab market recommendations:
- Each buy suggestion can be excluded and re-optimized. Exclude offers three
scopes: this exact variant, this name at this level (any enchants), or the
whole item. Buy/sell suggestions can also have their price overridden (used
for buying and selling). Active corrections show as removable chips + Reset.
Motivation: market prices are imperfect; this lets the…