Skip to content

Repository files navigation

Eclipse

An Elixir/OTP MUD proxy that sits between your telnet client (e.g. Mudlet) and a MUD server. Eclipse intercepts both directions of the connection, decodes the protocol layer (telnet negotiation, MCCP2 compression, GMCP, line/prompt framing), and routes everything through a supervised plugin system with a declarative automation DSL — triggers, multi-line chains, aliases, timers, events, command queues, and a behavior tree engine for combat logic.

The core is MUD-agnostic and client-agnostic. Game-specific logic lives entirely in plugins.

Mudlet ──telnet──▶ Eclipse (localhost) ──telnet/MCCP2/GMCP──▶ MUD server
◀── triggers · chains · queues · BT routes ──

Why a proxy? Client-side scripting is limited by the client's language and single-threaded performance. Eclipse runs automation on the BEAM: each plugin is an isolated, supervised process; shared state lives in ETS with lock-free reads; a crashing plugin restarts cleanly without dropping your connection; and plugins hot-reload from disk while you stay connected.

Features (v1.0)

  • Full telnet stack — option negotiation, MCCP2 decompression (including mid-packet activation), GMCP parse/forward, EOR/GA prompt detection, ANSI handling
  • Plugin framework — OTP-supervised plugins with crash isolation, dependency ordering, TOML profiles, ETS shared state, and file-watching hot reload
  • Trigger & chain DSLoc_match / oc_then / oc_then_maybe decorators for regex line triggers, GMCP triggers, aliases, and multi-line chains with cancellation (oc_cancel_on), absolute/idle timeouts, enable/disable toggles, and one-shot effects (:gag_once, :replace_once)
  • Events & timers — Registry-based PubSub with framework lifecycle topics (:eclipse_*), a Timers GenServer (after/2, every/2), and a prompt evaluation loop
  • Command queuing — echo-sentinel CommandQueue tracking 14 queue types through a SENDING→WAITING→READY FSM with completion events
  • Behavior tree SDK — pure, gas-limited tick engine with 10 node types, a use Eclipse.BT.Route DSL for writing routes in Elixir, and a prompt-driven Eclipse.BT.Loop with runtime overrides

Quick start

Requires Elixir ~> 1.17 on OTP 27+.

git clone https://github.com/LionOps/Eclipse.git
cd Eclipse
mix deps.get
mix test# 800+ tests

Create a profile (host/port settings live in TOML, not code):

mkdir -p ~/.config/eclipse/profiles/default
cp docs/examples/default/profile.toml ~/.config/eclipse/profiles/default/
listen_port = 7777
[server]
host = "your-mud.example.com"port = 23
[plugins]
autoload = []

Start the proxy and point your client at it:

iex -S mix

Connect Mudlet (or any telnet client) to localhost:7777. Eclipse connects onward to the MUD and forwards everything — from there, plugins decide what to watch, gag, rewrite, or inject.

Writing a plugin

A plugin is a module. Decorators bind to the function defined directly beneath them.

defmoduleMyPlugins.VitalsdouseEclipse.Plugin,name: :vitals,deps: []# Regex line trigger: fires on matching server output.oc_match({:line,~r/^You have (\d+)\/(\d+) health/})defon_health(%Eclipse.MatchContext{frames: [%{captures: [hp,_max]}]},_seg,_conn_pid,state)do{:ok,Map.put(state,:hp,String.to_integer(hp))}end# GMCP trigger: fires on a GMCP package, payload already JSON-decoded.oc_match({:gmcp,"Char.Vitals"})defon_vitals(_ctx,seg,_conn_pid,state)do{:ok,Map.put(state,:vitals,seg.payload)}endend

Multi-line chains match a sequence of lines, with cancellation and timeouts. Cancellation handlers are co-located with the chain via oc_on_cancelled (state is bound in each clause; the last expression becomes the new state):

defmoduleMyPlugins.VortexdouseEclipse.Plugin,name: :vortex,deps: []oc_match({:line,~r/^You begin channeling Vortex\.$/,reset_on_prompt: false,idle_timeout: 60_000})oc_then({:line,~r/^Your channel of Vortex completes\.$/})oc_cancel_on({:line,~r/^You stop\.$/})oc_cancel_on({:line,~r/^You are interrupted\.$/})oc_on_cancelleddo:cancel_on->Map.put(state,:interrupted_at,System.monotonic_time()):idle_timeout->stateenddefon_vortex(_ctx,_seg,_conn_pid,state)do# runs only after both lines matched in order{:ok,state}endend

Triggers can gag or rewrite traffic with positional flags (oc_match(spec, :client, :gag)), plugins can inject data in either direction via Eclipse.ConnectionHandler.inject/2, and Eclipse.Plugins.Ping ships as a worked end-to-end example of a client-direction GMCP command.

Add your module to the profile's autoload list, or drop the file into the watched plugin directory in dev and it hot-reloads on save.

Architecture

WhereWhat
lib/eclipse/connection_handler.exPer-connection process; the bidirectional hot path
lib/eclipse/telnet_parser.exTelnet IAC state machine, MCCP2 inflation
lib/eclipse/gmcp_parser.ex, line_buffer.exGMCP framing, line/prompt splitting
lib/eclipse/middleware/Trigger dispatcher, chain cursor machinery
lib/eclipse/plugin.exPlugin behaviour + the oc_* decorator macros
lib/eclipse/plugins/Built-in plugins (CommandQueue, Ping, Core.Target)
lib/eclipse/bt/Behavior tree SDK: engine, nodes, route DSL, loop
test/Mirrors lib/; property tests cover the telnet parser

Design notes worth knowing: trigger dispatch runs in-process on the connection's hot path (no GenServer hop per line); plugin state is written by owning processes and read lock-free from ETS; the BT tick engine is a pure function — all persistent state lives in a controller struct passed through the tick.

Status

v1.0 (Proxy Core) shipped 2026-06-09 — 17 phases, 807 tests, the full feature set above. A detailed build record lives in .planning/reports/MILESTONE_SUMMARY-v1.0.md.

v2.0 (Developer Tooling) is in development: session recording & replay, telemetry, a session inspector, a GMCP control channel, and plugin-author docs/scaffolding.

License

MIT

About

Proxy Server SDK for MUD systems

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages