Back to Projects

Sharpee Platform Status

A Modern TypeScript Interactive Fiction Platform

April 2025 - February 2026 | 10+ Months of Development
20 npm Packages 48 Stdlib Actions 131 ADRs Written Zifmia 0.9.90 Desktop App Dungeonn 650/650 Complete

Platform Overview

Sharpee is a modern, TypeScript-based Interactive Fiction platform designed for extensibility, clean separation of concerns, and developer experience. This report covers one week of intensive development since February 11, 2026, highlighted by the completion of the Dungeon validation story at a perfect 650/650 score, endgame integration (750 total), a comprehensive documentation overhaul, and the Zifmia 0.9.90 desktop release with Linux support.

20
npm Packages
+1 since Feb 11
48
Stdlib Actions
200+
Grammar Patterns
131
ADRs Written
+4 since Feb 11
3,500+
Unit Tests
0.9.91
Beta Version
was 0.9.86

What's New (Feb 12 - Feb 18)

Dungeon Reaches Perfect Score — 650/650 + 100 Endgame

The Dungeon validation story — a critical platform test porting 1981 Mainframe Zork — achieved a perfect 650/650 main game score with the completion of the Canvas puzzle. Endgame integration adds 100 additional points for a 750-point total. All 17 walkthroughs pass (771+ tests). The complete game is playable from start to victory across CLI, browser, and Tauri desktop.

ADR-129: Transactional Score Ledger

Replaced ScoringService with WorldModel primitives (awardScore, revokeScore, hasScore, getScore, getScoreEntries, setMaxScore). Score state stored in world model, survives serialization.

Fast Test Bundle (38x Speedup)

esbuild bundles platform + story + tester into single JS file. Full walkthrough chain build time reduced from 95s to 2.5s. Template: scripts/test-bundle-template.js.

Plugin State Save/Restore

Envelope format { worldState, pluginStates } preserves state machine and scheduler state across save/restore. Critical for walkthrough chaining.

globalThis Registry Pattern

Capability registry and interceptor registry use globalThis to share state across require() boundaries in bundled JavaScript. Fixes ISSUE-052 cross-module registry loss.

Zifmia 0.9.90 Desktop Release

Linux installers (AppImage, .deb). Engine restart fix. Version sync across all 4 sources (package.json, version.ts, tauri.conf.json, Cargo.toml). Mac release script updated for Tauri v2.

ADR-100: Accessibility Accepted

Full audit of Zifmia React client. 3-tier ARIA implementation plan. Critical gap: no aria-live region on transcript output. Status moved from IDENTIFIED to ACCEPTED.

Documentation Overhaul

Root README expanded to 20 packages with roadmap. CONTRIBUTING.md rewritten. Architecture README with key ADR links. API Reference README listing 16 HTML pages. Full ADR index: 90 Implemented, 12 Proposed, 3 Accepted.

Transcript Tester Enhancements

output contains condition type for randomized content. [WHILE:] loop auto-skip bug fixed. Commands without assertions now a validation error. Conditional [IF:] blocks for non-deterministic NPC behavior.

Parser & Scope Overhaul (ADR-093)

5-Level Numeric Scope System

All 24 stdlib actions declare defaultScope + use context.requireScope()

// Scope levels (highest = most restrictive)
enum ScopeLevel {
  UNAWARE  = 0,  // Entity not known to player
  AWARE    = 1,  // Player knows it exists (can hear/smell)
  VISIBLE  = 2,  // Player can see it
  REACHABLE = 3, // Player can touch it
  CARRIED  = 4,  // In player's inventory
}

// Grammar: declares ONLY semantic constraints
grammar.forAction('if.action.opening')
  .verbs(['open'])
  .pattern(':target')
  .hasTrait('target', TraitType.OPENABLE)  // What kind of thing
  .build();

// Action: enforces scope dynamically
validate(context) {
  const check = context.requireScope(target, ScopeLevel.REACHABLE);
  if (!check.ok) return check.error!;
}

Implicit Take System

6 actions auto-take items when reachable but not carried

// Player types: "wear hat" (hat is on the floor, not in inventory)
// Engine: "(first taking the hat)" -> then wears it

const result = context.requireCarriedOrImplicitTake(item);
if (!result.ok) return result.error!;
// Applied to: dropping, inserting, giving, showing, throwing, wearing

Adjective Disambiguation (ADR-093)

Entities now declare adjectives on IdentityTrait. Parser scores adjective matches (+4 points) for multi-word disambiguation. "press yellow button" correctly resolves among 4 identically-named buttons. Wire disambiguation ("braided wire" vs "shiny wire") fixed via adjective declarations.

Text Output Pipeline (ADR-095/096)

Formatter System & Perspective Placeholders

42 action templates converted to perspective-aware output

// Article selection by noun type
{a:item}  // common -> "a sword" / "an apple"
          // proper -> "Excalibur" (no article)
          // mass -> "some water"

// List formatting with Oxford comma
{a:items:list}      // "a sword, a key, and a lantern"
{the:items:or-list} // "the north, the south, or the west"

// Perspective placeholders (42 template files updated)
'taken': "{You} {take} {the:item}."
// 2nd person: "You take the sword."
// 1st person: "I take the sword."
// 3rd person: "She takes the sword."

ADR-107: Dual-Mode Authored Content

Entities use literal text or message IDs for localization

// Either pattern works through the same API
description: "You are in a dark room"          // Literal text
description: "dungeo.msg.room.west_house"      // Message ID (localized)

// Single unified call handles both
const text = textService.getEntityText(entity, 'description', {}, world);

// Key lesson: trait properties like cantTakeMessage must use
// message IDs, not literal strings, to avoid dot-heuristic misrouting

Engine Plugin Architecture (ADR-120)

TurnPlugin Contract

4 packages + envelope save/restore

interface TurnPlugin {
  id: string;
  priority: number;  // NPC=100, StateMachine=75, Scheduler=50
  onAfterAction(context: TurnPluginContext): ISemanticEvent[];
  getState?(): unknown;   // For save/restore
  setState?(state: unknown): void;
}

// Save format preserves plugin state alongside world state
{ worldState: {...}, pluginStates: { scheduler: {...}, stateMachine: {...} } }
Plugin Package Priority Responsibility
@sharpee/plugin-npc 100 NPC service (extracted from engine, -57 lines)
@sharpee/plugin-state-machine 75 Declarative FSM runtime (ADR-119)
@sharpee/plugin-scheduler 50 Daemon/fuse scheduler service
@sharpee/plugins Core TurnPlugin contract + PluginRegistry

State Machine Migrations (ADR-119)

Five imperative event handlers migrated to declarative state machines:

Machine States Pattern
Trapdoor open / closed Linear progression
Death Penalty alive / one_death / game_over Linear with CustomEffect scoring
Rainbow inactive / active Bidirectional toggle
Reality Altered inactive / shown Terminal
Victory playing / victory Terminal (replaced daemon polling)

Zifmia Story Runner (ADR-121/122)

.sharpee Bundle Format

Zip archive: story.js (ESM) + meta.json + optional theme.css and assets/. Bundle size: ~147KB for Dungeon.

Tauri Desktop Client (0.9.90)

8 Rust IPC commands. Saves to AppData. Windows MSI + macOS DMG + Linux AppImage/DEB installers. Version sync across all 4 sources. Engine restart fix.

Save/Restore System

Multi-slot localStorage with lz-string compression (~70% reduction). Auto-save every turn. Envelope format preserves plugin state. Delta-based world state serialization.

Rich Media (ADR-122/124)

Entity annotations with illustrations. Scoped CSS themes. Asset map plumbing via React context. Float layout for images.

Browser Client

DOS-era Infocom aesthetic. Command history, status line, PC speaker beep. eventemitter3 for browser compatibility. Updated to 0.9.90-beta.

ADR-130: Zifmia vs Story Installer

Product identity boundary established: Zifmia = generic runner, story installer = author's branded product. Future npx sharpee build --tauri packaging.

Combat System

Melee Architecture

Story-level engine with platform interceptor hooks

// PC attacks NPC:
attacking.ts -> ActionInterceptor -> MeleeInterceptor (Dungeon)
                                  -> BasicCombatInterceptor (generic)

// NPC attacks PC:
npc-service.ts -> NpcCombatResolver -> meleeNpcResolver (Dungeon)
                                    -> basicNpcResolver (generic)

// No combat extension loaded:
"Violence is not the answer." // validation block
Component Details
melee.ts 390 lines. Canonical FIGHT-STRENGTH, BLOW resolution, 9 outcomes
melee-tables.ts 189 lines. DEF1/DEF2/DEF3 result tables from MDL source
melee-messages.ts 273 lines. 5 weapon tables, 3-7 variants per outcome
@sharpee/ext-basic-combat Generic combat for stories without custom melee
DIAGNOSE command Wound status messages matching MDL melee.137:302-324
Cure daemon CURE-CLOCK: heals 1 wound per 30 turns

Event Chaining & Domain Events (ADR-094/106)

Event Chain Registration

Cascade, override, and keyed registration modes

// Chain: opening a container automatically reveals contents
world.chainEvent('if.event.opened', (event, world) => {
  const items = getVisibleContents(event.data.entityId);
  if (items.length === 0) return null;
  return { type: 'if.event.revealed', data: { items } };
}, { mode: 'keyed', key: 'stdlib.chain.opened-revealed' });

// Transaction grouping
//   if.event.opened   { transactionId: 'txn-123', chainDepth: 0 }
//   if.event.revealed  { transactionId: 'txn-123', chainDepth: 1 }

Domain Events Migration (ADR-106)

25+ actions migrated from action.success to domain events

// Before: Generic action.success with messageId
world.createEvent('action.success', { messageId: 'if.action.taking.taken' });

// After: Domain event with typed data
world.createEvent('if.event.took', {
  messageId: 'if.action.taking.taken',
  params: { item: itemName, container: containerName },
});

// Key fix: NPC template substitution (ISSUE-054)
// Must include params: action.data in npc.spoke/npc.emoted events

Action Interceptors (ADR-118/126)

Hook Point Purpose
preValidate Block action before validation (e.g., hot axe prevents taking)
postValidate Add requirements after standard validation
preExecute Modify state before execution
postExecute React to execution (e.g., trap triggers)
postReport Add custom messages to output

9 stdlib actions with interceptor support: going, putting, throwing, pushing, entering, taking, attacking, switching_on, dropping.

ADR-126: Destination Interceptors

Going action checks BOTH source room interceptors (for if.action.going) and destination room interceptors (for if.action.entering_room). Used for gas room explosion on entry with lit flame source.

All Packages (20)

Foundation

@sharpee/core
@sharpee/world-model
@sharpee/if-domain

Runtime

@sharpee/engine
@sharpee/stdlib
@sharpee/sharpee

Language & Text

@sharpee/parser-en-us
@sharpee/lang-en-us
@sharpee/text-blocks
@sharpee/text-service

Plugins

@sharpee/plugins
@sharpee/plugin-npc
@sharpee/plugin-scheduler
@sharpee/plugin-state-machine

Extensions & Tools

@sharpee/ext-basic-combat
@sharpee/platforms/*
@sharpee/transcript-tester
@sharpee/if-services
@sharpee/event-processor

Zifmia is a standalone desktop application (not published on npm). See ADR-130 for the product identity boundary between the Zifmia runner and author story installers.

Dungeon (Zork) — Platform Validation Story

17 Walkthrough Chains | 771+ Tests Passing | 650/650 + 100 Endgame

Canonical fidelity verified against 1981 MDL source (mdlzork_810722). Score: 650/650 main game + 100 endgame = 750 total. All NPCs complete. Endgame implemented with randomized trivia. Full game playable start to victory.

Canvas Puzzle Complete (ADR-078)

Ghost ritual: burn incense, pray, drop frame piece. 34-point scoring arc. Three-phase basin puzzle. Frame breaking sequence after thief death.

Royal Puzzle Rewrite

Position-based card detection (pos 36 per MDL CPOBJS[37]). 48-move canonical solution with 19 pushes. Template rendering and entity resolution fixes.

Endgame Integration

4 critical bugs fixed: crypt door, stone button, scoring, inventory strip. Mirror Room soft-lock resolved. 100 milestone points across 3 endgame rooms.

Don Woods Stamp

MDL-accurate timed delivery: order, 3-turn fuse, mailbox placement. OTVAL=1 ("One Lousy Point"). Donald Woods ASCII art.

Volcano Region Overhaul

10 room connections fixed per MDL source. Glacier melting, brick/fuse/safe explosion. Balloon flight refactor with VehicleTrait.

FORTRAN Score Audit Complete

MDL 616-point version confirmed canonical. 4 non-canonical bonuses removed (55 pts). Score fully reconciled: 115 rooms + 260 OFVAL + 231 OTVAL + 10 light-shaft + 34 canvas = 650.

Architecture Decision Records (131 Total)

Complete ADR Audit: 90 Implemented | 12 Proposed | 3 Accepted

All 131 ADRs catalogued in flat table with verified statuses. Full roadmap section listing all non-implemented, non-abandoned ADRs. Plus 10 Superseded, 7 Abandoned, 4 Other.

New ADRs (Since Feb 11)

ADR-129 Transactional Score Ledger

WorldModel primitives replace ScoringService

ADR-130 Zifmia vs Story Installer

Product identity boundary for packaging

ADR-131 Automated World Explorer

BFS-driven game explorer for breadth coverage testing

Key Status Changes

ADR Change
ADR-078Canvas puzzle — now fully Implemented
ADR-100Screen Reader Client — IDENTIFIED to ACCEPTED, 3-tier plan written
ADR-004Marked Superseded (by four-phase action pattern)
ADR-013Marked Abandoned (lighting as extension)
ADR-034Marked Abandoned (event sourcing)
ADR-048Marked Abandoned (static language arch)
ADR-059Marked Abandoned (action customization boundaries)
ADR-112Marked Abandoned (client security model)
ADR-113Marked Abandoned (map position hints)

Previous ADRs (093-127) documented in the Feb 11 report.

Infrastructure & Build System

Fast Test Bundle

esbuild bundles platform + story + tester into single JS. Build: 95s to 2.5s (38x). --version VER flag on build.sh for explicit version control.

dist-npm/ Elimination

Eliminated dist-npm/ directory across all 19+ packages. Fixed dual module resolution bug where esbuild resolved stale dist-npm/ over current dist/.

NPM Publishing Fixes

ts-forge was rewriting imports to broken relative paths and stripping workspace dependencies. Fixed in tsf repo. All packages publishable.

Mac Release Script

Updated for Tauri v2 (DMG path changed). Restructured for extract, codesign, rebuild. Fixed root-owned file permissions and trap cleanup race.

Website Updates

Mobile responsive nav (flex-wrap + media queries). Browser client updated to 0.9.90-beta. Stale games/dungeo/ directory pruned. Downloads page with Linux installers.

Documentation Overhaul

README: 20 packages + roadmap. CONTRIBUTING: rewritten (149 in, 450 out). Architecture README with key ADRs. API Reference: 16 HTML pages. ADR index: complete.

Development Timeline (Since Feb 11)

February 12, 2026

ADR-129: Transactional Score Ledger + NPC Serialization Fix

Replaced ScoringService with WorldModel primitives. Fixed NPC canAct getter lost after JSON serialization. Thief re-stealing fix via IdentityTrait.concealed.

February 13, 2026

dist-npm/ Elimination + Capability Registry Fix + Volcano Region

Eliminated dual module resolution across all packages. globalThis pattern for cross-module registry (ISSUE-052). Volcano room connections fixed per MDL. 5 new MDX developer guide pages.

February 14, 2026

Volcano Puzzles + Balloon VehicleTrait Refactor

Glacier melting, brick/fuse/safe explosion. Balloon flight rewritten with VehicleTrait integration. GDT commands removed from walkthroughs, replaced with real navigation.

February 15, 2026

Serialization Audit + NPM Publishing Fix + Exorcism Fix

~50 (entity as any) instances converted to entity.attributes. ts-forge import rewriting fix. Exorcism handler event ID corrected. Commands without assertions now validation errors.

February 16, 2026

Fast Test Bundle (38x) + Plugin State Save + Walkthrough Renumber

esbuild single-file test bundle. Save/restore envelope format with plugin states. All walkthroughs renumbered wt-01 through wt-15. Score: 600/650. canContain(), entity.name, scheduler fuse, disambiguation fixes.

February 17, 2026

Royal Puzzle + Canvas Puzzle + Don Woods Stamp = 650/650

Royal puzzle rewritten with position-based card detection. Canvas/ghost ritual completed (34 pts). Timed brochure delivery. ADR-130 Zifmia packaging. Zifmia 0.9.90 released. 771 tests across 16 walkthroughs.

February 18, 2026

Endgame Complete + ADR-100 Accessibility + Documentation Overhaul

4 endgame bugs fixed, 100 milestone points, wt-17 endgame walkthrough. ADR-100 accepted with 3-tier ARIA plan. Mobile nav fix. All docs rewritten: README, CONTRIBUTING, architecture, API, ADR index (131 ADRs). Version 0.9.91.

For the full timeline from April 2025 through February 11, 2026, see the previous report.

Platform Bugs Fixed (Feb 12-18)

Bug Fix
NPC serialization (canAct getter lost) Replace with direct isAlive && isConscious property access
Dual module resolution (dist/ vs dist-npm/) Eliminated dist-npm/ entirely across all packages
Capability registry cross-module (ISSUE-052) globalThis pattern for Map persistence
canContain() missing VEHICLE/ENTERABLE One-line fix adding both trait checks
entity.name priority Fixed: IdentityTrait.name > displayName > id
Scheduler fuse same-turn tick (off-by-one) skipNextTick boolean flag
Disambiguation {options} rendering (ISSUE-054) Skip tryProcessDomainEventMessage for client.query
MessageEffect event loss (ISSUE-056) One-line push to pendingEmittedEvents
Engine restart double entities New restartGame() method with world.clear()
NPM publishing (ts-forge import rewriting) Fixed in tsf repo; stopped stripping workspace deps

Platform Quality Assessment

Overall Grade: A+

The platform has reached a new milestone: the Dungeon validation story is complete with a perfect score, the endgame is implemented, all 17 walkthroughs pass, and all documentation has been rewritten. 131 ADRs catalogued and verified, 20 npm packages published, accessibility roadmap accepted.

Aspect Rating Key Improvement (Since Feb 11)
Architecture Excellent Score ledger in world model, globalThis registry, plugin state save
Extensibility Excellent ADR-130 packaging boundary, condition evaluator extensions
Text Pipeline Excellent NPC template substitution fix (ISSUE-054), message ID enforcement
Parser Excellent Wire/alias disambiguation, entity.name priority fix
Desktop Client Excellent 0.9.90 release, Linux installers, engine restart, version sync
Combat System Excellent NPC serialization fix, full game verified through all walkthroughs
Build System Excellent 38x fast test bundle, dist-npm elimination, NPM publishing fixes
Test Coverage Excellent 3,500+ unit tests, 17 walkthroughs (771+ tests), all passing
Documentation Excellent Complete rewrite: README, CONTRIBUTING, architecture, API, 131 ADRs indexed
Story Fidelity Excellent 650/650 perfect score, endgame complete, FORTRAN audit reconciled
Accessibility Planned ADR-100 accepted, 3-tier ARIA plan, audit complete

What's Next

Category Remaining Work
Accessibility (ADR-100) Tier 1: transcript aria-live, input label, status line ARIA. Tier 2: semantic HTML5, dialog focus traps. Tier 3: announcements, skip links, high contrast
ADR-131 Automated world explorer (BFS-driven breadth coverage testing)
ADR-130 Story installer packaging (npx sharpee build --tauri)
ADR-089 Phase E Advanced verb conjugation, past tense support
Dungeon Polish Thief movement to fixed room-list cycling (matches MDL). Royal puzzle second exit (slot + steel door). Full 750/750 walkthrough chain clean pass
GenAI Layer Story spec templates, LLM code generation pipeline
NPC System Conversation trees, behavior scheduling, dialogue extensions
File Renames ADR-013b merge into ADR-014, ADR-013c to intfiction-post.md, ADR-082 duplicate to ADR-082z