Playprint Docs
Dashboard Home

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
});
MethodDescription
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

TraitRangeMeaning
aggressive0–1Tendency to initiate confrontation and press advantage
bold0–1Willingness to take risks for potential reward
deceptive0–1Use of misdirection, feints, and hidden information
chaotic0–1Tendency toward unpredictable, pattern-breaking play
urgent0–1Preference for fast, decisive action over patience
expansive0–1Broad 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)

BiasDerived FromDescription
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)

BiasDerived FromDescription
expressivenesscommsProfile.expressivenessHow chatty the ghost is (0 = silent, 1 = very vocal)
provocationtaunt affinity − supportive affinityTendency 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

FunctionDescription
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

FieldRequiredDescription
event_nameYesEvent type (e.g. "decision.batch", "match.start", "match.end", "legend.create")
schema_versionYesSemver string (e.g. "1.0.0")
game_idYesYour registered game identifier
environmentYesOne of dev, staging, prod
anonymous_user_idYesHashed player ID (min 16 characters)
session_idNoSession identifier
match_idNoMatch identifier
legend_idNoLegend identifier (for ghost/rival matches)
opponent_typeNoOpponent type (e.g. "human", "ghost", "ai")
payloadNoFree-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
}
StatusMeaning
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

ParamRequiredDescription
game_idYesYour registered game identifier
anonymous_user_idYesHashed 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": { ... }
}
StatusMeaning
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

ParamRequiredDescription
game_idYesYour registered game identifier
anonymous_user_idYesHashed player ID

Response

// 200 OK
{
  "ok": true,
  "deleted": {
    "profiles": 1,
    "events": 342
  }
}
StatusMeaning
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"