-
Notifications
You must be signed in to change notification settings - Fork 0
Command Parser and Targeting
Audience: Engine Developer Status: ✅ Ready
How a typed line becomes an action: the parser turns input into a verb + arguments, resolves a target using the classic Diku targeting grammar, and — for messages the action produces — routes perspective-aware output through act(). This page covers targeting and act(); the dispatch model (the four-namespace verb precedence, abbreviation, and rank gating) is documented on Builder Commands.
Related: Zone Runtime & Actor Model, Entity Component Model, Abilities & Effects.
Classic Diku (targeting.go):
sword → first visible match for "sword"
2.sword → the 2nd match
all.coin → every match for "coin"
all → everything in scope
long sword → an entity whose keywords include BOTH words (isname prefix)
parseTargetSpec produces a TargetSpec{all, bare, index, keywords}. Key rules:
- The
all./N.selector prefix applies only to the first word; the rest are additional isname keywords. -
all.setsall; a numericN.setsindex. An explicit0.xmaps toindex = -1, an "explicit-but-no-match" sentinel distinct from the unqualifiedindex = 0. A non-numeric prefix likea.swordis treated literally (the dot stays part of the keyword). - Bare
all(no keyword) means everything in scope. -
Untrusted-input hardening:
atoiBoundedaccepts only digits and caps at six (< 1e6), so a pasted mega-number can't drive large work or overflow; work is bounded by the scope population, never by input length.
isname matching (matchesKeywords): every typed word must be a case-insensitive prefix of one of the candidate's keywords. bare matches everything; an empty spec matches nothing.
Scope values: ScopeInventory, ScopeEquipment, ScopeRoomLiving, ScopeRoomItems, ScopeContainer. scopeCandidates concatenates candidate lists in the order the verb passes them, so a verb searching room-then-inventory finds the floor item before the carried one.
| Scope | Candidates |
|---|---|
ScopeInventory |
actor.contents |
ScopeRoomLiving |
room occupants with a Living, excluding the actor
|
ScopeRoomItems |
room contents without a Living, excluding the actor |
ScopeEquipment |
the actor's Wearer.worn, surfaced in content wear-slot order |
ScopeContainer |
resolved explicitly by get x from y → resolveInContainer
|
Resolve pulls candidates, filters to visible matches (via canSee, below) in scope order, then applies Diku selection: all → every hit; index > 0 → the Nth (1-based) or nil; index < 0 → nil (the explicit-no-match sentinel); default → first hit.
Resolve calls z.canSee(actor, candidate) before matching. canSee delegates to visibleTo — the single visibility chokepoint shared by targeting and act()'s leak surface. It honors: self/nil always visible; holylight sees all; invisible unless the viewer has detect_invis; a dark room unless the viewer has light/infravision; hidden unless sense_hidden; and staff wizinvis by trust rank. These are open-string flags the engine reads and content sets (holylight and the other trust flags are reserved — content cannot self-grant them; see Trust Tier Model).
Not a parser token:
me/selfis not part of the command targeting grammar. A player's only keyword is its own character name, and the room scopes exclude the actor — so a player cannot target themselves by name in a room scope. Theself/targetsynonyms live only in the ability/effect-op layer and in Lua handler binds, not in the player command grammar.
The Diku idiom: one call emits the right variant to the actor, the victim, and the room, with $-substitutions per recipient (act.go). Recipient selection (ActTo): ToActor, ToVictim, ToRoom, ToRoomExceptActor.
Substitution tokens (this is the complete set — a hand-written single-pass scanner, not Sprintf, so there is no format-string injection path):
| Token | Expands to |
|---|---|
$n / $N
|
actor name / victim name |
$p / $P
|
object name / second object name |
$t / $T
|
literal text args (copied verbatim, never re-scanned) |
$$ |
a literal $
|
Not implemented: the pronoun tokens
$s/$e/$m(possessive / subjective / objective) do not exist — there is no pronoun handling and no gender field to feed one.
nameFor is the per-recipient name resolver and the leak guard: "You" when the viewer is the entity, and "Someone" / "someone" when the viewer can't see the actor — the same canSee predicate as targeting, so messaging can never leak what targeting hid.
Two concealment behaviors, both keyed on canSee:
-
Presence lines (arrivals/departures) use
actConceal: a room recipient who can't see the actor gets nothing at all. A hidden, sneaking, or dark-room mover moves silently. -
Sound / masked lines (speech, combat) use plain
act(): the line is delivered, but the unseen actor is masked as"Someone".
The reasoning: a plain act() "Someone arrives." would leak that something concealed is present, so presence uses actConceal while ongoing sound stays audible-but-anonymous. (Presence-line stealth deliberately does not evade an aggressive mob's aggro-on-entry — that is a separate, intentional choice.)
TelosMUD — Wiki under construction.
- Builder Reference
- Builder Commands
- Trust Tier Model
- Pack Authoring
- Pack MUD Settings
- Pack Lua Scripting
- Pack Lua Hooks
- Pack Entity Reference
- Building Instanced Zones
- Engine Developer Reference
- Architecture Overview
- Entity Component Model
- Zone Runtime & Actor Model
- Instanced Zones
- Command Parser & Targeting
- Edge & Protocol
- GMCP Reference
- Persistence & Durability
- Content Loading & Hot Reload
- Abilities & Effects
- Combat System
- Loot, Spawns & Crafting
- Accounts & Auth Internals
- Orchestration & Directors
- Scoped Event Bus
- Cross-Shard Handoff
- Lua Sandbox Internals
- Distributed Systems Model
- RPC & Protobuf