A Node.js tool to parse and analyze Urban Terror demo files (.urtdemo), extract player information and chat messages, detect aimbot usage, and store data in PostgreSQL for searching.
- Parse Urban Terror demo files (protocol 70, Urban Terror 4.3.4)
- Extract player information (names, teams, client IDs)
- Extract chat messages (global chat, team chat, server messages)
- Aimbot detection - multi-metric analysis system
- Huffman decompression using precomputed lookup table (matches Q3 engine)
- PostgreSQL storage with full-text search support
- Metabase integration for analytics and searching
The analyzer includes a sophisticated aimbot detection system developed through extensive research and calibration against verified natural player demos (32 demos) and confirmed cheater demos (15 demos).
Rather than relying on any single metric, the system uses multi-metric correlation. Each detection method contributes to an overall suspicion score. This approach:
- Reduces false positives (natural players rarely trigger multiple flags)
- Catches different aimbot types (snap aimbots, smooth aimbots, trigger bots)
- Provides explainable results (specific flags indicate what was detected)
| Score Range | Verdict | Meaning |
|---|---|---|
| 0-9 | CLEAN | No significant indicators |
| 10-29 | MINOR_FLAGS | Worth noting but not conclusive |
| 30-49 | QUESTIONABLE | Warrants closer manual review |
| 50-69 | SUSPICIOUS | Multiple concerning indicators |
| 70-100 | HIGHLY_SUSPICIOUS | Strong evidence of abnormal behavior |
| Metric | Value | Notes |
|---|---|---|
| False Positive Rate | 3.1% (1/32) | Natural demos incorrectly flagged ≥30 |
| Detection Rate (≥30) | 93.3% (14/15) | Cheaters flagged as QUESTIONABLE+ |
| High Confidence (≥50) | 60.0% (9/15) | Cheaters flagged as SUSPICIOUS+ |
| Missed Cheaters | 6.7% (1/15) | Sophisticated smooth aimbot |
The one missed cheater uses a very sophisticated smooth aimbot that perfectly mimics natural movement patterns. Detecting such advanced cheats would require machine learning techniques.
These statistics define "normal" human aiming behavior:
| Metric | Min | Max | Average | Notes |
|---|---|---|---|---|
| Max Angular Velocity | 2040°/s | 4169°/s | 3338°/s | Fastest single movement |
| 95th Percentile Velocity | 820°/s | 2575°/s | - | Sustained high velocity |
| Snap Rate (>3500°/s) | 0% | 6.9% | 3.5% | Percentage of very fast movements |
| Medium Velocity Shots | 0% | 8.7% | 3.6% | Shots fired while adjusting aim |
| Zero Pre-Shot Adjustments | 86% | 99% | 93% | Stable aim before firing |
| Intermediate/Snap Ratio | 2.9 | 27.0 | 7.7 | Gradual movements per fast snap |
| Diagonal Movements | 0 | 10 | 4.1 | Mixed horizontal+vertical movement |
| Metric | Natural Players | Cheaters | Separation |
|---|---|---|---|
| Max Velocity | 2040-4169°/s | 820-3540°/s | Overlap (some) |
| Medium Velocity Shots | 0-8.7% (avg 3.6%) | 0-6.7% (avg 1.1%) | 80% cheaters = 0% |
| Zero Pre-Shot Adj | 86-99% | 94-100% | Cheaters higher |
| Direction Changes | ~0.7% | ~0.3% | Cheaters lower |
| Overshoot Rate | ~0.07 | ~0.04 | Cheaters lower |
Key Finding: The most reliable single indicator is medium velocity shots. 80% of cheater demos have exactly 0% medium velocity shots (never fire while adjusting), compared to only 9% of natural demos.
This section explains each detection method in plain language so anyone can understand what we're looking for and why it indicates cheating.
When you play a first-person shooter, your crosshair moves around the screen as you look for enemies. We record these movements many times per second and measure:
- How fast the crosshair moves (angular velocity, measured in degrees per second)
- Where the crosshair stops (the angle/position)
- When shots are fired relative to crosshair movement
- How smooth or jerky the movements are
By analyzing these patterns, we can identify behaviors that are physically impossible or statistically improbable for human players.
What it measures: How fast the player's view/crosshair moves.
When a human moves their mouse, there's natural variation - sometimes fast, sometimes slow, with smooth acceleration and deceleration. Aimbots often produce unnatural movement patterns.
AIMLOCK_LOW_MAX_VELOCITY (High Severity)
"The player's fastest crosshair movement is suspiciously slow."
Normal players occasionally make very fast flick movements (2000-4000+ degrees per second) when quickly turning or snapping to a target. If someone's fastest movement in an entire match is only 800-1500 deg/s, it suggests their aim is being "locked" by software that makes only tiny adjustments. The aimbot is doing the aiming, so the player never needs to make fast movements themselves.
Statistics: Natural players: 2040-4169°/s max. Flagged if below 2000°/s. One detected aimbot had max velocity of only 820°/s.
SPARSE_INTERMEDIATE_MOVEMENTS (High Severity)
"The player jumps directly from still to fast with nothing in between."
Imagine drawing a line from point A to point B. A human would gradually speed up, reach peak speed in the middle, then slow down approaching the target. Their movements would show a range of speeds: slow, medium, fast, medium, slow.
An aimbot often works differently: the crosshair is still (zero speed), then INSTANTLY at the target (high speed), then still again. There are very few "in-between" movements. We count how many medium-speed movements (100-2000 deg/s) occur relative to fast snaps (>2000 deg/s). Natural players have 5-10+ medium movements for every snap; aimbots often have fewer than 2.
Statistics: Natural intermediate/snap ratio: 2.9-27.0 (avg 7.7). Flagged if ratio below 2.0 (high severity) or 4.0 (medium severity).
PURE_SINGLE_AXIS_SNAPS (High Severity)
"All fast movements are perfectly horizontal OR vertical, never diagonal."
When humans aim, they rarely move the mouse in a perfectly straight horizontal or vertical line. Even when primarily moving left/right, there's usually a tiny bit of up/down mixed in - our hands aren't perfectly precise.
Aimbots often calculate the shortest path to a target and may only adjust one axis (horizontal OR vertical) at a time. If someone makes 20+ fast movements and NONE of them have any diagonal component, that's extremely suspicious.
Statistics: Natural players: 0-10 diagonal movements per demo (avg 4.1). Some aimbots show exactly 0 diagonal movements among 18-29 fast movements.
REPEATED_EXACT_ANGLES (High Severity)
"The player keeps making the exact same angle change, over and over."
When you snap to different enemies at different distances and positions, each snap should be a different angle. If 15%+ of a player's fast movements are IDENTICAL angle changes (down to 0.5 degrees), it suggests software is computing and executing the same programmed movement repeatedly. Humans simply cannot reproduce exact angles this consistently.
Example: If someone repeatedly snaps exactly 23.5° right and 4.0° down, the same amount each time, that's a telltale sign of automated aim.
REPEATED_EXACT_VELOCITY (Medium Severity)
"Fast movements happen at the exact same speed repeatedly."
Similar to repeated angles, aimbots often operate at fixed speeds. If 25%+ of high-velocity movements occur at the exact same speed (rounded to nearest 10 deg/s), it suggests automated movement rather than natural human variance.
CONSISTENT_SNAP_PITCH (High Severity)
"Most snaps end at the exact same vertical level."
"Pitch" is the up/down angle - looking up at the sky is high pitch, looking at the ground is low pitch. When aiming at enemies, you'd expect to hit different vertical levels depending on where enemies are, whether they're crouching, on stairs, etc.
If 70%+ of a player's snaps end at the SAME pitch (vertical angle), it suggests an aimbot is consistently targeting the same hitbox position (usually center mass or head height). Natural players show much more variation - some shots at head level, some at torso, some while enemies are jumping, etc.
BINARY_VELOCITY_PATTERN (Low Severity)
"Movements go instantly from zero to maximum speed with no acceleration."
When humans move their mouse fast, there's a brief moment of acceleration - you don't go from 0 to 3000 deg/s instantly. There's a tiny ramp-up period.
Some aimbots produce "binary" movement: either completely still OR at full speed, with nothing in between. The crosshair goes from motionless to maximum velocity in a single frame. While this can happen naturally with very quick flicks, if it happens constantly with NO gradual movements, it's notable.
What it measures: The relationship between aim movement and firing.
This is one of our most powerful detection methods based on a simple insight:
ZERO_MEDIUM_VEL_SHOTS (High Severity)
"The player ONLY fires when their aim is perfectly still OR just after snapping."
Think about how a normal player shoots:
- Sometimes you shoot while your aim is steady (locked on target)
- Sometimes you shoot while still making small adjustments (tracking a moving target)
- Sometimes you shoot just after a fast snap to a new target
We categorize shots by the crosshair speed at the moment of firing:
- Low velocity (<200 deg/s): Aim was essentially locked/still
- Medium velocity (200-1000 deg/s): Still adjusting while firing
- High velocity (>1000 deg/s): Fired during or just after a snap
Natural players: About 1-4% of shots are fired at medium velocity (adjusting while shooting).
Aimbots: Often 0% medium velocity shots. Why? Because the aimbot either:
- Locks aim perfectly still, THEN fires (low velocity), or
- Snaps to target, THEN fires (high velocity)
The aimbot never fires while "adjusting" because it doesn't need to adjust - it calculates the exact position and goes there directly. If someone fires 25+ shots and NOT A SINGLE ONE was at medium velocity, that's a strong indicator of automated aim.
Statistics:
- Natural players: 0-8.7% medium velocity shots (average 3.6%)
- Cheaters: 0-6.7% medium velocity shots (average 1.1%)
- 80% of cheater demos have exactly 0% medium velocity shots
- Only 9% of natural demos have 0% medium velocity shots
- This is our most statistically significant indicator
What it measures: How precisely and repetitively the player hits targets.
EXTREMELY_LOW_PITCH_DIVERSITY (Low Severity)
"All shots land at almost the same vertical level."
If someone fires 30+ shots and only hits 2 different pitch levels (e.g., only head height and upper chest), it suggests an aimbot that always targets the same hitbox area. Natural gameplay produces much more variety because:
- Enemies are at different heights (stairs, jumping, crouching)
- You don't always have time to aim perfectly
- Different situations call for different shot placements
PERFECT_PRE_AIM (Medium Severity)
"Every single shot has zero aim adjustment just before firing."
We look at the crosshair movement in the frame immediately before each shot. Natural players are usually still making tiny adjustments (tracking the target, compensating for their own movement). If 100% of shots have ZERO adjustment in the final frame, it suggests the aimbot achieved perfect lock before allowing the shot.
ALWAYS_ADJUSTING_BEFORE_SHOTS (High Severity)
"The player is always moving their aim when shooting, never stable."
The opposite extreme is also suspicious. Natural players stabilize their aim before most shots (86-99% have zero final adjustment). Some aimbots that constantly micro-adjust produce 0% stable shots - they're ALWAYS moving when the shot fires. This is unnatural and indicates software-controlled aim.
Statistics:
- Natural players: 86-99% zero pre-shot adjustments (avg 93%)
- Aimbots: 94-100% zero adjustments (toggled type) OR 0-50% (micro-adjusting type)
- Natural unique pitch levels: 5-14 per demo
- Aimbot unique pitch levels: 2-5 per demo
What it measures: The timing pattern of shots, specifically for "trigger bots."
A trigger bot is different from a traditional aimbot:
- Traditional aimbot: Software moves your crosshair TO the enemy
- Trigger bot: You aim naturally, but software automatically clicks when crosshair passes over an enemy
Trigger bots are harder to detect because the aim movement looks natural. But the TIMING gives them away.
HIGH_INSTANT_SHOT_RATE (High Severity)
"Most shots are fired the instant the crosshair settles on target."
Human reaction time has limits. When your crosshair moves over an enemy, your brain needs time to:
- Recognize the crosshair is on target
- Decide to shoot
- Send the signal to click
This takes at least 150-200ms for the fastest humans. Most players take 200-400ms.
A trigger bot fires INSTANTLY (under 50ms) when the crosshair touches the enemy. If 50%+ of shots are fired with 0ms of stable aim time, it's a strong trigger bot indicator.
CONSISTENT_SHOT_TIMING (Medium Severity)
"The delay between settling and shooting is suspiciously consistent."
Human reaction times VARY. Sometimes you're ready and react fast, sometimes you're distracted and slow. Your timing will have natural variance.
Trigger bots (or aimbots with fire delay) often have very consistent timing - the same delay every time. We measure this using "coefficient of variation" (CV). Natural players have CV around 0.2-0.3 (20-30% variance). A CV below 0.10 (less than 10% variance) suggests automated timing.
SETTLE_SNAP_PATTERN (High Severity)
"Most shots follow a pattern: moving → instant stop → instant fire."
This detects a specific trigger bot behavior:
- Player moves crosshair naturally
- Crosshair stops on enemy (trigger bot detects this)
- Shot fires immediately (<50ms after stopping)
If 70%+ of shots follow this exact pattern, it's a strong trigger bot signature.
What it measures: The shape and corrections in aim movement paths.
ABSOLUTELY_NO_CORRECTIONS (Medium Severity)
"The player never reverses their aim direction - every movement goes directly to target."
When humans aim, we constantly make small corrections. We overshoot slightly and pull back, or undershoot and push forward. Our aim path wiggles.
Aimbots calculate the exact path and execute it perfectly. They never need to "correct" because they never make mistakes. If we see hundreds of movements with ZERO direction reversals, it suggests non-human aim.
NO_OVERSHOOT (Low Severity)
"The player never overshoots targets - they stop exactly on the enemy every time."
Overshooting is natural. You flick toward an enemy, go slightly past them, then correct back. Even skilled players overshoot occasionally.
Aimbots don't overshoot because they know exactly where to stop. If someone makes many fast movements and NEVER overshoots, their aim is suspiciously precise.
Statistics:
- Natural direction change rate: ~0.7% (constant micro-corrections)
- Cheater direction change rate: ~0.3% (fewer corrections needed)
- Natural overshoot rate: ~0.07 (7% of fast movements overshoot)
- Cheater overshoot rate: ~0.04 (4% of fast movements overshoot)
| Flag | What It Detects | Severity |
|---|---|---|
AIMLOCK_LOW_MAX_VELOCITY |
Aim never moves fast - locked by software | High |
SPARSE_INTERMEDIATE_MOVEMENTS |
Missing gradual movements - jumps from still to fast | High |
PURE_SINGLE_AXIS_SNAPS |
Only horizontal OR vertical movement, never diagonal | High |
REPEATED_EXACT_ANGLES |
Same angle change repeated - robotic precision | High |
CONSISTENT_SNAP_PITCH |
Always snaps to same vertical level - targets same hitbox | High |
ZERO_MEDIUM_VEL_SHOTS |
Never fires while adjusting - only when locked or after snap | High |
HIGH_INSTANT_SHOT_RATE |
Fires instantly when on target - trigger bot | High |
SETTLE_SNAP_PATTERN |
Move → instant stop → instant fire sequence | High |
ALWAYS_ADJUSTING_BEFORE_SHOTS |
Never stable before shooting - constant micro-adjustments | High |
REPEATED_EXACT_VELOCITY |
Same movement speed repeatedly - fixed aimbot speed | Medium |
LOW_SNAP_PITCH_DIVERSITY |
Low variety in where snaps land vertically | Medium |
CONSISTENT_SHOT_TIMING |
Shot timing too consistent - automated | Medium |
ABSOLUTELY_NO_CORRECTIONS |
Never corrects aim - perfect paths | Medium |
BINARY_VELOCITY_PATTERN |
Instant acceleration with no ramp-up | Low |
NO_OVERSHOOT |
Never overshoots targets - too precise | Low |
EXTREMELY_LOW_PITCH_DIVERSITY |
Shots cluster at very few vertical levels | Low |
Different cheats work in different ways, and each leaves distinct fingerprints. Here's what we can detect:
How it works: The player toggles the aimbot on (usually with a key press) when they see an enemy. The aimbot instantly snaps the crosshair to the target. The player then toggles it off to aim naturally until the next engagement.
What it looks like in-game: The crosshair is moving normally, then INSTANTLY jumps to an enemy, fires, then resumes normal movement. Very obvious when watching demos at slow speed.
Telltale signs we detect:
SPARSE_INTERMEDIATE_MOVEMENTS- Crosshair goes from still → snap → still with no gradual movementPURE_SINGLE_AXIS_SNAPS- Snaps are perfectly horizontal or vertical (bot calculates shortest path)REPEATED_EXACT_ANGLES- Same snap angle used repeatedly (bot uses same calculation)CONSISTENT_SNAP_PITCH- Always snaps to same body part (bot targets same hitbox)
How it works: The aimbot keeps the crosshair "locked" onto enemies at all times, but with very subtle movements. Some versions are "silent" - the player's view appears normal, but bullets are redirected to hit enemies regardless of where the crosshair appears to be pointing.
What it looks like in-game: The player's aim seems unnaturally stable, almost glued to targets. Very few sudden movements. Shots land even when aim appears slightly off.
Telltale signs we detect:
AIMLOCK_LOW_MAX_VELOCITY- Fastest movement is suspiciously slow (never needs to flick because aim is always locked)AIMLOCK_MINIMAL_MOVEMENT- Very few aim movements overall (bot handles aiming)ZERO_MEDIUM_VEL_SHOTS- Never fires while adjusting (aim is always perfectly locked)
How it works: Instead of snapping instantly, the aimbot moves the crosshair toward the target at a controlled, human-like speed. Designed to look natural and evade detection.
What it looks like in-game: Aim appears to "track" enemies smoothly, almost too smoothly. The crosshair follows targets without the jitter and corrections natural to human aim.
Telltale signs we detect:
LOW_INTERMEDIATE_MOVEMENTS- Missing the "searching" movements between engagementsBINARY_VELOCITY_PATTERN- Despite smooth appearance, still shows instant velocity changesABSOLUTELY_NO_CORRECTIONS- Never overshoots or corrects - perfect paths every time- Combined with low pitch diversity - always hits same target areas
Note: Sophisticated smooth aimbots are the hardest to detect because they're specifically designed to mimic human patterns. Our current system catches most, but the most advanced ones may require machine learning to identify.
How it works: Unlike traditional aimbots, trigger bots don't move the crosshair at all. The player aims naturally with their own skill. The bot only handles ONE thing: clicking the fire button the instant the crosshair passes over an enemy.
Trigger bots work by either:
- Reading game memory: Checking if the crosshair ID points to an enemy entity
- Screen analysis: Using computer vision to detect when crosshair is over an enemy color/shape
What it looks like in-game: Aim movement appears completely natural and human. But shots are fired with inhuman reaction time - the instant the crosshair touches an enemy, even when flicking past them at high speed.
Telltale signs we detect:
HIGH_INSTANT_SHOT_RATE- Shots fire instantly (0ms delay) when crosshair reaches targetCONSISTENT_SHOT_TIMING- Timing is suspiciously consistent (low variance in reaction time)SETTLE_SNAP_PATTERN- Characteristic pattern of: moving → instant stop → instant fire
Why trigger bots are hard to detect: The aim itself is human, so movement analysis won't catch it. We focus on the TIMING - humans cannot react in under 150ms, but trigger bots fire in under 10ms consistently.
Based on our testing with 15 confirmed cheater demos:
| Aimbot Type | Detection Rate | Typical Score | Common Flags Triggered |
|---|---|---|---|
| Snap/Toggle | ~100% | 50-80 | SPARSE_INTERMEDIATE, REPEATED_EXACT_ANGLES |
| Silent/Lock | ~100% | 45-70 | AIMLOCK_LOW_MAX_VELOCITY, ZERO_MEDIUM_VEL_SHOTS |
| Trigger Bot | ~90% | 30-50 | HIGH_INSTANT_SHOT_RATE, CONSISTENT_SHOT_TIMING |
| Smooth Aimbot | ~75% | 30-45 | LOW_INTERMEDIATE, BINARY_VELOCITY_PATTERN |
| Advanced Smooth | ~0% | 0-10 | None - mimics human patterns too well |
| Aimbot Type | Detection Difficulty | Why |
|---|---|---|
| Snap/Toggle | Easy | Obvious instant snaps leave clear signatures |
| Silent/Lock | Medium | Unusual lack of movement is detectable |
| Trigger Bot | Medium-Hard | Aim looks natural, must detect timing anomalies |
| Smooth Aimbot | Hard | Specifically designed to mimic human patterns |
const AimAnalyzer = require('./src/analyzer/aim-analyzer');
const analyzer = new AimAnalyzer({ debug: false });
const results = await analyzer.analyzeDemo('./path/to/demo.urtdemo');
console.log(`Suspicion Score: ${results.suspicionScore}`);
console.log(`Verdict: ${results.verdict.level}`);
console.log(`Flags:`, results.viewAngleAnalysis?.flags);const results = await analyzer.analyzeMultiple([
'./demo1.urtdemo',
'./demo2.urtdemo',
]);
for (const result of results) {
console.log(`${result.filename}: ${result.suspicionScore} (${result.verdict.level})`);
}Urban Terror demo files use the Quake 3 engine demo format with Huffman compression:
- Header: Version string + protocol number (70 for UT 4.3.4)
- Packets: Each packet contains messages (gamestate, server commands, snapshots)
- Huffman Compression: All packet data is Huffman-compressed using a static tree
| Value | Command | Description |
|---|---|---|
| 0x01 | svc_nop | No operation |
| 0x02 | svc_gamestate | Initial game state (config strings, baselines) |
| 0x03 | svc_configstring | Config string update (only in gamestate) |
| 0x04 | svc_baseline | Entity baseline (only in gamestate) |
| 0x05 | svc_serverCommand | Server command (chat, print, cs updates) |
| 0x06 | svc_download | File download |
| 0x07 | svc_snapshot | Game snapshot (entity states) |
| 0x08 | svc_EOF | End of message |
| Range | Purpose |
|---|---|
| 0 | CS_SERVERINFO - Server info |
| 1 | CS_SYSTEMINFO - System info (pak files, etc.) |
| 2-31 | Various game settings |
| 32-287 | Models |
| 288-543 | Sounds |
| 544-575 | Players (CS_PLAYERS + clientNum) |
| 576+ | Items, locations, etc. |
The parser uses a precomputed Huffman lookup table for fast and accurate decompression:
- Located in
src/parser/huffman-table.js - 65536-entry lookup table (16-bit index)
- Generated from the same frequency data (
msg_hData) used by the Quake 3 engine - Handles variable-length codes (2-11 bits)
Identifying which player recorded the demo uses multiple methods:
- End Pattern Detection: Search for
0x00 clientNum 00 00 00pattern at end of gamestate - PM Welcome Detection: Parse
[pm] [Authed] Welcome back PlayerNamemessages (only visible to POV player) - Known Name Matching: Match against known player names
Current detection rate: ~83%
- https://urbanterror.info (official website)
- https://urbanterror.fandom.com/wiki/Demo (demo info)
- https://github.com/FrozenSand/ioq3-for-UrbanTerror-4 (official UT4 client source)
- https://github.com/undeadzy/q3dmplayer_urbanterror (reference parser)
- https://www.reddit.com/r/hacking/comments/1pzwiw/how_do_anticheat_programs_detect_aimbot_use/
- https://madelinemiller.dev/blog/the-4-year-late-postmortem-of-an-advanced-aimbot-detection-system/
- https://www.cs.memphis.edu/~xgao1/paper/dsn17_2.pdf
- https://ieeexplore.ieee.org/document/6252489
- https://www.databricks.com/dataaisummit/session/machine-learning-aimbot-detection-call-duty
- https://intellisec.de/research/aimbots/2020-eurosec.pdf
- https://www.usenix.org/system/files/sec23_slides_choi.pdf
- https://prof-jeffyan.github.io/nime06.pdf
- Node.js (v16 or higher)
- Docker and Docker Compose (for PostgreSQL, Metabase, and JSDoc)
- Or standalone PostgreSQL if not using Docker
- Install dependencies:
npm install- Copy environment file:
cp .env.example .env- Start services with Docker Compose:
docker-compose up -d- Run database migrations:
npm run migrateAfter starting Docker Compose, the following services are available:
| Service | URL | Description |
|---|---|---|
| Metabase | http://localhost:3001 | Analytics and database query UI |
| JSDoc | http://localhost:3002 | API documentation browser |
| PostgreSQL | localhost:5432 | Database (use with psql or clients) |
If you update the code documentation and want to regenerate JSDoc:
# Rebuild the JSDoc container
docker-compose build jsdoc
# Restart the service
docker-compose up -d jsdocYou can also generate documentation locally:
# Install JSDoc
npm install -g jsdoc
# Generate docs
jsdoc -c jsdoc.json
# View in browser
npx serve docsParse a single demo file:
npm run parse -- parse ./example-demos/example.urtdemoParse multiple demos:
npm run parse -- parse ./example-demos/*.urtdemoOutput as JSON (without database):
npm run parse -- parse ./example-demos/example.urtdemo -o jsonSearch for keywords in chat:
npm run parse -- search "keyword"Search with filters:
npm run parse -- search "keyword" --player "PlayerName" --team --limit 50The database includes these main tables:
- demos: Demo file metadata (filename, map, date, protocol)
- player_profiles: Unique players with tracking flags
- players: Per-demo player appearances (links to profiles)
- chat_messages: All chat messages with full-text search support
See src/db/schema.sql for the complete schema.
Metabase provides a powerful interface for searching, querying, and visualizing demo data.
-
Start the services:
docker-compose up -d
-
Access Metabase at http://localhost:3001
-
Complete the setup wizard:
- Create an admin account
- When asked to add a database, select PostgreSQL
- Use these connection settings:
- Host:
postgres(container name) - Port:
5432 - Database:
urt_demo_analyzer - Username:
urt_user - Password:
urt_password
- Host:
Find all chats containing a keyword:
SELECT player_name, message, d.filename, d.map_name
FROM chat_messages c
JOIN demos d ON c.demo_id = d.id
WHERE message ILIKE '%keyword%'
ORDER BY c.created_at DESC;Find all demos a player appeared in:
SELECT DISTINCT d.filename, d.map_name, p.team
FROM players p
JOIN demos d ON p.demo_id = d.id
WHERE p.name ILIKE '%playername%'
ORDER BY d.created_at;Mark a player as a suspected cheater:
UPDATE player_profiles SET suspected_cheater = TRUE, notes = 'Suspicious aiming patterns'
WHERE name ILIKE '%playername%';Find all trolls and cheaters:
SELECT name, troll_count, is_troll, banned_for_cheating, suspected_cheater, notes, demos_played
FROM player_profiles
WHERE is_troll = TRUE OR banned_for_cheating = TRUE OR suspected_cheater = TRUE
ORDER BY troll_count DESC;├── src/
│ ├── analyzer/
│ │ └── aim-analyzer.js # Aimbot detection logic
│ ├── parser/
│ │ ├── demo-parser.js # Main demo parsing logic
│ │ ├── message-reader.js # Huffman-compressed message reading
│ │ ├── snapshot-parser.js # Game snapshot parsing
│ │ ├── huffman-table.js # Precomputed Huffman lookup table
│ │ └── index.js # CLI entry point
│ └── db/
│ ├── schema.sql # Database schema
│ └── migrate.js # Migration script
├── training-demos/
│ ├── cheater-demos/ # Verified cheater demos for testing
│ └── natural-demos/ # Verified natural player demos
├── example-demos/ # Demo files for testing
└── docker-compose.yml # Docker services configuration
✅ Parse Urban Terror 4.3.4 demo files (.urtdemo) ✅ Extract player names, teams, and client IDs ✅ Extract chat messages (global and team chat) ✅ Extract view angles for aim analysis ✅ Detect multiple aimbot types (snap, silent, smooth, trigger) ✅ Handle large config strings (up to 8192 chars) ✅ Huffman decompression with precomputed lookup table ✅ PostgreSQL storage with full-text search
- JavaScript/Node.js
- Bash
- Python