VikingStoryteller
Records Valheim adventures with combat contributions, player stats, activity summaries and F8 leaderboards. Includes searchable logs, bounded retention and optional Epic Loot gear reporting.Viking Storyteller
Experimental 0.4.3, built and tested against Valheim 1.0.15. Keep a factual record of your Vikings' adventures: kills, deaths, damage contributions, assists, skills, building, crafting, gathering, AFK estimates, biome crossings and exact-creature revenge.
Press F8 in game to open your own server statistics. The package also includes optional tools for a searchable local HTML overview and structured logs for humans and analysis tools.
Storyteller is read-only: it records gameplay without changing it or providing admin commands. It does not send automatic chat messages.
For players and server operators
Use Storyteller with your own world and mod list. It discovers the active world and connected players through Valheim; no server address, player allowlist, world seed, private admin plugin, or hosted account is required. Records stay on the collector host. Clients send gameplay reports only to the game server they joined.
Install it on a dedicated server and participating clients, or use a player-hosted game as the collector. Each world has separate records. Preferences belong in BepInEx configuration; the package contains no preconfigured server settings. Existing configuration is preserved on upgrades. Server operators control collection, shared leaderboards and retention; clients control reporting, snapshots, optional Epic Loot metadata and the F8 hotkey.
Epic Loot is optional, and the reporting tools are optional. You do not need the author's other mods or server-management setup. Automatic updates, restarts and administrator chat/teleport commands are not part of this package.
Tested environment: Windows dedicated-server Valheim 1.0.15. Player-hosted and Linux installations are intended use cases but have not received equivalent native smoke testing. The included Python tools take explicit paths and work independently of the game process. Report game version, mod version, hosting platform and relevant errors when seeking help; remove account identifiers and private gameplay data before posting logs.
Compare players in F8
The Leaderboard tab compares collected world totals for creature killing blows, deaths, assists, creature damage, trees felled, mining hits, placement actions, crafts, doors opened/closed/combined and active minutes. Placements include terrain and plants; mining hits are not ore quantities. The server setting Enable leaderboards controls availability. Ties share ranks; views show the top 50 plus your own rank. Unknown metric coverage is omitted. Choose Today, Last 7 days (including today), or All time. Daily boundaries use the server timezone recorded when compact history begins. Rankings use available observations, not equal-duration contests. Totals follow the reporting account and display its latest character name. Deaths rank highest counts first. Native gathering counters are distinct from kills and are not added across overlapping tiers. Detailed player stats remain your own; shared rankings include character names and aggregate values.
Optional Epic Loot support
Epic Loot is detected automatically when installed, with no hard dependency. Equipped items report rarity, quality and enchantment identifiers/values when gear changes; ordinary snapshots do not repeat the full gear payload. F8 Skills & condition shows the last reported equipped gear. Missing, disabled or incompatible Epic Loot leaves ordinary reporting working. Optional Epic Loot equipment controls this integration. Gear is sampled at the snapshot interval; temporary changes between samples can be missed. World loot and supported item movements are recorded separately as described below. Socket-specific effects and inferred damage bonuses are not tracked.
Loot and item movements
Storyteller records observed creature loot creation and successful world pickups, including partial stacks, with item name, quantity, quality, time, position and optional Epic Loot rarity/effects. It links an acquired item to a creature only when exact world-item identity and metadata agree. Player-dropped items can be linked to their dropping player and are labeled separately from creature loot. Existing items, expired origins and unsupported drop paths remain unknown origin; equipping gear never proves where it came from.
The item ledger observes manual inventory transfers, player drops, ordinary crafting, building/planting consumption, and food/mead use. If installed, Tidy Chests quick deposit is detected through its actual transfer method regardless of hotkey, including the backtick binding. BestAutoSort reports accepted/partial chest changes and resolves the actor from the transaction sender. Direct locally owned chest consumption records the recipe or plant when the exact requirement list matches. FeedLikeGrandma feeder removals are labeled as automation without blaming a player. Rejected or duplicate requests are not item movements. A chest-side commit does not prove delivery to the player or consumption in a particular recipe. Unsupported custom storage paths can remain unobserved.
Records identify container network IDs and positions rather than assuming similarly named boxes are the same chest. Item custom data is never modified. Its fingerprint distinguishes otherwise similar enchanted items during transfer observation; the private payload is not logged. No complete chest inventories are uploaded. Routine item events go into the searchable detail stream; enchanted-item events stay in the story stream. Search by item, player, container ID or location. This is evidence of observed transactions, not a guaranteed complete inventory balance or a permanent item audit.
Install the new release on the server and participating clients for these reports. Epic Loot, Tidy Chests and BestAutoSort remain optional. [Loot] controls world loot and ordinary stackable items; [Ledger] controls transfers. Origin correlation retains at most 4,096 drops and 4,096 creature carriers for up to 86,400 simulation seconds. Logs share the existing retention budgets; expired transactions cannot be recovered from lifetime statistics.
Storage and lifetime history
Default per-world retention: 64 MiB raw detail, 64 MiB raw story, and 128 MiB compressed archives. Detail segments age out after seven days; story segments and compressed archives after thirty days. Whichever age or size limit is reached first applies. Settings are in [Retention]. Checks run about once per minute; active segments and pending batches can briefly exceed the budgets.
Lifetime collected stats remain in -stats.json and a compact -ledger.json checkpoint. Retention verifies that checkpoint, compresses closed segments and verifies their decompressed checksum before removing the raw source. Archives also expire, so disk usage does not grow indefinitely. Expired detailed chronology cannot be reconstructed from totals. Totals are replaced from cumulative state, never re-added from archives. A failed historical import or checkpoint prevents cleanup. Existing logs with missing/unreadable cumulative stats persist a retention hold for history recovery; inspect -retention.json and collector failures if disk space stops being reclaimed. State dictionaries are bounded separately. These budgets exclude world saves, administrator backups and exported reports.
HTML/JSON reports use retained raw logs by default; the CLI option --include-archives includes retained compressed logs. Lifetime ledger data is separate from date-filtered event totals. Generated day/player exports are pruned on rebuild so stale exported history does not quietly accumulate.
Compact history and boss encounters
Compact daily summaries retain kills, deaths, assists, creature damage, activity, placements, emotes and selected gathering/crafting/door counters after detailed logs expire. The defaults keep 60 days, then consolidate older days into monthly totals retained for 24 calendar months. Both are configurable. Each period caps at 128 player records; daily creature categories and notable events are bounded. Lifetime totals remain separate when monthly records expire.
The first startup imports available raw event/detail logs into period history once, with event-ID deduplication and an atomic marker. It does not replay lifetime totals or invent unavailable history. Compressed archives are not included in this automatic import. An import failure leaves existing data intact and pauses retention. Imports are limited to two million relevant events; malformed files require repair before retrying. The HTML overview provides selectable daily/monthly summaries separate from its event-based period totals.
A boss defeat saves an observed encounter summary: duration, damage shares grouped across player respawns, killing blow, healing, unassigned damage and player deaths linked to that exact boss. It keeps the latest 64 summaries in compact state and emits a searchable boss_summary event. Summaries cover the final observed encounter segment; full recovery, long damage gaps, collector restarts or missing evidence can shorten it. They are not a reconstruction of unseen combat.
Health interpretation
Health clamped by a falling maximum (including food decay) is recorded separately from damage and excluded from new combat percentages. Clamps do not emit one log line per tick. Actual enemy hits, poison, falls and other health losses still count. All reporting clients need this version for that distinction. Older collected damage totals may include food-related health loss; they are preserved because their original causal context cannot be recovered reliably.
Poison and burning ticks gain source attribution when the reporter observes their application and retains reliable provenance. Accepted poison replacements update their source; ignored weaker poison applications do not. Fire/spirit pools with multiple or unknown sources remain unassigned. Existing effects first seen after an ownership change, a restart, or an attribution-buffer reset remain unknown until a new source can be established. Lethal ticks retain their exact hit evidence until a matching deferred death is observed. Attribution changes reports only, never game damage or status effects. Previously unattributed historical ticks are preserved.
Install and update
Install the same version on the server and participating clients, then restart them. Dependencies remain BepInExPack Valheim 5.4.2350 and JsonDotNET 13.0.4. No additional UI mod or Jotunn dependency is required.
Clients without Storyteller can still join. Version 0.1 clients provide deaths; 0.2 clients provide enhanced combat/activity/action reports. A 0.4 client negotiates 0.4, then falls back through 0.3, enhanced 0.2 and death-only 0.1. The new stats window, native counters and snapshots require a 0.3 server. Missing collectors receive at most four negotiation attempts; reporting requires acceptance.
On the first collector startup without a completed import marker, retained 0.1 event logs automatically backfill identifiable kills and deaths into F8. A completion marker is saved atomically with the totals, preventing repeat imports across restarts. Current progress is preserved, duplicates are skipped, and unreadable logs defer the whole import. The Combat tab labels imported totals. Only logs present during the first completed import are included. Deleted history and damage, assists, AFK, work or skill stats absent from 0.1 cannot be reconstructed. The HTML tools can still search and summarize retained 0.1 events. Current skill levels are a snapshot, not levels earned on this server. Keep your existing world and Storyteller files when updating normally.
In-game stats window
F8 opens the window; F8, Escape or Close dismisses it. The hotkey is configurable if another mod already uses it. The window blocks normal movement/attack/look input while open and restores control on close. It refreshes from the collector about every 15 seconds while open, with a manual refresh button.
- Combat: creature killing blows, deaths, assists, player killing blows, actual damage dealt/taken and observed health gains.
- Adventure: active/AFK/unobserved time, placed pieces, emotes and searchable native/item counters.
- Skills & Condition: last observed biome, health, stamina, eitr, armor, weight, comfort, weapon, food and skill levels.
These are your world's accumulated collected records, not a live character sheet or necessarily your entire character history. Missing coverage says “Not collected yet.” Snapshots display their timestamp. The server uses your authenticated connection to retrieve your records. Up to 384 metric rows are returned, with additional entries retained in server records.
Ten-minute work summaries
Version 0.4 groups routine work into one summary per reporting player approximately every ten minutes, plus partial summaries when they disconnect or the collector stops. Kills, deaths, revenge, emotes and biome transitions stay individual story events.
- Building uses completed placements, with their actual positions and piece names.
- Mining and woodcutting use the game's hit counters, not ore yielded or trees felled. Crafting, upgrades, harvesting and fishing use their native action counters.
- Work positions are captured when the action occurs. Separate 25-metre 3D cells retain separate work sites; map coordinates are X/Z, with Y available in JSON. Mining/tree positions describe the player, not the exact resource object.
- The main activity uses distinct 30-second bins containing work evidence. It never compares a hundred pickaxe hits directly with two buildings or claims exact minutes spent. Tied categories are mixed. It describes observed work, not combat/travel dominance across all gameplay.
- Combat can link work observed in the preceding 60 seconds and, for deaths, within 35 metres. This says recent work nearby, not proof someone was attacked while building. Delayed work batches can leave this link absent.
- Summaries show protocol/reporting coverage, partial windows, active/AFK/unknown time and references to combat events. Referenced kills/deaths must not be added again to individual event counts.
Clients batch work about every 15 seconds; only story output is grouped by ten minutes. Windows use server receipt times; a delayed batch can span a boundary. Pending windows checkpoint every 15 seconds and recover as partial summaries after an unexpected restart. Up to the last checkpoint interval can be lost on a crash. Stable summary IDs allow deduplication after replayed recovery. Bounded buffers can drop evidence under sustained overload, reported in status.
F8's Adventure tab shows your last completed work summary and up to twelve sites. The HTML overview includes all retained sites, links to combat and searchable underlying evidence. Detailed work requires 0.4 on both sides; an older client with no work reports is not labelled idle.
Native gameplay statistics
Observe changes to all 205 actual PlayerStatType categories in the tested build: crafting/upgrades, mining, woodcutting, gathering, food, fishing, farming, taming, pets, building, travel, sailing, portals, boss credit and more. Named dictionaries retain what was picked up, crafted, harvested, eaten or placed. Only the currently connected local character profile is observed.
Counters are read before/after completed game methods and aggregated into bounded batches, normally every 15 seconds. Imported totals never become new progress. A respawn retains the same profile's pending progress; profile, connection and world changes clear it. Native death/kill categories can overlap the independently observed combat records: do not add them together. Increment and set changes remain separate, including decreases/resets.
Native callbacks can miss actions from mods that bypass those game methods. Counters use the game's units. Limited dictionary/batch sizes and backpressure favor gameplay over collecting every event. If optional native hooks fail, combat reporting remains enabled and status reports the missing feature.
Snapshots, skills, sessions and biomes
Snapshots normally arrive every 30 seconds. They include current health/max health, stamina, eitr, armor, carry/max weight, comfort, equipped weapon/quality, food and remaining duration, up to 128 skills, position, biome and sampled movement/condition flags. During detected AFK, routine snapshots slow to 120 seconds by default; resuming input or changing biome triggers an earlier sample. Combat reporting continues normally.
The client checks its biome every two seconds and can send an earlier snapshot when it changes. Very brief crossings can be missed. A biome transition does not establish travel method or the exact crossing instant.
Skill changes compare nearby samples of the same character within one connection. The first sample, a different character, clock rollback or a gap over 120 seconds starts a baseline. Newly seen skills require a baseline too. Gains/losses are observed differences; Storyteller does not guess whether a death, command or another mod caused them.
Session events describe when the collector observed a ready reporting connection and its end. Session duration includes AFK and excludes pre-negotiation time; it is not active play time. An abrupt process failure may leave a session without an end record.
Combat contributions and revenge
Each character's network owner observes resulting health changes. Damage measures HP actually removed after mitigation, caps overkill at remaining HP and ignores zero-loss hits. Each player/creature death can contain HP loss, hit count and percentage by source, with a separate killing blow. Unknown attackers and environmental causes remain unknown or explicit cause buckets; damage-over-time attribution only follows identifying game data.
An encounter resets on observed full recovery or after 120 seconds without recorded health loss. Partial healing can make total HP lost exceed maximum health. Percentages use observed encounter damage, not guaranteed complete damage or a causal percentage of blame. Health continuity gaps and out-of-order reports are flagged. Missing owners, restarts, queue limits or mods bypassing observed methods can leave gaps. In-progress encounters do not survive a restart.
An assist means recorded damage in the creature's final observed encounter without its killing blow; each account receives at most one assist per creature, including across respawns. Exact-creature revenge links require matching world and entity IDs, not just the same creature species. Health gains can include food and other HP changes, not just healing abilities.
AFK, emotes, building and removals
AFK defaults to five minutes without observed in-game input. The grace period counts as active. Activity is reported about every 15 seconds; long stalled/suspended intervals are explicitly unobserved rather than assigned to active or AFK time. This is a heuristic, not proof of physical presence. There are no kicks.
The client reads input-activity timing and focused controller activity, never key contents or chat text. Emotes count as activity. Incoming damage and boat movement do not prove someone is present.
Successful emotes and completed Player.PlacePiece calls record the local player, action, time and position. The server also records recognized boats and built structures when their network objects are permanently removed. Removal cannot distinguish destruction, dismantling or admin deletion, and does not identify a culprit. Ordinary client unloading is not permanent removal. Unrecognized/custom object types can be absent.
Server files
Under BepInEx/config/VikingStoryteller/:
README-v5.mdandschema-v5.json: starting points and field meanings, including supported native statistics.<world-id>-events.jsonl: one versioned event per line, with stable IDs, UTC observation time, game time, source evidence and links.<world-id>-detail.jsonl: routine evidence: individual placements, health, metrics, work samples, snapshots and activity. Search here when investigating a summary.<world-id>-work.json: pending work-window checkpoint.<world-id>-loot.json: bounded item-origin correlation checkpoint, not a chest inventory.<world-id>-state.json: compact death/revenge correlation and deduplication.<world-id>-stats.json: cumulative per-player totals and latest snapshots.status.json: version, feature readiness, protocol coverage, accepted/rejected/dropped counts and queues.
Events flush about every two seconds. State saves about every 15 seconds and on normal unload. Event/detail logs rotate near 4 MiB or at the next write after a UTC date change. Configurable age and size retention bounds both raw and compressed history; see below. Crashes can lose buffered records, and old rotated files can leave history incomplete. Raw local records contain platform account identifiers, names, in-game locations and gameplay; do not publish them.
Optional local HTML and search tools
The tools/ folder contains dependency-free Python 3.10+ tools. These are run separately by the operator; the mod does not launch a web server. Point --server at an ordinary dedicated-server folder, the collector folder itself, or a parent directory containing server/BepInEx/.... Use your numeric world ID from the log filenames.
python data_hub.py --server "/path/to/valheim-server" --world WORLD_ID --output "/path/to/story-reports"
python preview.py --server "/path/to/valheim-server" --world WORLD_ID --output "/path/to/story-reports" --port 8766
The first command creates overview.html, summary.json, compact history.json, bosses.json and ledger.json, report.json, events.jsonl, an indexed events.sqlite, UTC day partitions, per-player exports and schema.json. The HTML is self-contained. The second optionally serves it on 127.0.0.1 only; open http://127.0.0.1:8766/ on the same computer and use Refresh data. No external scripts, analytics or fonts are loaded.
Filter by period, player, event type and text; drill into damage percentages, source lines, skill snapshots and exact-event links. Routine telemetry can be hidden from the timeline. Matching client/server biome observations remain as evidence but count once. The optional managed-server observer includes records from the local maintenance/admin/stories folder when present.
python query.py --database "/path/to/story-reports/events.sqlite" --player "Your Viking" --kind player_death --limit 20
python query.py --database "/path/to/story-reports/events.sqlite" --text Lox --since 2026-09-20T00:00:00-05:00
Derived exports omit platform account IDs and session tokens, retain source file/line references, and distinguish unavailable values from zero. UTC observation time is the query clock; the browser displays local time. Exports reflect retained input files; stale generated day/player files are pruned on rebuilding. Source logs remain untouched. Gameplay exports are private and are not included in this package.
Configuration
JacobsValheim.VikingStoryteller.cfg preserves existing master switches:
Send gameplay death reports: master client reporting/negotiation switch, despite its historical name.Collect gameplay death reports: master server collection and own-stats service switch.AFK after seconds without input: default 300, range 60–3600.Report player snapshots: default enabled.Player snapshot interval seconds: default 30, range 15–300; observed biome changes can report sooner.Open stats hotkey: default F8.
Restart the affected game/server after editing configuration. The optional Python preview opens only its explicitly requested local port.
Validation and limits
Model and reporting tests cover identity, damage math, correlation, AFK, snapshots, native deltas, respawn/profile boundaries, bounded own-stat views, provenance, SQLite search and missing-data handling. Isolated native-game tests exercise real damage/death/removal, patched profile increments, protocol v1/v2/v3/v4 negotiation, serialization, own-stats replies, input gating, and rejection of wrong identities, tokens, replayed or invalid reports. The HTML includes grouped work and detailed evidence.
Normal multi-PC play and visual/input testing of the new in-game window remain necessary. Headless native tests verify its hooks and server data path, not the rendered client window. This is an experimental release; missing or conflicting mods can leave incomplete reports. The server validates identity, world, session token, timestamp, ownership and payload bounds, but client observations are not anti-cheat proof.
Correlation retains 2,000 deaths and up to 8,192 victim IDs. Up to 2,048 encounters and 64 source buckets per encounter are retained. Native buffers cap at 512 names, cumulative native metrics at 4,096 names per player. World rollback can reuse IDs: preserve a backup, then archive/reset corresponding Storyteller state if you deliberately want a fresh reporting history.
MIT source and a Windows build script are included. Build against your own Valheim/BepInEx/Harmony assemblies. Game assemblies and private test/admin tools are not redistributed.


