The game
The other pages ask whether the data is sound. This page asks what happened in the game. Same Parquet, different grain.
Every number here is the result of a query, and its SQL syntax sits above its result for transparency. There are no hardcoded values typed into the text.
Every number here is the result of a query. The queries are hidden in this view. Switch to Engineering, top right, to read the SQL above each result.
1. A run is not a page load
A page load opens when the browser loads the game and closes when it unloads. A run starts when the player enters gameplay and ends when they leave it. One page load can hold several runs, so the two grains give different answers to the same question.
SELECT runs::INTEGER AS runs,
characters_played::INTEGER AS characters,
maps_played::INTEGER AS maps,
total_sim_s,
avg_run_wall_s,
avg_run_sim_s,
max_run_wall_s,
min_kills::INTEGER AS min_kills,
max_kills::INTEGER AS max_kills,
avg_kills
FROM game_summary
2. How long is a run?
A run has two durations, and they differ by design. Wall time counts every second
of the run. Play time counts only the states where the simulation advances, so
it excludes the talent tree, the pause overlay and the stage transitions.
app_states.csv decides which states are which, not the pipeline.
SELECT run_id,
-- A short label for the axis. The full run_id stays in the table below,
-- so nothing is hidden; this only shortens what the chart prints.
'load ' || page_load_seq || ', run ' || run_seq AS run,
character_key AS character,
first_map,
maps_visited::INTEGER AS maps,
duration_s AS wall_s,
sim_active_s AS playing_s,
kills::INTEGER AS kills,
outcome
FROM fact_run
ORDER BY started_srv
The pale bar is wall time and the solid bar is time with the clock running. Everything between them is menus, pauses and cutscenes.
3. Which characters get played?
The table lists every documented character, including the ones nobody chose. That is a left join from the reference data, so an unplayed character shows as a zero rather than vanishing.
SELECT character_key AS character,
description,
runs::INTEGER AS runs,
sim_active_s AS playing_s,
best_kills::INTEGER AS best_kills,
avg_kills
FROM character_usage
4. How do the boss fights end?
No column here claims a boss died at the player's hands. The recorder emits
no defeat event, and the win state never occurs in this sample. So the pipeline reports the state
that followed each fight, and calls a reward screen progressed. That is the
closest available proxy for a defeat, and it is a proxy rather than evidence.
SELECT boss_key AS boss,
description,
encounters::INTEGER AS encounters,
progressed::INTEGER AS progressed,
died::INTEGER AS died,
won::INTEGER AS won,
unresolved::INTEGER AS unresolved
FROM boss_encounters_by_kind
The three cards are totals of three columns in the table above, so they add no new data.
5. How do runs end?
SELECT outcome,
runs::INTEGER AS runs,
avg_wall_s,
avg_sim_s,
best_kills::INTEGER AS best_kills
FROM run_outcomes
An unresolved run is one where the page load ended before the player left gameplay. The run was real. Its ending was never recorded, so the pipeline declines to guess one.
What this page cannot tell you
The sample is small, and the cards above count it. Read the shapes, not the averages.
Two limits come from the wire format rather than the sample size. There is no
boss defeat event, so progressed is the strongest word available. There is no
stage grain, so a run that walks three maps reports the span it covered and not
a row per map.