DIRECTOR
Decide with boundaries
Combine validated telemetry, deterministic rules, explicit action allowlists, and conservative fallbacks in one typed lifecycle.The decision contract
01Telemetry→
02Validated state→
03Rules or AI→
04Typed decision→
05Game approval
ts
interface DirectorDecision {
decisionId: string;
actions: Array<{ type: DirectorActionType; priority: "low" | "medium" | "high"; reason: string; payload: Record<string, unknown> }>;
confidence: number; // 0..1
source: "rules" | "ai" | "fallback";
expiresAt?: string;
}Decision context
Keep context compact, current, and non-sensitive. Use stable IDs and allow only actions implemented by the current runtime.
ts
const request = {
playerId: "player_123",
sessionId: "session_123",
gameState: { levelId: "forest-01", playerHealth: 22, recentDeaths: 4, objectiveDwellMs: 272000 },
allowedActions: ["offer_hint", "spawn_reward", "reduce_enemy_pressure"],
};Events and sessions
Events are validated before optional delivery. Sessions aggregate useful state without forcing raw event history into each decision.
ts
await playstate.sessions.start({ sessionId: "session_123", playerId: "player_123" });
await playstate.events.track({ playerId: "player_123", sessionId: "session_123", type: "player_died", timestamp: new Date().toISOString(), data: { levelId: "forest-01" } });
await playstate.sessions.add("session_123", { type: "objective_progress", data: { percent: 42 } });
const summary = await playstate.sessions.end("session_123");Deterministic rules
Rules run locally. Conditions support nested dot paths plus eq, neq, gt, gte, lt, lte, in, and contains.
ts
const strugglingPlayer = defineRule({
id: "assist-struggling-player",
when: { all: [
{ field: "recentDeaths", operator: "gte", value: 3 },
{ field: "playerHealth", operator: "lte", value: 30 },
]},
then: [
{ type: "spawn_reward", payload: { rewardType: "health_pickup", amount: 1 } },
{ type: "offer_hint", payload: { hintId: "forest-path-hint" } },
],
});Source priority
| Priority | Source | Network | Behavior |
|---|---|---|---|
| 1 | Rules | No | Matching actions return immediately. |
| 2 | AI | Yes | Contextual server generation. |
| 3 | Fallback | No | Conservative actions keep play moving. |
Engine adapters
ts
import { unity, godot, unreal, generic } from "playstate-labs";
const unityJson = unity.toUnityPayload(decision);
const godotDictionary = godot.toGodotPayload(decision);
const unrealPayload = unreal.toUnrealPayload(decision);
const payload = generic.toPayload(decision);Validate twice.
Adapters preserve the contract, but your engine must still allowlist types and validate payload values before execution.
