Funnel web visit → game session · current window
Stage funnel
page_load · engine_ready · menu_shown · play_click are client
beacons (counted once per page visit); connect · spawn come
from the server log. A cliff between engine_ready and
menu_shown means the web client booted but never reached the
menu — i.e. a boot hang.
Stage-to-stage drop-off
| Stage | Count | % of top | % of prev |
|---|
By country
| Country | Loads | Engine | Menu | Play |
|---|
Daily trend page loads vs spawns per day
Overview headline KPIs · Δ vs first half of window
Activity what + when filtered players play
Favorite animals (one per UUID)
Cause of player death (filtered)
Hour-of-day played (run starts, local time)
Run-length distribution
Buckets: <30s · 30s–2m · 2–5m · 5–15m · 15m–1h · >1h.
Geography where filtered players live
Player heatmap
Choropleth shaded by player count (UUID-keyed). World + NA views colour by country (ipinfo ISO2); US view colours by state (ipinfo region). Side list mirrors the same data — top 12 entries, scope-aware. Filter pills above honour the heatmap.
Network latency + transport for filtered players
Network (filtered)
Network ping over time (filtered)
Pools every [ping-stat] sample across all filtered
players, binned into ~24 buckets over the visible span. Solid line =
median; shaded band = p25–p95; dashed line = bin tail (max single sample).
Avg-ping distribution (per UUID)
Buckets: <30 · 30–60 · 60–100 · 100–200 · 200–500 · >500 ms.
Players sortable table · click a row to inspect runs
Players (UUID-keyed; click row for runs)
One row per UUID. Ping cell shows recent [ping-stat]
samples (10 s cadence per peer) as a sparkline + p95; activity
cell shows per-run survival as a bar sparkline + total play.
Hover a sparkline for sample count + peak. Sort by clicking a
column header; click a row to open per-run details on the right.
| ● | Rank | Level | Name | UUID | Current run | Location | Net | Client | OS | Ping | Activity | Deaths | Animals | Favorite | First seen | Last seen |
|---|
World map (live)
Live snapshot from the latest [map-state] dump (10 s cadence, server-side). Water + trees are static; entities + groups update on each refresh. Hover for a quick tooltip. Drag the canvas to pan (the world wraps on a torus, so panning past an edge brings the other side into view); scroll-wheel zooms toward the cursor; Shift+drag aggregates stats for a box; click an entity to pin its full stats. Press Esc to clear · recenter resets pan + zoom.
Group sizes
Groups by species
Active groups
Derived from the latest [map-state] dump. Solo bots are excluded — count shown in summary cards.
Population over time
AI series sourced from [bots] census line per snapshot.
Player series is event-driven from spawn/death/disconnect [server] peer lines, plotted as a step line.
Long windows downsample server-side to ~400 buckets (avg per bucket) so the chart stays responsive.
Age distribution by species
Buckets by growth fraction (size − baby) ÷ (adult − baby): cub < 33 %,
juvenile 33–67 %, adult ≥ 67 %, alpha = is_alpha flag (size ≈ adult × 1.5).
Aggregated across the latest [map-state] dump of every selected deploy.
Deaths per game-day
Warnings
Chat log
Global text chat from every connected player. Searched server-side
against message + sender (case-insensitive, indexed). Cheat
commands (/wisdomof* family) are filtered before they
reach the log. Cross-deploy by default — narrow with the date range
/ deploy picker.
Pride-hunt funnel (Lion only)
rallies → spots → challenges → kills. Conversion ratio per stage in cards above.
Predator × prey — kills by species (stacked)
x-axis = prey species. Stacked colour = predator. Replaces the per-predator triplet + the predator-totals bar — column heights = matrix row totals; segment heights = matrix cell values.
Killer → victim matrix (combat only)
Hero of the matrix sub-tab. Row totals = deaths by species (replaces the standalone bar); column totals = predator kills (replaces the predator-kills bar on Summary).
Species death × phase (day vs night)
Cause of death × species (stacked)
Victim avg state at death
Avg growth & size at event
Strength ranking
Alpha-vs-alpha win differential. Score = wins − losses against other alphas; bot-only kills don't count.
Player-killing prowess
Net result against human players (player kills − player deaths). Negative means humans dominate this alpha; positive = the alpha is hunting players successfully.
Kills vs deaths
Total kills + total deaths across all opponents.
Alphas — full board
Score = kills − 2 × deaths. Alphas respawn at dawn with fresh names; same name across deploys = same character. Click a row to highlight their events below.
Alpha vs alpha — rivalries
Newest first. Loser respawns at the next in-game dawn.
Player challenges
Every time a human player traded blows with an alpha and one side fell.
Snapshot perf live perf log series
Avg packet size (B)
Total bytes / snapshot-tick
Food + corpses
Players-online line moved to World › Ecosystem — pop-series there shows humans + bots stacked, richer than the line that lived here.
Netstat windows [netstat] dumps every 30 s · A/B via deploy picker
Sourced from [netstat] blocks (auto-emit every 30 s + on the /netstat chat command).
Multi-deploy is the A/B view here — pick Phase-0 + Phase-1 deploys in the header to see before/after side-by-side. All ratios are SUM/SUM to dodge Simpson's-paradox traps when player counts differ between deploys.
Snapshot pipeline health
Per-packet bytes avg/max vs the 1100 B SCTP single-datagram cap. Twin axis: byte_cap_sat % (encoder bailed because next entity wouldn't fit). Encode-µs avg/max → cards above.
FX filter savings — out-of-view blocks by kind, per window
Stacked bars per window. High = good. Each unit is a cosmetic FX RPC the Phase 1 _peers_in_view_of filter blocked from going to a peer outside view radius — bytes / packets that did not fly. Counter is pre-filter (all peers); the filter cuts before the wire. recv_eat_anim dominates because grazing fires fastest.
FX regression detector — actual bytes that flew, per kind
actual_sent = bytes − oov_bytes per kind per window. This is the real regression signal: should scale linearly with peer count and average actor density. Spike without proportional player climb = filter regression (someone added a bare .rpc() at a new FX site and missed the _peers_in_view_of wrapper).
Per-kind aggregate (selected window)
Per-peer bufferedAmount peak — heatmap
Cells: peak bytes queued in the WebRTC SCTP send buffer per peer per window. ≥ 1 KB = noticeable; ≥ 16 KB = backpressure threshold; ≥ 64 KB = client falling badly behind. Each row is one peer (labelled by latest spawn name); each column one window.
Ping median vs byte_cap_sat %
Each point = one (window × peer ping_stat). Cluster in the upper-right = saturation correlates with felt latency; flat = saturation harmless.
Ping median vs FX out-of-view count
Bursts of OOV FX bytes correlated with latency spikes confirms the "guard saves felt latency" thesis.
Saves backup savanah-saves daily mirror · /tmp/savanah/backups.jsonl
Player saves + Discord identity store rsynced from
~/.local/share/godot/app_userdata/Savanah/ into the private
majicmaj/savanah-saves repo by savanah-backup.timer
(03:17 UTC daily, append-forever). Status badges:
pushed = new commit,
no_changes = idempotent no-op,
skipped = lock held / source missing,
error = backup failed (check journal).
Repo size over time
repo_bytes = du -sb ~/savanah-saves/.git at each run; tracks cumulative history size. working_bytes = full checkout incl. saves+identity.
Recent runs
Newest first. Failed rows show the failing command + exit code in msg; on prod, full stderr is in journalctl --user -u savanah-backup.
Species base stats — baby / adult / alpha
Derived from scripts/sim/constants.gd. No per-peer entropy applied (spawned animals roll ±10% on speed / damage / HP / size); alpha row reflects deterministic AI_ALPHA_STAT_MULT = 1.5. DPS = Q-attack damage ÷ ATTACK_COOLDOWN. Click any column header to sort.
Time to adult — eating strategies
Baby → adult arc per species, simulated against three eating strategies. Numbers derived live from scripts/sim/constants.gd + net.gd (BITE_GROWTH_FRACTION, GROWTH_SECONDS, Well Fed buff hooks). Each cycle includes the WF lockout (60 s, hunger frozen) and the on-expire −25% hunger drop.
How the strategies differ
- Ideal — refill the moment Well Fed expires, so the +1% growth bonus fires every cycle. Optimal but assumes infinite food + zero travel time.
- Plan — wait for hunger to drain back to 25% before re-feeding. Gives one WF trigger per long cycle; passive growth + WF bonus carry the rest.
- Worst — only top up halfway (0 → 50 %) so Well Fed never fires. Bites still grow you; passive baseline runs in parallel. Slowest pure-eating arc, but still beats no-eating (5 h passive floor).
Skill-tree perks — per species
One shared 4-branch skill tree (Offense / Survival / Mobility / Hybrid). At each growth milestone you're offered a rarity-rolled pick; T1 perks unlock at spawn, T2/T3 need a prior pick in the same branch. A few perks are gated by species — below, each animal shows only the perks it can actually be offered. Flat fields stack additively; multiplicative fields chain. Derived live from scripts/sim/constants.gd PERKS + the skill_tree.gd gating rules.
Pickup berries + buffs
Spinning cubes scattered on land. E to eat. Re-eating extends timer instead of stacking magnitude. Synthetic buffs (Well Fed, Alpha Slayer) listed at bottom — not spawned in the world.
POI bias — which item kinds spawn where
Each item kind has a preferred POI biome (ITEM_BIOME_PREF in scripts/sim/constants.gd). On every spawn the engine rolls ITEM_POI_SPAWN_BIAS chance to place the drop inside a matching named POI; failing that, ITEM_GRASS_SPAWN_BIAS for grass-tuft jitter; otherwise uniform anywhere. POI bias is positional only — it does not change the per-tier rarity roll or the global ITEM_CAP.
Leaderboard rank — percentile placement
The big gem on the leaderboard's Rank board is a relative placement: the active player pool is sliced top-down across Bronze→Diamond. Recomputed every fetch — moving up the board can move your gem. Inactive players (last seen outside the active window) drop to Unranked so they can't permanently block higher tiers.
Rules
Mirrors tools/dashboard/ranks.py::assign_percentile_tiers. Worked examples in the table above show how the slice rounds at small N — N=100 gives clean 1/4/5/15/25/50; smaller pools collapse via the floor rule.
Player levels
The small Lvl chip on the leaderboard is your career level — earned from quality runs, not raw playtime. A run "qualifies" for a level when its survival time meets that level's minimum (scaled by the species multiplier below — fragile species need shorter runs, well-protected species need longer). Promote to a level once you have enough qualifying runs. Levels are absolute and never drop; they don't depend on what other players are doing. Mirrors scripts/sim/ranks.gd on the game side.
Per-species duration multiplier
Applied to the per-level minimum-run-survival threshold. Lion is the baseline (1.00×). Greater than 1 = harder to qualify on that species; less than 1 = easier. Tunable in scripts/sim/constants.gd::RANK_SPECIES_DURATION_MULT — adjust there and any subsequent level refresh on the game server picks up the new value.
Cheats & debug commands
Type these in the in-game chat box. Cheat commands work only on a local server you host with cheats enabled — tick Enable cheats in the Host Local Server dialog, or toggle at runtime with /cheats on / /cheats off (bare /cheats flips it; /cheats list prints the catalog in-chat). They are permanently locked on the public online server. Client debug toggles are local-only visual aids and work anywhere. Mirrors _handle_local_chat_command in scripts/net/net.gd.