Every few weeks, someone asks a version of the same question on a game dev forum: "How do I add bots that play like real players, without running dedicated servers?" The honest answer is that the games industry solved this problem years ago — for racing games. It just never got generalised. This post explains how those systems work, and how to build one for any genre.
What is a Drivatar?
Forza's Drivatar is the most famous example of what we'd call ghost AI: an AI opponent trained on a specific real player's behaviour, that stands in for them when they're offline. Your friend's Drivatar brakes late into corners because they brake late into corners. Racing against it feels like racing against them — even though they're asleep on another continent.
The lineage is longer than Forza. Racing ghosts have let players race their friends' recorded laps since the arcade era. Souls-likes leave asynchronous traces of other players' deaths. Phantom Abyss fills its temples with the phantoms of every runner who died there before you. All of these share one insight: you don't need two players online at the same time to create social play — you need a believable behavioural stand-in for one of them.
How does ghost AI work without servers?
Strip away the branding and every system in this family has the same four-stage architecture:
- Capture — record the player's meaningful decisions during normal play: what they did, how risky it was, how informed it was, when in the match it happened.
- Extract — aggregate those decisions across matches into a compact behavioural profile: aggression, risk tolerance when winning vs losing, bluff rate, tempo, consistency.
- Bias — convert the profile into weights that steer your existing game AI. The ghost isn't a new AI — it's your bot, biased to behave like a specific person.
- Present — give the ghost an identity (name, archetype, playstyle description) so opponents know who they're playing.
Notice what's not in that list: netcode, matchmaking, live connections, or dedicated servers. The capture step runs inside the game client. The profile is a small blob of numbers. It can be stored locally, shared as a code, or synced through whatever backend you already have. That's why ghost AI is the cheapest path to multiplayer-feeling gameplay: asynchronous multiplayer without multiplayer infrastructure.
Do you need machine learning?
Forza trains neural networks in the cloud, and if you're modelling continuous control like driving lines, you may need that. But for the majority of genres — card games, strategy, action, board-style games — the decisions are already discrete, and a statistical profile driving a biased rules AI produces ghosts that friends genuinely recognise. Recognition, not perfect imitation, is the bar: the moment a player says "that is SO you" about a ghost, the system works.
This is the approach our open @playprint/core package takes. It's a zero-dependency TypeScript engine that runs entirely inside your game — no account, no network required:
import { PlayprintTracker, createGhost, mapGhostBiases } from '@playprint/core';
const tracker = new PlayprintTracker({ gameId: 'my_game' });
tracker.startMatch();
tracker.decision({ label: 'attack' }); // risk inferred from label
tracker.outcome({ type: 'hit', delta: 0.5 });
tracker.decision({ label: 'bluff', risk: 0.9, information: 0.3 });
const profile = await tracker.endMatch('win'); // 14-dimension profile
// Convert the profile into biases for YOUR game's AI
const ghost = createGhost(profile);
const aiParams = mapGhostBiases(ghost, {
attackFrequency: { bias: 'aggression', range: [0.1, 0.9] },
retreatThreshold: { bias: 'patience', range: [0.2, 0.8] },
bluffChance: { bias: 'deception', range: [0, 0.3] },
reactionTime: { bias: 'consistency', range: [800, 200] },
});
The tracker records decisions, createGhost() reduces the profile to five bias weights (aggression, patience, risk tolerance, consistency, deception), and mapGhostBiases() projects them onto whatever parameters your AI already exposes. Your bot code doesn't change — it just gets a personality.
What about privacy — and kids?
Ghost AI has an underrated safety property: it's social play with no live connection between players. No chat, no voice, no strangers, nothing to moderate. The profile contains gameplay tendencies — numbers — not names, faces, or messages. That's why we think this architecture is the right foundation for social features in children's games, where live multiplayer is a compliance and safety minefield. It's the reasoning behind our COPPA-safe design: per-game identities are cryptographically unlinkable by default, and behavioural data is the only data there is.
Where to start
If you want to experiment, npm install @playprint/core and you can have a tracked match producing ghost biases in an afternoon — entirely offline. When you want hosted profiles, cross-game Legends, and analytics, the @playprint/sdk package layers the playprint.ai platform on top without changing your tracking code. The quickstart covers both paths.
Racing games proved that ghosts of real players are more compelling than any hand-tuned bot. The rest of the industry just hasn't caught up yet.