Sharpee
Sharpee / Platform / Overview

Platform

Sharpee is the TypeScript platform underneath every Chord story, and a full authoring surface in its own right. This page is the under-the-hood tour for a reader who knows Chord and wants to see the machinery, or who wants to author directly in TypeScript.

One engine, two authoring surfaces

A Chord .story file compiles to a story IR; the story loader turns that IR into entities, traits, phrases, and wiring on the platform. A TypeScript story builds the same things directly against the API. Either way, the same engine runs the game. Chord is not a sandboxed subset of the platform. The two surfaces aim for full parity: anything you can express in a Chord story you can also express in a TypeScript story, and anything you can express in a TypeScript story you can also express in a Chord story.

The layers

Responsibility is deliberately split, and knowing the split tells you where anything lives:

LayerOwnsExamples
engineturn cycle, command execution, event dispatchscheduler, prose pipeline
world-modelentities, traits, behaviors, world stateContainerTrait, LightSourceBehavior
stdlibthe standard actions and common patternstaking, opening, going (43 actions)
parser-en-usgrammar patterns, command parsingverb patterns, noun resolution
lang-en-usevery piece of user-facing textmessages, formatters
storygame-specific content and overridescustom traits, puzzle logic
clientrendering and inputbrowser client, terminal REPL

Entities, traits, behaviors

Everything in the world is an entity; capability comes from traits composed onto it (Openable, Lockable, LightSource, Actor, …). Traits are data; the logic that manipulates them lives in behaviors, and behaviors own all world mutations:

const door = world.createEntity('cellar door', EntityType.DOOR);
door.add(new OpenableTrait());
door.add(new LockableTrait({ keyId: key.id }));

When Chord composes a door, lockable with the tarnished key, this is exactly what the loader builds.

Actions: the four-phase pattern

Every action, standard or story-authored, runs the same four phases:

  • validate: can this happen? Return ok, or a named error.
  • execute: mutate the world by delegating to behaviors. If your execute phase is complex, the logic belongs in a behavior.
  • report: emit the domain events that describe what happened.
  • blocked: emit the events for a validation failure.

Actions never print text. They emit domain events carrying a messageId plus parameters; at end of turn the engine's prose pipeline resolves those against the language layer and renders the output. That separation is what makes story-specific message overrides, perception filtering (darkness), and localization possible without touching action logic.

Text is a tree, not a string

The rendering layer is the phrase algebra: a message realizes to a tree of typed Phrase values (noun phrases with number, article selector, pronoun set; verbs; lists; choices), and a single per-locale Assembler walks the finished tree to produce articles, agreement, punctuation, and pronoun references. Nothing collapses to a string early, which is why "You can see Tobias here." and "a brass lantern" both come out right with no author effort.

Capability dispatch

Generic verbs get entity-specific meaning by registration, not by patching actions: a trait declares which action IDs it handles, a behavior implements the four phases for them, and the standard action delegates when it finds the trait. That is the platform's one extension seam for "this thing responds to LOWER differently," and it is the same seam Chord's authored traits compile onto.

Digging further