Projects

What I've Been Building

LudoSense

Game Analytics Platform

LudoSense is a game analytics platform I built to capture, process, and visualise player telemetry data for my game development passion. The system spans the full vertical stack: a GDScript SDK plugin that instruments the game client with event batching and retry-on-failure, a Python backend (FastAPI, Celery, Redis) that ingests and processes those events, a PostgreSQL database, and a React dashboard. This was originally hosted on AWS, but has since been taken down to avoid idle costs, and is now hosted on Railway. This allows me to incorporate analytics into any games that I make in the future, or even help other developers by providing them with an SDK and a README for how to set things up in their own projects.


The data pipeline follows a medallion architecture: raw events land in bronze tables exactly as the game client sends them, pipeline tasks clean and normalise them into silver tables, and gold tables aggregate the results into hourly, daily, weekly, or monthly metrics that power the dashboard. The gold layer is fully configurable in that developers can define what their events mean (using counters, funnels, duration distributions, segmented breakdowns, rates, or session aggregates), and the pipeline computes the results automatically. Adding a new metric means writing a definition, not changing the ingestion path. This makes it simple to instrument new game mechanics and get immediate feedback on how players are interacting with them.


After falling in love with a library organising game, I decided to make my own 3D game, Alchemy Shoppe Chaos, the first time ever working in 3D. This led to some interesting analytical results, all displayed and discussed on this page. Everything you see below is the real output of that pipeline, served as static JSON for the purposes of a portfolio piece, aside from one illustrative example. The data covers 7 unique players across multiple days of play, and highlights both performance metrics that matter to esports research as well as some player experience insights that are backed by psychology research. A small demonstration of this medallion architecture is available in the public GitHub repository.


PythonFastAPIPostgreSQLSQLAlchemy 2.0AlembicCeleryRedisReactRechartsRailwayTypeScriptGodot 4

Performance Profiling

Peripheral input metrics captured at 10Hz

LudoSense captures the same peripheral input metrics that esports research labs measure manually, including motor timing, visual scanning, aim precision, and performance degradation over a session. The difference here is that LudoSense computes these values automatically in the game's SDK, so no manual processing is required. The data is captured at 10Hz, but could easily be adjusted to capture at any rate. The charts below demonstrate three key performance profiles, measured in Alchemy Shoppe Chaos: aiming metrics, key transitions, and per-minute curves. The final chart shows a cross-session comparison of the per-minute curves demonstrating differences between players for actions per-minute, camera angular velocity, items shelved per-minute, and error rates.


Aim Metrics

283 ms

Median Acquisition Time

0.84

Median Aim Efficiency

834 ms

Median Dwell Time

1,346

Samples

Acquisition Time (ms)

n = 1,346

Measures time from first camera movement toward a target to pressing interact.

Aim Efficiency

n = 1,242

Demonstrates the ratio of direct angular displacement to total path swept.

Dwell Time (ms)

n = 1,346

Time spent aimed at a target before pressing interact.

Interactions above the 97.5th percentile are excluded as these represent deliberative pauses rather than motor targeting.

These three metrics break down each interaction into its specific motor components, search, approach, and commit, following the same targeting framework often used in esports performance research. The sharp peak below 500ms in the acquisition time histogram shows that most of the targeting is fast and automatic as this measures how quickly the player locates and orients to a target. Aim efficiency is the measurement of how accurately a player can aim at a target with values closer to 1.0 indicating aiming straight at the target with no correction. The 0.84 median value from this dataset indicates that the players are reasonably direct, but still make some corrective movements, which is consistent with a 3D environment where the target shelf isn't always immediately visible. Dwell time is the final aim metric calculated with longer dwell times suggesting a player is deliberating the placement, confirming the correct shelf before placement. The median value of 834ms for dwell time vs 283ms median acquisition time suggests that confirming the correct placement of an item demands more cognitive effort than locating the shelf in the first place. In an esports context, these same three metrics directly measure a player's motor fluency under pressure. The difference is that research labs typically derive them manually from gameplay that exports data to be analysed whereas LudoSense computes them automatically from the SDK at capture time.

Key Transitions

800 ms

Median Interval

1,111 ms

Mean Interval

3,199 ms

P95 Interval

5,128

Samples

Inter-Key Interval Distribution

n = 5,128

Distribution of time gaps between consecutive key presses across all sessions.

Intervals longer than 5 seconds are excluded by the export as breaks between bursts of activity rather than continuous input.


Transition Matrix

Counts of consecutive input actions. Rows are the action moved from, columns the action moved to.
From \ ToAuto FlyInteractItem HighlightDownLeftRightUp
Auto Fly0100103
Interact52141451147
Item Highlight0100000
Down0400011
Left0700012
Right01901204
Up0440021313
Transitions
0
5
28
88
214
Diagonal = same action repeated

Each cell counts how many times the row action was immediately followed by the column action within the same 5-second window.

These two visualisations capture complementary aspects of input behaviour: the interval distribution shows how fast a player issues consecutive inputs, while the transition matrix shows which action follows which. Together, they give a complete picture of input tempo and sequencing. The mass sitting between 200ms and 800ms indicates rapid, rhythmic input during active gameplay, with a long right tail where the player pauses to deliberate. This is a granular measure of motor fluency that maps directly to actions-per-minute in esports research. The transition matrix reveals the dominant gameplay loop in Alchemy Shoppe Chaos: Interact → Move → Interact, corresponding to picking up an item, moving to a shelf, and placing the item where it belongs. In a sorting game such as this, this is the expected pattern and is consistent across players. In games with more complex input mechanics, such as fighting game combos, MOBA ability rotations, or FPS strafe-shoot patterns, the same matrix computation would surface skill-differentiating sequences. LudoSense computes both from the same stream of key events captured at the SDK level.

Per-Minute Performance

12 minutes of active gameplay. Breaks in the lines are minutes with no recorded activity.

Actions per Minute

Total key presses recorded per minute of active gameplay.

Camera Angular Velocity

Mean camera rotation speed per minute in degrees per second.

Items Shelved per Minute

Number of items placed per minute of active gameplay.

Error Fraction

Proportion of non-green placements per minute of active gameplay.

These four curves track performance across a single 12-minute session of gameplay, converting what would otherwise be static summary statistics into a temporal profile. Actions-per-minute measures raw input activity, tracking how actively a player is issuing inputs across the session. Angular velocity captures visual scanning intensity with higher values indicating active visual scanning and lower values demonstrating the player has locked onto a target or paused to deliberate. Items shelved measures productive throughput with rising values indicating the player is learning the shelf layout and declining values demonstrating possible fatigue or increasing difficulty. Lastly, error fraction measures the overall accuracy of play during a session with any value above zero meaning the player made at least one wrong-bookcase, wrong-shelf, or wrong-ordering error in that minute. Taken together, this temporal profile reveals whether performance in a session is stable, improving, or degrading over time. These patterns map directly to learning curves, attentional fatigue, and engagement states in the performance psychological literature.

Cross-Session Comparison

Actions per Minute

Average key presses per active minute for each session.

Camera Angular Velocity

Average camera rotation speed per active minute for each session.

Items Shelved per Minute

Average placements per active minute for each session.

Error Fraction

Overall proportion of non-green placements for each session.

Where the per-minute curves show how one player's session unfolds over time, these bars compare the same four metrics across all seven sessions. This is the between-subjects view with the same game and same mechanics, just with different players on different days. Any differences that emerge here could reflect individual skill, familiarity with the game, play style, or even hardware setup. At this sample size, the bars are descriptive rather than inferential, but they illustrate the kind of cohort comparison that LudoSense enables automatically from its per-session aggregation. Filters can be applied that allow for comparison between specific types of players, such as those who use a trackpad vs those who use a separate mouse. A built-in player survey as well as a hardware snapshot will allow for these types of comparisons.

Player Experience Insights

Psychology-backed analysis of how players interact with game mechanics

LudoSense also provides insights into how players interact with game mechanics, backed by years of experience in psychology research. The analytics don't just count errors made by players, it classifies them into distinct types, such as navigation errors or category confusion, by tracking cognitive load through decision timing and mapping physical navigation patterns. The charts below highlight the most common error types, settings changes, and upgrade sequencing.


Error Structure

Green

1,323

98.7% of placements

Correct bookcase, shelf, and volume ordering.

Blue

11

0.82% of placements

Correct bookcase and shelf, but wrong volume ordering.

Yellow

1

0.07% of placements

Correct bookcase, wrong shelf, indicates subcategory confusion.

Red

6

0.45% of placements

Wrong bookcase entirely, indicates a navigation or memory error.

Observed Outcomes

Seven captures, sessions 1-7 in chronological order, the same as the cross-session comparison chart. Each bar is normalised to 100%, so sessions of 69 and 521 placements compare directly.

If the tutorial hadn't landed

Illustrative

Hypothetical data, not measured. This is the signature a player who never grasped volume ordering would leave: session 4's blue band against six otherwise-green runs.

The colour coding above matches the game's own ghost feedback system: green for correct, blue for wrong volume order, yellow for wrong shelf but correct bookcase, and red for incorrect overall. The near-total green across all seven sessions tells a clear game design story: players understood the category system almost perfectly, with zero red errors across most of the sessions. The interesting signal is in the blue colour, the volume ordering errors, which are concentrated almost entirely in one of the illustrated sessions. That pattern would suggest the tutorial may not have landed properly for that hypothetical player in terms of explaining the within-shelf ordering by volume. This is exactly the kind of targeted, actionable insight that distinguishes analytics from raw data. My experience with psychological research allows me to pinpoint not just that players made errors, but the specific mechanics that led to them.

Pinpointing Setting Changes

Settings vs Defaults

Kept the default
Most common departure
Second departure

17 settings tracked across 7 captures. Each bar is 7 sessions wide, split by the value that session ended on.

Not shown: Colourblind Mode, Font Style, Invert Y, SFX Volume, Vsync, Window Mode; these were unchanged by all 7 players.

For gaming studios, knowing which settings players change, and what values they change them to, is one of the simplest ways to improve the out-of-the-box experience for gamers. If most players immediately adjust a setting to the same value, such as camera sensitivity, that value should probably be the default for the game itself. The settings that don't appear in the chart are just as informative as they were left untouched by all seven players, which validates those default values. Departures that cluster around a single alternative value demonstrate a strong signal to change the default. Where they scatter across multiple values suggests more of a genuine personal preference rather than a miscalibrated default. LudoSense tracks this passively with the SDK capturing the settings at launch and any changes made during the gameplay, requiring no additional instrumentation from the developer.

Upgrade Purchases

37

upgrade purchases across 7 sessions

35 of 37

purchases left the player with zero points

4 of 7

sessions ended on an auto-fly purchase

5

longest unbroken run of one upgrade (Auto-Fly, S7)

Build order

  • S1
    1. Hold 1
    2. Hold 2
    3. Fly 1
    4. Fly 2
  • S2
    1. Hold 1
    2. Hold 2
    3. Hold 3
    4. Fly 1
    5. Hold 4
  • S3
    1. Store 1
    2. Item 1
    3. Item 2
    4. Fly 1
    5. Fly 2
    6. Fly 3
    7. Fly 4
  • S4
    1. Hold 1
    2. Hold 2
    3. Fly 1
    4. Fly 2
    5. Store 1
    6. Fly 3
  • S5
    1. Fly 1
    2. Hold 1
  • S6
    1. Fly 1
    2. Hold 1
    3. Hold 2
    4. Item 1
    5. Store 1
    6. Item 2
  • S7
    1. Hold 1
    2. Item 1
    3. Fly 1
    4. Fly 2
    5. Fly 3
    6. Fly 4
    7. Fly 5
Auto-Fly
Hold Capacity
Item Highlight
Storage Highlight
Solid = opening purchase

Each row is a single session with purchases made from left to right in the order they were made. The solid colour is the session's opening purchase with hollow boxes being all purchases made after.

Purchases per upgrade

Total purchases per upgrade type across all seven sessions. The solid fill demonstrates the upgrade was a first purchase with the other purchases coming later in the session.

Upgrade purchase patterns reveal what players perceive as most valuable and when they feel each constraint. Hold Capacity appears as the first purchase in four of the seven sessions, suggesting inventory limit is the first friction point players tend to encounter. Auto-fly dominates later purchases and frequently closes out the session as the final purchase, demonstrating that once basic carrying needs are met, gathering items more quickly becomes the priority. Item highlight and storage highlight are purchased less frequently, indicating players are comfortable with their ability to identify items and shelves without assistance, which makes sense for the size of this playtest. The fact that 35 of 37 purchases left the player with 0 upgrade points shows that players spend their points as soon as they can afford to, demonstrating that they fully understand the upgrade flow and are treating the purchases as quality-of-life improvements. For game designers, this kind of purchase sequencing data surfaces which mechanics create the most friction and whether the upgrade economy is paced correctly.

Pipeline Architecture

How LudoSense processes and analyses game data

The data journey starts in the game client itself, Godot 4 in this instance. The LudoSense GDScript SDK captures input snapshots at 10Hz, the position, camera orientation, keys held, and angular velocity, and batches them into events sent to the API. Item placements, upgrade purchases, and settings changes are captured as separate event types. All of this arrives at the API as JSON payloads and lands in bronze tables exactly as the game client sends them, preserving the raw data for auditability.


Celery workers pick up unprocessed bronze rows and transform them into typed, indexed silver tables: sessions, input snapshots, key events, and item placements. A single input_batch event containing parallel arrays of sampled data is exploded into individual snapshot rows, each with explicit position, angular velocity, and keys-held fields ready for time-series queries. A single item_placed event becomes a placement row with ghost colour, category, subcategory, volume, and timing fields. The transform is defensive throughout with malformed entries skipping their own row rather than failing at the whole batch.


The research metrics layer sits above the silver layer. Functions like per-minute aggregation, inter-key interval computation, and transition matrix construction operate directly on the silver rows to produce the analysis in the charts above. For the purposes of this portfolio piece, the results are exported as static JSON files served by Vercel. In a production deployment, the same computations would feed the React dashboard of LudoSense itself through the gold layer's configurable metric definitions, counters, funnels, durations, segmented breakdowns, and rates, all defined without changing the ingestion path.

End-to-End Data Flow

  1. Source

    Game Client

    Godot 4 · GDScript SDK

  2. Ingest

    API

    FastAPI

  3. Raw

    Bronze

    PostgreSQL

  4. Transform

    Celery

    Celery + Redis

  5. Typed

    Silver

    PostgreSQL

  6. Compute

    Research Metrics

    Python

  7. Output

    Charts

    React · Recharts

Game Client · Godot 4 · GDScript SDK

The SDK samples input at 10Hz measuring position, camera orientation, keys held, and angular velocity, and batches those snapshots alongside item placements, upgrade purchases, and settings changes. Batches retry on failure, so a dropped connection doesn't cost the session.

Every event follows this path once. Hover or tab to a stage for what happens there.

Bronze and silver are the medallion layers described above. The gold layer sits between the research metrics and the dashboard in a deployed instance; here the metrics are exported directly.

Explore the Project

Play Alchemy Shoppe Chaos on itch.io or browse the public repo yourself.