ADVENTURE LOG

Other Systems › Adventure Log
RETIRED 2026-10-22 (D, Tower rulings form card T31-2). “We are retiring and removing the player side adventure log from the game. The player action log is server side only now. Remove the icon from the menu.” The game no longer has this window, its menu plate, its Settings section, its exports or its upload. What players re-read from it — the floor-101 letter and each system’s UNLOCK line — lives on as Settings > Notices (Settings). Everything below describes the retired system and is kept as its record; the client code is archived in the client repo under archive/adventure-log/.

Archive — the system as it was (2026-07 → 2026-10-22)

Everything from here to the changelog is the record of the retired system, kept in the present tense it was written in. None of it is in the game today; the server keeps an account action log that only the GM console reads.

A permanent, chapter-bound chronicle of everything that has ever happened on the account — hatches, advancements, breedings, floor clears, boss kills, deaths, realm events, and every battle fought. The log itself never forgets; only the depth of battle detail is pruned over time.

Architecture — events, not text

Chapters

Event types

EventParamsChapter line (rendered)
HATCHname, grade, sex, eggGrade, pen or floor, bred, types"A creature hatched: Rare Fawn (F), bred in pen 2 from an Uncommon egg."
HATCH_BATCHc (count), g (grade tally), s (species breakdown), m (mutants)"Hatched 25 eggs (17 Worn, 6 Common, 1 Uncommon, 1 Rare) → 3× Flicker, 2× Scorpling, 2× Ignis, 2× Fry, 1× Fledgling, 1× Tempest, +14 more · 1 mutant."
BREEDa, b, ag/bg (parent grades), pen, grade, species"Riptide [Epic] and Naiad [Rare] in pen 3 produced a Rare Fry egg."
ADVANCEfrom, to, lv, stage"Fawn advanced into Hart at level 18 · stage 2."
MUTANT_HATCHsame fields as HATCH"A mutant variant emerged: Epic Nommo (M), bred in pen 1 from a Rare egg."
LEVEL_UPname, lv, from"Ingot reached level 33 (from 32)."
BATTLEbattle_id + summarySummary row with a Battle Log button.
FLOOR_CLEARfloor"Portal opened — floor <n> cleared."
BOSS_DEFEATEDname, floor, rounds, survivors"Pelagos, the Trench Warden has fallen on floor 20 in 7 rounds · 2/3 standing."
DEATHname, floor, killer, rounds, pipeline"Monkey died on floor 31 to Gorgon — the streak is lost."
UPGRADEn, f (from), t (to), k (1 win / 0 fail)"Sublime Onyx graded Rare → Epic." / "Grading Soul of the Mage Epic → Legendary failed — the bundle was spent."
DISMANTLEc (count), g (grade tally), y (yield), m (mode)"Dismantled 37 items (22 Worn, 12 Common, 3 Rare) → +148 Scrap · +12 Essence."
PURCHASEw (what), q (qty), p (price), u (currency)"Bought the Soul of the Archer for 2,500 Ichor."
SOCKETn (socketable), h (host)"Rough Garnet socketed into Arming Sword."
DEVOURn (consumer), f (fodder), k (kind: mon / pellet / mutagen / essence), x (xp), o→g (grade), m (mutant)"Reliquary devoured Reliquary — +12,000 XP, grade Worn → Rare, became a mutant." / "Reliquary consumed a Blood Pellet — +1,000 XP."
LETTERf (floor)"A letter was waiting on floor 101. Click to read it again." The row reopens the letter.
EQUIP_UPGRADEnameReserved - nothing writes it yet. A renderer exists ("<name> was forged."); forging currently logs no event.
LEAGUE_EVENTtextReserved - nothing writes it yet. A renderer exists (the text verbatim).
REALM_EVENTseason, actionNever built - nothing wrote it and there was no renderer; realms and seasons left the game on 2026-08-28 (see Death & Realms).

HATCH ALL is one event

Param IDs reference codex CSVs; the log stores no names or descriptions. Ordinary per-battle XP ticks are not logged as separate events — they live inside the battle summary.

Battle Logs (sub-item of the Adventure Log)

Renderers degrade, never break. Every field above is optional at render time: a log written before a field existed still reads correctly, it just says less — "A creature hatched: Cairn." is a valid line from an old save. Adding a field to an event therefore never needs a migration, and old chapters are never rewritten.

Viewing a battle log

1 — Tap

The player taps the Battle Log button on a battle summary row in a chapter.

2 — Fetch & decompress

The blob is fetched by battle_id and decompressed in memory (<1 ms for a 1–3 KB blob) — only that one battle, never the rest of the history.

3 — Render

Turn structs are deserialized and rendered into readable turn-by-turn text via templates + codex data. Compression is invisible at the UI layer.

Retention policy

Battle classFull turn detailSummary row
Boss battlesKept foreverForever
PvP battlesKept foreverForever
Ordinary wild battlesLast N days / last N battles, then compacted awayForever

Storage budget — compress first, shed only at the cap

Nothing is deleted until the archive is full. Every event and every battle's numbers are kept, because they are balancing data to be pulled off real players; what changes with age is how they are stored. The archive is capped at 1,029 KiB and sheds its oldest whole blocks past that (The budget, below).

AgeFormCost
Newest 500 eventsExpanded in advChapters, rendered in the log window~68 bytes each
Older eventsPacked one-per-line and gzipped into advArc blocks (base64). Falls back to the plain packed string where CompressionStream is unavailable — still complete, just larger~7 bytes each, a ~10× cut
Newest 25 battles (legacy saves only)Full round-by-round rendered log, last 10 rounds — only fights written before the packed store; new fights are packed (How a fight is stored)~5 KB each
Battles 26–200Compact row: result, floor, XP, Food, participants. The prose goes, the numbers stay~290 bytes each
Older battlesArchived into advArc as BATTLEROW entries. The chapter line falls back to the BATTLE event's own stub~7 bytes each

Measured on the two worst real accounts, with the full history intact in both cases: 905 KB → 373 KB (500 live events + 3,380 archived in 38 KB) and 638 KB → 317 KB. Applied on write and on load, so an oversized save shrinks the moment it is opened.

High-frequency events use one-letter keys — {c:37,g:{Worn:22},y:'+148 Scrap'} — and a bulk action is always one event: dismantling 113 eggs costs one line with a grade tally, never 113 lines.

Reading the archive

The archive is DEVICE-LOCAL and does not reach the server. The Stage 4a save diet strips advArc, advChapters, advBattles and floors from every upload — they were 89% of a real save's bytes, and advArc riding the upload was itself the 2026-08-01 save-ceiling incident (68,560 B of a 127,115 B save; /api/save refuses over 131,072 B). So /api/backup/export does not carry log history, and a new device starts with an empty log. Pulling history off a player is the on-demand advlog extraction round (admin-backend.html §8). Each block is {n, z} (gzip, base64) or {n, t} (plain), plus the day index below. Unpack a block's text into events by splitting lines on \n and fields on \x01 — ts, TYPE, JSON params:

import gzip, base64, json
for b in save.get('advArc', []):
    txt = gzip.decompress(base64.b64decode(b['z'])).decode() if 'z' in b else b['t']
    for line in txt.split('\n'):
        ts, typ, params = line.split('\x01'); ev = (ts, typ, json.loads(params))

The day index — how a per-day view is possible at all

On release since 0.64. A packed event carries only ts = HH:MM. The DATE lived on the chapter object, and advTrim drops a chapter the moment its last event is archived — so the archive was a single undated stream. Nothing was ever lost; the history simply could not be cut into days. That is the whole of “the log only shows a day”.

Retrofitting an archive written before the index

The dates were never gone — an earlier note on this page said they were, and that was wrong. Every BATTLE and BATTLEROW carries params.id / params.i = 'B-' + Date.now().toString(36), the exact epoch millisecond at which advPush minted the dayKey, and new Date(ms).toDateString() is that same call — so it reproduces the original key exactly on the device that wrote it.

Only entries with no battle anywhere before or after them stay undated, and fall to the reader’s “Earlier” tile.

The day grid — what the player sees

On release since 0.64. The log opens on a grid of tiles, one per day, newest first. Archived days are included — which is the whole point, because a day whose events had all been packed away used to disappear from the list entirely, and that is what “the log only keeps a day” actually was.

The budget — what happens when the device fills

On release since 0.64. The archive is capped at 1,029 KiB and sheds its oldest whole blocks when it exceeds that.

How a fight is stored

On release since 0.64 (abfb7fa). A fight used to be kept as rendered English in plain JSON — about 5 KB each, and only 25 kept, which on autoplay is under an hour of history. Packed, a fight is 44 bytes, and every number stored is the number that happened.

At 44 bytes a fight, 200 fights a day costs 9 KB and a thousand costs 43 KB. A 512 KiB budget holds around 12,000 fights.

The budget bounds the whole store (lane I, 32391b5, on dev): the sealed blocks, the pending streams, the fights kept as prose because they failed the round trip (blRaw) and the word dictionaries (blW, blS) all count toward BL_BUDGET. Past it the oldest sealed blocks and raw fights are shed together, oldest first, and the newest block always stays. The dictionaries cannot lose single entries, because sealed blocks index into them, so once they pass a quarter of the budget they are rebuilt from the fights still stored.

Changelog