# p4o docs ## Example This is an example page. ## Get started Hello world! ## p4o p4o (Paketo) is Tikab's internal package ecosystem for shared modules. Use p4o when you want reusable functionality with a stable API across multiple projects. ### Why p4o exists * Reusability across repositories * Isolation from app-specific implementation details * Stable and versioned interfaces * Better long-term scalability for internal tooling ### What to read next * [What is and is not a package](./what-is-a-package) * [Package fit checklist](./package-fit-checklist) ## Package Fit Checklist Use this quick checklist before extracting code into p4o. ### 1) Rule of three Do you have at least two consumers now (or very soon)? ### 2) Boundary and cohesion Can you explain the module in one sentence, and do all exports match that purpose? ### 3) API stability Can you keep a small, stable API with versioning discipline? ### 4) Independence Can it run with explicit inputs instead of app globals? ### 5) Testability Can it be tested without booting the entire app stack? ### 6) Footprint Does it avoid pulling in large frameworks for a tiny feature? ### 7) Ownership Is there a clear owner to maintain versions and docs? If the answer is yes for most points, it is a strong package candidate. ## What Is and Is Not a Package A good p4o package is a small, cohesive, reusable unit with: * a clear API * no app-specific assumptions ### A package should have * High cohesion: one clear purpose * Low coupling: minimal knowledge of host app internals * Stable API surface: documented and versioned * Deterministic behavior: predictable inputs/outputs and test coverage * Portability: usable by multiple projects without modifications ### Good candidates * Shared utilities: parsing, formatting, validation * Integration clients with stable contracts * Cross-cutting primitives: logging, caching, retry/backoff ### Not good candidates * App-specific routing/page logic * Code tightly bound to one environment or one product * Thin wrappers with no additional value * Hidden mutable singletons with implicit side effects ## 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. ```ts twoslash // [!include ~/snippets/me-gosta/ids-and-schemas.ts] ``` 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: ```ts twoslash // [!include ~/snippets/me-gosta/effects.ts] ``` 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). ```ts twoslash // [!include ~/snippets/me-gosta/dialogue-tree.ts] ``` 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. ```ts twoslash // [!include ~/snippets/me-gosta/narrative-node.ts] ``` ### 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. ```ts twoslash // [!include ~/snippets/me-gosta/scene-composition.ts] ``` ### 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. ```ts twoslash // [!include ~/snippets/me-gosta/save-load.ts] ``` ## Visual novel example The `examples/` folder ships a complete visual novel built on me-gosta. It demonstrates the full lifecycle: data authoring → engine bootstrap → PixiJS rendering → dialogue UI → save/load. ### Project structure ``` examples/src/ ├── engine/ # GameEngine wrapper, React provider, PixiJS stage ├── assets/ # Scenario data, character sprites, scene backgrounds │ └── dialogues/ # Branching dialogue trees (rooftop scenario) ├── components/ │ └── dialogue/ # DialogueOverlay, typewriter hook, option selection └── App.tsx # Demo harness: start → play → game-over states ``` ### Scenario data A full `GameBaseData` defines the entire game state — narrative nodes, scenes, dialogues, characters, variables, achievements — in one serializable object: ```ts twoslash // [!include ~/snippets/me-gosta/scenario-data.ts] ``` ### Dialogue authoring Messages are keyed by ID in a flat map. Options on each message branch the tree. Effects and conditions on options gate progression and modify state: ```ts twoslash // [!include ~/snippets/me-gosta/dialogue-authoring.ts] ``` Key patterns: * `internalMonologue: true` — renders differently in the UI (no speaker name, italic styling) * `effects` on options — fire when selected (character trust changes, achievement unlocks) * `conditions` on options — only show the option if met (trust threshold, action performed) ### Engine lifecycle ```ts twoslash // [!include ~/snippets/me-gosta/engine-lifecycle.ts] ``` The engine exposes observables (`game$`, `currentScene$`, `currentDialogueId$`) so the React layer subscribes and re-renders without polling. ### PixiJS rendering The `PixiStage` component subscribes to `currentScene$`. When the scene changes, it: 1. Destroys current sprites 2. Loads new background and character textures via `Assets.load()` 3. Positions sprites using normalized coordinates (\[-1, 1] → viewport center) 4. Respects `depth` for z-ordering (background at 0, characters at 5+) Scene data drives the renderer — no imperative draw calls in game logic. ### Dialogue UI `useDialogue` hook subscribes to `DialogueManager.getCurrentDialogue()` and the current message observable. It drives: * Typewriter text animation (Typed.js) * Speaker name display * Numbered option buttons (1–9 hotkeys + Enter to advance) * Dialogue-end detection When the player selects an option, effects fire immediately through `GameBase.handleEffect()`, which routes to the relevant manager (character trust, scene change, achievement unlock, etc.). ## Getting started ### Bootstrapping a game A game is a `GameBaseData` object — a plain serializable structure defining all content upfront. No imperative setup; the engine hydrates from data. ```ts twoslash // [!include ~/snippets/me-gosta/bootstrap.ts] ``` `GameBase` reads the data, initializes all managers, and calls `getNarrativeTree().init()` to set the entry node. From there the narrative graph drives scene transitions and dialogue starts via effects. ### Engine wrapper In production you'll wrap `GameBase` in a thin `GameEngine` class that handles: * IndexedDB persistence (`save()` / `load()`) * Observable state for React bindings (`game$`, `currentScene$`, `currentDialogueId$`) * Schema registration (`GameEffectSchemaRegister`) See `examples/src/engine/GameEngine.ts` for a complete reference implementation. ### React integration ```tsx // Minimal provider pattern const engine = new GameEngine({ data: myScenario }); ; ``` `useGameEngine()` pulls the engine from context. Subscribe to observables for reactive UI. ### Next * [Core Concepts](./core-concepts) — ID system, effects, schemas * [Visual Novel Example](./examples) — Full walkthrough ## me-gosta Runtime engine for narrative-driven products — visual novels, branching dialogues, character-driven games. ### Architecture at a glance `GameBase` is the central orchestrator. It owns a set of managers, each responsible for one domain: | Manager | Responsibility | | -------------------- | --------------------------------------------------------------- | | `NarrativeTree` | DAG of story nodes, conditions, branching | | `DialogueManager` | Message trees with options, speakers, effects | | `SceneManager` | Visual composition (backgrounds, character sprites, clickables) | | `CharacterManager` | Character state (trust, morale, stress, expression) | | `VariableManager` | Arbitrary typed variables (int, string, bool) | | `TimeManager` | Discrete time steps | | `AchievementManager` | Player actions and unlockable achievements | | `ChapterManager` | High-level story structure | Effects are the glue — every player choice dispatches a discriminated-union effect that `GameBase.handleEffect()` routes to the correct manager. ### Sections * [Core Concepts](./core-concepts) — ID system, effects, data schemas, save/load * [Visual Novel Example](./examples) — Full walkthrough of `examples/` showing scene composition, dialogue trees, and the engine lifecycle