Plan: Game State Debug Log (Binary Ring Buffer) #19
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Plan: Game State Debug Log (Binary Ring Buffer)
Overview
A new src/engine/debuglog module that records a comprehensive snapshot of game state every tick into a fixed-size binary ring buffer file. Activated by --debug-log command-line flag. On crash or manual dump (hotkey), the most recent N seconds of gameplay are preserved for post-mortem analysis. A companion dump function writes the buffer to a human-readable text file.
Module: src/engine/debuglog.h / debuglog.c
Data structures:
/* Packed input snapshot — 3 bytes (8 actions × 3 states) /
typedef struct InputSnapshot {
uint8_t held; / bitmask of ACTION_* held this tick /
uint8_t pressed; / bitmask of ACTION_* pressed this tick /
uint8_t released; / bitmask of ACTION_* released this tick /
} InputSnapshot;
/ Per-entity summary — ~20 bytes each /
typedef struct EntitySnapshot {
uint8_t type; / EntityType /
uint8_t flags; / active, facing, dead, invincible /
int16_t health;
float pos_x, pos_y;
float vel_x, vel_y;
} EntitySnapshot;
/ Full tick snapshot — fixed size header + variable entity list /
typedef struct TickSnapshot {
uint32_t tick; / g_engine.tick /
uint32_t frame_size; / total bytes of this record /
/ Input /
InputSnapshot input; / 3 bytes /
/ Player state (expanded) /
float player_x, player_y;
float player_vx, player_vy;
int8_t player_health;
uint8_t player_flags; / on_ground, jumping, dashing, inv, has_gun /
float player_dash_timer;
int8_t player_dash_charges;
float player_inv_timer;
float player_coyote;
uint8_t player_aim_dir;
/ Camera /
float cam_x, cam_y;
/ Physics globals /
float gravity;
float wind;
/ Level info /
char level_name[32]; / truncated level path/tag /
/ Entity summary /
uint16_t entity_count; / number of active entities /
EntitySnapshot entities[]; / variable-length array */
} TickSnapshot;
Ring buffer on disk:
Actually — in-memory ring buffer with periodic flush is better:
Public API:
void debuglog_init(void); /* Allocate ring buffer /
void debuglog_shutdown(void); / Final flush + free /
void debuglog_enable(void); / Turn on recording /
bool debuglog_is_enabled(void); / Query state /
/ Called once per tick from engine_frame, after update, before consume /
void debuglog_record_tick(void);
/ Dump the ring buffer contents to a readable text file */
void debuglog_dump(const char *path);
Integration Points
Dump Format (text output)
When debuglog_dump() is called, it writes a human-readable text file like:
=== Debug Log Dump ===
Ticks: 18200 to 19800 (1600 ticks, 26.7 seconds)
Level: assets/levels/mars02.lvl
--- Tick 18200 ---
Input: [LEFT] [JUMP_pressed]
Player: pos=(1234.5, 567.8) vel=(-150.0, -200.3) hp=3 on_ground=0 dashing=0 aim=FORWARD
Camera: (1100.2, 400.0)
Physics: gravity=700.0 wind=0.0
Entities (12 active):
[0] GRUNT pos=(1400, 580) vel=(-40, 0) hp=2
[1] FLYER pos=(1600, 300) vel=(0, 10) hp=1
...
--- Tick 18201 ---
Input: [LEFT]
Player: pos=(1232.0, 564.5) vel=(-150.0, -180.0) hp=3 ...
...
File Changes Summary
File Change
src/engine/debuglog.h New — Header with API and snapshot structs
src/engine/debuglog.c New — Ring buffer implementation, record/dump logic
src/engine/input.h Add input_get_snapshot() declaration
src/engine/input.c Implement input_get_snapshot() (~10 lines)
src/engine/core.c Add debuglog_record_tick() call in the tick loop (~3 lines)
src/main.c Parse --debug-log flag, debuglog_set_level() calls, F12 dump hotkey, help text update (~25 lines)
Makefile Add debuglog.o to the object list (~1 line)
include/config.h Add DEBUGLOG_BUFFER_SIZE constant (~2 lines)
Design Tradeoffs
Performance Budget