Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

Core concepts

Branded IDs

Every domain entity uses a branded string ID (SceneId, DialogueId, CharacterId, VariableId, etc.). These are created via factory helpers and validated with Zod schemas at runtime.

import {
	DialogueIdSchema,
	dialogueId,
	GameBaseDataSchema,
	sceneId,
	variableId,
} from "@tikab-interactive/me-gosta";
 
const scene = sceneId("scene_intro");
const dialogue = dialogueId("dlg_intro");
const variable = variableId("trust");
 
const dialogueParse = DialogueIdSchema.safeParse(dialogue);
const gameParse = GameBaseDataSchema.safeParse({});
 
export const usageSummary = {
	scene,
	dialogue,
	variable,
	dialogueParseOk: dialogueParse.success,
	gameParseOk: gameParse.success,
};

The branded types prevent accidental cross-domain assignment at compile time while remaining plain strings at runtime (serializable, indexable, debuggable).

Effects

Player actions produce effects — a discriminated union keyed by kind. GameBase.handleEffect() dispatches each to the owning manager:

import type { GameBaseEffect } from "@tikab-interactive/me-gosta";
import { variableId } from "@tikab-interactive/me-gosta";
 
const baseEffects: GameBaseEffect[] = [
	{
		kind: "variable",
		name: "set-variable",
		variableId: variableId("trust"),
		value: 10,
	},
];
 
export function enqueueEffects(next: GameBaseEffect[]) {
	return [...baseEffects, ...next];
}

Effects are attached to dialogue options, narrative node transitions, or triggered programmatically. The full set of kinds: dialogue, scene, character, variable, time, achievement, notification.

Dialogue trees

A dialogue is a flat map of messages keyed by ID. Each message has optional speaker, internalMonologue flag, and a list of options. Options can carry conditions (gates) and effects (side-effects on selection).

import type { DialogueData } from "@tikab-interactive/me-gosta";
import { characterId, playerActionId } from "@tikab-interactive/me-gosta";
 
const jacobId = characterId("char_jacob");
const flirtActionId = playerActionId("act_flirt");
 
const messages: DialogueData["messages"] = {
	intro: {
		id: "intro",
		internalMonologue: true,
		message: "I am feeling nervous. What should I say?",
		options: [
			{
				text: "Steady yourself and look at Jacob",
				nextMessages: [{ id: "jacobGreeting" }],
			},
			{
				text: "Stall by admiring the skyline",
				nextMessages: [{ id: "skylineMonologue" }],
			},
		],
	},
	jacobGreeting: {
		id: "jacobGreeting",
		speaker: { characterId: jacobId },
		message: "Hey, you look thoughtful. Everything okay up here?",
		options: [
			{
				text: "Flirt: the view is almost as good as his smile",
				effects: [
					{
						kind: "achievement",
						name: "perform-action",
						playerActionId: flirtActionId,
					},
					{
						kind: "character",
						name: "change-trust",
						characterId: jacobId,
						amount: 2,
					},
				],
				nextMessages: [{ id: "jacobTease" }],
			},
			{
				text: "Play it safe: talk about the weather",
				effects: [
					{
						kind: "character",
						name: "change-trust",
						characterId: jacobId,
						amount: -1,
					},
				],
				nextMessages: [{ id: "weatherChat" }],
			},
		],
	},
	coffeeEnding: {
		id: "coffeeEnding",
		speaker: { characterId: jacobId },
		message: "Coffee sounds perfect. Let's sneak out.",
		options: [
			{
				text: "Accept the invitation",
				conditions: [
					{
						name: "character-trust",
						characterId: jacobId,
						kind: "min",
						threshold: 5,
					},
					{ name: "action-performed", playerActionId: flirtActionId },
				],
				nextMessages: [],
			},
		],
	},
};
 
export { messages };

Conditions reference character state (character-trust, threshold) or prior actions (action-performed). This enables gated branches without external logic.

Narrative graph

NarrativeTree is a DAG of nodes. Each node carries an effect list (what happens when the node activates) and a condition list (whether it's reachable). The firstNodeId kicks off traversal.

import type { GameBaseData } from "@tikab-interactive/me-gosta";
import {
	dialogueId,
	narrativeNodeId,
	sceneId,
} from "@tikab-interactive/me-gosta";
 
const narrative: GameBaseData["narrative"] = {
	firstNodeId: narrativeNodeId("node_rooftop"),
	nodes: [
		{
			id: narrativeNodeId("node_rooftop"),
			effects: [
				{ kind: "scene", name: "set-scene", sceneId: sceneId("scene_rooftop") },
				{
					kind: "dialogue",
					name: "start-dialogue",
					dialogueId: dialogueId("dlg_rooftop"),
				},
			],
			conditions: [],
			children: [narrativeNodeId("node_cafe")],
		},
		{
			id: narrativeNodeId("node_cafe"),
			effects: [
				{ kind: "scene", name: "set-scene", sceneId: sceneId("scene_cafe") },
			],
			conditions: [
				// Only reachable if trust is high enough
			],
			children: [],
		},
	],
};
 
export { narrative };

Scenes

A scene is a composition of visual objects — backgrounds, character sprites, clickable areas. The renderer (PixiJS in the example) reads the scene data and renders accordingly.

import type { SceneData } from "@tikab-interactive/me-gosta";
import { characterId, sceneId } from "@tikab-interactive/me-gosta";
 
const scene: SceneData = {
	id: sceneId("scene_rooftop"),
	objects: {
		background: {
			kind: "image",
			imageUrl: "https://cdn.example.com/rooftop-night.webp",
			position: { x: 0, y: 0 },
			scale: 1,
			depth: 0,
		},
		jacob: {
			kind: "character",
			characterId: characterId("char_jacob"),
			spritesheet: {
				assetId: "https://cdn.example.com/jacob-neutral.json",
				frameKey: "neutral",
			},
			position: { x: -0.2, y: 0 },
			scale: 1,
			depth: 5,
		},
	},
};
 
export { scene };

Save / Load

GameBase.save() serializes all manager state into a SaveGameBase. GameBase.load() rehydrates from it. The schema validates on load, making migrations explicit — if the shape changes, the parse fails and you handle it.

import type { SaveGameBase } from "@tikab-interactive/me-gosta";
 
export function cloneSave(save: SaveGameBase): SaveGameBase {
	return structuredClone(save);
}