engine · c# core

The engine that gets out of your way.

Tapestry's C# core ships only the primitives a MUD actually needs — entities, events, command routing, a scripting bridge — and leaves the game to packs. Everything is replaceable. Nothing is locked in.

pre-v1 .NET 10 Jint 4.7 AGPL-3.0 Cross-platform

Architecture #

Three primitives carry the whole engine: an entity with components, an event bus that fans out asynchronously, and a command router that turns text into intent. Everything else is a pack.

Entity

Entity
id + components + tags
Component
declarative data shape
Tag
cheap categorical marker
EntityStore
queryable index

Event Bus

Bus.Emit
fan-out to subscribers
Subscriber
declared in pack manifest
Priority
deterministic ordering
Cancellation
veto chain

Command Router

Parser
verb + target + args
Resolver
aliases & scopes
Handler
pack-provided
Output
structured to client

GMCP & the three-tier accessibility model #

Tapestry treats GMCP as a first-class output channel — not an afterthought. Every system describes its state through a typed schema, and the engine ships three presentation tiers so screen readers, terminal clients, and rich web clients all get appropriate fidelity from the same source of truth.

TIER 01

Plain text

Narrative prose. Always present. Telnet works without negotiation; screen readers get a clean stream.

TIER 02

Structured GMCP

Typed events like Char.Vitals and Room.Info — enough for clients to render panels and a11y feedback.

TIER 03

Rich payload

Optional extended schemas: ASCII maps, inventory diffs, dialog trees. Clients negotiate what they want.

Connections & identity #

The engine ships a built-in account system with email-based identity. Players register once and their session state — character, location, inventory — survives disconnects.

Link-dead reconnection handles the most common MUD frustration gracefully. When a player's connection drops, the engine holds their session for a configurable grace period. On reconnect, they resume silently with no loss of state. The grace period, idle timeout, and reconnect behavior are all tunable in server.yaml.

Pre-auth is opt-in per world. Disable it and players connect directly via telnet or the web client with no account required.

Packs & scripting #

A pack is a manifest plus a folder of YAML and JavaScript. Content is declarative; scripting is reserved for behaviour. The Jint runtime sandboxes pack scripts and exposes a curated API for the engine primitives.

manifest.yaml on_arrive.js
# @you/midnight-bazaar / tapestry.yaml name: "@you/midnight-bazaar" version: "0.3.1" type: world engine: ">=0.0.1" dependencies: "@tapestry/core": "^0.1.0" content: rooms: "areas/**/rooms/*.yaml" scripts: "scripts/**/*.js"

Why Jint?

We chose Jint over a custom DSL or embedded Lua so pack authors can use the tooling they already know — TypeScript types, editor autocomplete, npm-style linters. Scripts run in-process but sandboxed: no filesystem, no network unless the manifest declares it.

Install modes #

Three ways to run the engine. Pick what matches your operational shape.

Docker

For most worlds

$ docker compose up

A docker-compose.yml ships with the repo. Mounts your world directory, zero host .NET install needed.

Source

Direct run

$ dotnet run --project src/Tapestry.Server

Clone the repo and run with .NET 10 SDK. Fastest iteration loop for contributors and pack authors.

CLI

Package manager

$ npm install -g @tapestry-mud/cli

The tapestry CLI manages the full project lifecycle: init, install, start, publish, and engine management.

Repo & contribution #

The engine lives on GitHub. We practice spec-driven development: new features start with a written spec and a discussion window before any code lands. We label issues good first issue for newcomers.