API Reference
@playprint/core is a client-side TypeScript library. No API keys, no server, no HTTP endpoints. Everything runs locally.
npm install @playprint/core Classes
PlayprintTracker
The main entry point for recording telemetry and extracting profiles.
import { PlayprintTracker } from '@playprint/core';
const tracker = new PlayprintTracker({
gameId: 'my_game',
accountId: 'player_42', // optional — auto-generated if omitted
storage: new LocalStorageAdapter(), // optional — InMemoryStorage by default
riskMap: { 'charge': 0.85 }, // extend the default label→risk mapping
bluffTag: 'heavy_bluff', // tag that marks bluff decisions
patternBreakTag: 'pattern_break', // tag that marks pattern-breaking decisions
maxMatches: 50, // max matches to keep for profile extraction
}); | Method | Description |
|---|---|
startMatch(opts?) | Begin recording a match |
decision(input) | Record a decision (tiered input — see Quickstart) |
outcome(input) | Record an outcome ({ type, delta, attribution? }) |
endMatch(result) | Save match, extract and return updated PlayprintData |
getEvents() | Get current match events |
getProfile() | Compute profile from all stored matches |
Profile Extraction
extractProfile(matches, options?)
Aggregates match history into a PlayprintData profile. Called automatically by tracker.endMatch(), but also available standalone.
import { extractProfile } from '@playprint/core';
const profile = extractProfile(matches, {
bluffTag: 'heavy_bluff',
maxMatches: 50,
extensionExtractors: {
favoriteCard: (matches) => computeFavoriteCard(matches),
},
});
PlayprintData — 6 Canonical Traits
Trait Range Meaning aggressive0–1 Tendency to initiate confrontation and press advantage bold0–1 Willingness to take risks for potential reward deceptive0–1 Use of misdirection, feints, and hidden information chaotic0–1 Tendency toward unpredictable, pattern-breaking play urgent0–1 Preference for fast, decisive action over patience expansive0–1 Broad influence vs narrow, focused strategy
The SDK uses exponential moving average (EMA) aggregation over match history, weighting recent matches more heavily. All values are normalized to [0, 1].
Ghost AI
createGhost(profile, commsProfile?)
Ghost AI uses the 6 trait scores and optional CommsProfile to generate 8 behavioral bias weights that drive AI opponent behavior — both how the ghost plays and how it communicates.
import { createGhost } from '@playprint/core';
// Core biases only (from traits)
const ghost = createGhost(profile);
// { aggression: 0.72, patience: 0.28, riskTolerance: 0.65,
// consistency: 0.8, deception: 0.15, reach: 0.29,
// expressiveness: 0, provocation: 0 }
// Full biases (traits + comms)
const ghost = createGhost(profile, commsProfile);
// { aggression: 0.72, patience: 0.28, riskTolerance: 0.65,
// consistency: 0.8, deception: 0.15, reach: 0.29,
// expressiveness: 0.57, provocation: 0.68 }
GhostBiases — 8 Weights
Core Biases (from traits)
Bias Derived From Description aggressiontraits.aggressiveHow aggressively the ghost plays patience1 - traits.urgentWillingness to wait riskTolerancetraits.boldWillingness to take risks consistency1 - traits.chaoticPlay style predictability deceptiontraits.deceptiveTendency to feint or bluff reachtraits.expansiveHow broadly the ghost targets and exerts influence
Communication Biases (from CommsProfile)
Bias Derived From Description expressivenesscommsProfile.expressivenessHow chatty the ghost is (0 = silent, 1 = very vocal) provocationtaunt affinity − supportive affinity Tendency toward trash talk vs. sportsmanship
When no CommsProfile is provided, expressiveness and provocation default to 0 (silent, neutral).
mapGhostBiases(ghost, mapping)
Maps abstract biases to game-specific AI parameters via linear interpolation. Works with all 8 biases — including communication biases for emotes, chat, and messaging systems.
import { mapGhostBiases } from '@playprint/core';
const aiParams = mapGhostBiases(ghost, {
// Core gameplay
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: [200, 800] },
riskThreshold: { bias: 'riskTolerance', range: [0.3, 0.9] },
targetSpread: { bias: 'reach', range: [1, 4] },
// Communication
emoteFrequency: { bias: 'expressiveness', range: [0, 0.8] },
tauntChance: { bias: 'provocation', range: [0, 0.5] },
});
sampleFromDistribution(distribution)
Generic utility for sampling an action from a probability distribution. Works with GhostDecisionEngine implementations.
import { sampleFromDistribution } from '@playprint/core';
const action = sampleFromDistribution({ attack: 0.6, defend: 0.3, bluff: 0.1 });
// Returns 'attack', 'defend', or 'bluff' weighted by probability
Archetypes
getArchetype(profile)
Quick classification based on the strongest canonical trait. For richer output, use generateArchetype() below.
import { getArchetype, deriveTraits } from '@playprint/core';
const traits = deriveTraits(profile);
const arch = getArchetype(traits);
// { coreArchetype: 'Berserker', styleModifier: 'Reckless', displayName: 'Reckless Berserker', ... }
generateArchetype(traits)
Rich 5-layer personality descriptions built from a TraitProfile. Returns one of 12 core archetypes with style modifiers, tempo tags, behaviours, and taglines.
import { generateArchetype, deriveTraits } from '@playprint/core';
const traits = deriveTraits(profile);
const result = generateArchetype(traits);
// {
// coreArchetype: 'Berserker', // 1 of 12 core types
// styleModifier: 'Fierce', // from second-strongest trait
// displayName: 'Fierce Berserker',
// tempoTag: 'Late-game closer',
// behaviors: ['Waits, then strikes without mercy'],
// tagline: 'Charges in hard and trusts instinct over strategy.',
// }
12 core archetypes (polar pairs per trait): Berserker/Ghost (aggressive), Daredevil/Sentinel (bold), Phantom/Purist (deceptive), Wildcard/Metronome (chaotic), Blitz/Glacier (urgent), Cartographer/Specialist (expansive). Flat profiles produce "Enigma".
Traits
deriveTraits(profile, options?)
Maps raw profile data to 6 normalized canonical trait dimensions used by the archetype and presentation systems.
import { deriveTraits, CANONICAL_TRAIT_KEYS } from '@playprint/core';
const traits = deriveTraits(profile, {
overrides: {
deceptive: (p) => p.bluffRate * 1.2,
swordSkill: (p) => (p.extensions?.swordAccuracy as number) ?? 0.5,
},
});
6 canonical traits (CANONICAL_TRAIT_KEYS): aggressive, bold, deceptive, chaotic, urgent, expansive
Legacy 9-trait keys (aggression, riskTolerance, tempo, exploration, patience, targetLeaderBias, commitment, variance, tiltSensitivity) are automatically migrated. STANDARD_TRAIT_KEYS is a deprecated alias for CANONICAL_TRAIT_KEYS.
Presentation
getLegendPresentation(traits, skills?, options?)
Transforms internal trait data into player-facing UI content: title, trait cards with names and descriptions, archetype, coaching tips, and safety notes.
import { deriveTraits, getLegendPresentation } from '@playprint/core';
const traits = deriveTraits(profile);
const legend = getLegendPresentation(traits, null, {
overrides: {
aggression: { name: 'Battle Fury', tip: 'Attack more to raise this!' },
},
includeArchetype: true,
});
// legend.title → 'Fierce Strategist'
// legend.traits → [{ key, name, value, description, tip, iconKey }, ...]
// legend.archetype → GenerativeArchetypeResult
// legend.safetyNote → 'Based on gameplay only. No voice, chat, or personal info.'
Inference Utilities
Function Description inferRisk(label, customMap?)Infer risk from a label keyword (e.g. 'attack' → 0.7) inferTags(label)Infer intent tags from a label (e.g. 'bluff' → ['aggressive', 'heavy_bluff']) defaultComputeTempo(seq)Compute tempo phase from sequence (1–5 = early, 6–15 = mid, 16+ = late)
Storage Adapters
InMemoryStorage
Default storage. Data lives in memory only — lost on page reload. Good for testing.
LocalStorageAdapter
Persists to browser localStorage. Data survives page reloads.
import { LocalStorageAdapter } from '@playprint/core';
const storage = new LocalStorageAdapter(); // default prefix
const storage2 = new LocalStorageAdapter('myapp'); // custom prefix
StorageAdapter Interface
Implement this for custom backends (database, IndexedDB, API, etc.):
interface StorageAdapter {
saveMatch(accountId: string, match: MatchRecord): Promise<void>;
loadMatches(accountId: string): Promise<MatchRecord[]>;
saveProfile(accountId: string, profile: PlayprintData): Promise<void>;
loadProfile(accountId: string): Promise<PlayprintData | null>;
clear(accountId: string): Promise<void>;
}
Data Portability
exportPlayerData(storage, accountId)
Export all stored data for a player account (GDPR Article 20 compliance).
deletePlayerData(storage, accountId)
Delete all stored data for a player account (GDPR right to erasure).
PII Sanitization
stripKnownPii(event)
Event sanitizer that strips known PII patterns from game_payload and metadata fields. Pass as the sanitize option to PlayprintTracker.
Type Exports
All types are exported for TypeScript consumers:
import type {
PlayprintData, // 6-trait canonical profile
GhostBiases, // 8 ghost weights (5 core + reach + 2 comms)
TraitProfile, // Record<string, number>
DecisionInput, // Tiered decision input
DecisionPayload, // Full decision (Tier 3)
OutcomePayload, // Outcome data
MatchRecord, // Completed match with events
TelemetryEvent, // Individual telemetry event
Archetype, // Simple archetype { name, modifier? }
GenerativeArchetypeResult, // Rich 5-layer archetype
StorageAdapter, // Custom storage interface
TrackerOptions, // PlayprintTracker config
ExtractionOptions, // extractProfile() config
LegendPresentationResult, // UI-ready presentation
ExportedPlayerData, // GDPR export format
DeletionResult, // GDPR deletion result
} from '@playprint/core';
HTTP API
Studios using the hosted service interact with three HTTP endpoints. All requests require a per-game API key passed via the X-PLAYPRINT-KEY header.
Authentication
Every request must include the header:
X-PLAYPRINT-KEY: your_api_key_here
API keys are scoped to a game_id and environment (e.g. dev, staging, prod). You can create and manage keys from the Playprint Dashboard.
POST /api/telemetry/ingest
Batch-ingest telemetry events from your game client or server.
Request
POST /api/telemetry/ingest
Content-Type: application/json
X-PLAYPRINT-KEY: your_api_key_here
{
"events": [
{
"event_name": "decision.batch",
"schema_version": "1.0.0",
"game_id": "my_game",
"environment": "prod",
"anonymous_user_id": "hashed_player_id_min_16_chars",
"session_id": "sess_abc123",
"match_id": "match_001",
"payload": {
"label": "play_card",
"risk": 0.3,
"intent_tags": ["defensive"]
}
}
]
}
Event fields
Field Required Description event_nameYes Event type (e.g. "decision.batch", "match.start", "match.end", "legend.create") schema_versionYes Semver string (e.g. "1.0.0") game_idYes Your registered game identifier environmentYes One of dev, staging, prod anonymous_user_idYes Hashed player ID (min 16 characters) session_idNo Session identifier match_idNo Match identifier legend_idNo Legend identifier (for ghost/rival matches) opponent_typeNo Opponent type (e.g. "human", "ghost", "ai") payloadNo Free-form object with event-specific data
Limits
- Maximum 500 events per batch
- All events in a batch must be authorized by the same API key
Response
// 200 OK (or 400 if all rejected)
{
"accepted": 10,
"rejected": 0,
"errors": undefined // array of strings when rejected > 0
}
Status Meaning 200At least some events accepted 400Invalid JSON, empty events array, or all events rejected 401Invalid or missing API key 413Batch exceeds 500 events
curl example
curl -X POST https://playprint-website.vercel.app/api/telemetry/ingest \
-H "Content-Type: application/json" \
-H "X-PLAYPRINT-KEY: your_api_key_here" \
-d '{
"events": [{
"event_name": "decision.batch",
"schema_version": "1.0.0",
"game_id": "my_game",
"environment": "prod",
"anonymous_user_id": "hashed_player_id_min_16_chars",
"match_id": "match_001",
"payload": { "label": "attack", "risk": 0.7 }
}]
}'
GET /api/telemetry/profile
Retrieve a player's computed trait profile, legend presentation, skills, and ghost data.
Request
GET /api/telemetry/profile?game_id=my_game&anonymous_user_id=hashed_player_id
X-PLAYPRINT-KEY: your_api_key_here
Query parameters
Param Required Description game_idYes Your registered game identifier anonymous_user_idYes Hashed player ID
Response
// 200 OK
{
"game_id": "my_game",
"anonymous_user_id": "hashed_player_id",
"matches_processed": 47,
"last_match_id": "match_047",
"updated_at": "2026-03-10T14:30:00.000Z",
"traits": {
"aggressive": 0.72,
"bold": 0.65,
"deceptive": 0.15,
"chaotic": 0.20,
"urgent": 0.58,
"expansive": 0.29
},
"skills": { ... },
"presentation": {
"title": "Fierce Strategist",
"traits": [ ... ],
"archetype": { ... },
"safetyNote": "Based on gameplay only. No voice, chat, or personal info."
},
"comms_profile": { ... },
"proficiency": { ... }
}
Status Meaning 200Profile returned 400Missing game_id or anonymous_user_id 401Invalid or missing API key 404No profile found for this player
curl example
curl "https://playprint-website.vercel.app/api/telemetry/profile?game_id=my_game&anonymous_user_id=hashed_player_id" \
-H "X-PLAYPRINT-KEY: your_api_key_here"
DELETE /api/telemetry/profile
Delete a player's trait profile and all associated telemetry events. Use this for GDPR right-to-erasure requests.
Request
DELETE /api/telemetry/profile?game_id=my_game&anonymous_user_id=hashed_player_id
X-PLAYPRINT-KEY: your_api_key_here
Query parameters
Param Required Description game_idYes Your registered game identifier anonymous_user_idYes Hashed player ID
Response
// 200 OK
{
"ok": true,
"deleted": {
"profiles": 1,
"events": 342
}
}
Status Meaning 200Deletion completed (even if no data existed) 400Missing game_id or anonymous_user_id 401Invalid or missing API key
curl example
curl -X DELETE "https://playprint-website.vercel.app/api/telemetry/profile?game_id=my_game&anonymous_user_id=hashed_player_id" \
-H "X-PLAYPRINT-KEY: your_api_key_here"