Add prompt system design doc
This commit is contained in:
parent
74538756d5
commit
01d5848178
1 changed files with 232 additions and 0 deletions
232
docs/how/prompt-system.txt
Normal file
232
docs/how/prompt-system.txt
Normal file
|
|
@ -0,0 +1,232 @@
|
||||||
|
prompt system
|
||||||
|
=============
|
||||||
|
|
||||||
|
modal prompts that show useful info, with color markup support.
|
||||||
|
|
||||||
|
|
||||||
|
what we have now
|
||||||
|
================
|
||||||
|
|
||||||
|
server.py lines 314-320 picks between three hardcoded strings:
|
||||||
|
|
||||||
|
editor mode: " {line_number}> "
|
||||||
|
IF mode: "> "
|
||||||
|
everything: "mud> "
|
||||||
|
|
||||||
|
no stats, no color, no per-player customization. the prompt function
|
||||||
|
is inline in the server loop.
|
||||||
|
|
||||||
|
|
||||||
|
what we want
|
||||||
|
============
|
||||||
|
|
||||||
|
a prompt renderer: given a player, return the prompt string (with ANSI
|
||||||
|
already baked in based on their color_depth). each mode has its own
|
||||||
|
prompt template. templates use variables that get filled from player
|
||||||
|
state.
|
||||||
|
|
||||||
|
default prompts by mode:
|
||||||
|
|
||||||
|
normal mode:
|
||||||
|
<56%> 200 >
|
||||||
|
|
||||||
|
- stamina as percentage (stamina / max_stamina * 100, rounded)
|
||||||
|
- power level as integer (floor of pl)
|
||||||
|
- the > delimiter
|
||||||
|
|
||||||
|
combat mode:
|
||||||
|
<56%> 200 [punch left] vs Goku >
|
||||||
|
|
||||||
|
- same stamina/pl gauges
|
||||||
|
- current attack or defense stance in brackets
|
||||||
|
- opponent name
|
||||||
|
- if no active move: [idle]
|
||||||
|
|
||||||
|
editor mode:
|
||||||
|
(keep as-is: " {line}> ")
|
||||||
|
editor prompt is special — it comes from the editor, not the
|
||||||
|
prompt system. leave it alone.
|
||||||
|
|
||||||
|
IF mode:
|
||||||
|
(keep as-is: "> ")
|
||||||
|
the z-machine has its own prompt conventions. don't override.
|
||||||
|
|
||||||
|
|
||||||
|
color markup codes
|
||||||
|
==================
|
||||||
|
|
||||||
|
add a lightweight color markup language for prompt templates and
|
||||||
|
eventually for all builder-authored text. similar to miniboa's caret
|
||||||
|
codes but using {color} syntax so it reads more naturally and doesn't
|
||||||
|
collide with content text.
|
||||||
|
|
||||||
|
codes (16-color baseline):
|
||||||
|
|
||||||
|
{red} {RED} (bright)
|
||||||
|
{green} {GREEN}
|
||||||
|
{yellow} {YELLOW}
|
||||||
|
{blue} {BLUE}
|
||||||
|
{magenta} {MAGENTA}
|
||||||
|
{cyan} {CYAN}
|
||||||
|
{white} {WHITE}
|
||||||
|
{black} {BLACK}
|
||||||
|
{bold} {dim}
|
||||||
|
{reset} {/} (shorthand for reset)
|
||||||
|
|
||||||
|
two escaping approaches considered:
|
||||||
|
|
||||||
|
1. miniboa-style: ^r for red, ^R for bright red. terse, fast to type
|
||||||
|
in-game. but ^ collides with mountain terrain char and regex.
|
||||||
|
2. {tag} style: {red}, {bold}, {/}. more readable, no collisions,
|
||||||
|
familiar from other MUDs (CircleMUD uses &, ROM uses {).
|
||||||
|
|
||||||
|
going with {tag} style. it's what builders will type in room descriptions
|
||||||
|
and NPC dialogue too, so it should be readable. the render function
|
||||||
|
converts tags to ANSI based on client color depth, or strips them for
|
||||||
|
clients with no color support.
|
||||||
|
|
||||||
|
implementation: a colorize(text, color_depth) function in render/colors.py
|
||||||
|
that replaces {tag} tokens with the appropriate ANSI escape. clients with
|
||||||
|
caps.ansi=False get tags stripped entirely.
|
||||||
|
|
||||||
|
|
||||||
|
prompt template variables
|
||||||
|
=========================
|
||||||
|
|
||||||
|
templates are strings with {variable} references. the prompt renderer
|
||||||
|
fills them from player state.
|
||||||
|
|
||||||
|
core variables (available in all modes):
|
||||||
|
|
||||||
|
{stamina_pct} stamina as integer percentage
|
||||||
|
{stamina} current stamina (float, 1 decimal)
|
||||||
|
{max_stamina} stamina ceiling
|
||||||
|
{pl} power level (integer)
|
||||||
|
{name} player name
|
||||||
|
{mode} current mode string
|
||||||
|
{x}, {y} coordinates
|
||||||
|
|
||||||
|
combat-only variables:
|
||||||
|
|
||||||
|
{move} current attack/defense name, or "idle"
|
||||||
|
{opponent} opponent name
|
||||||
|
{combat_state} telegraph/window/idle
|
||||||
|
|
||||||
|
these overlap with color tag syntax ({red} vs {pl}). resolution:
|
||||||
|
color tags are a fixed set of known names. anything not in the color
|
||||||
|
tag set gets looked up as a template variable. or: process template
|
||||||
|
variables first (they produce plain text), then process color tags
|
||||||
|
(they produce ANSI). two-pass approach, clean separation.
|
||||||
|
|
||||||
|
|
||||||
|
the prompt function
|
||||||
|
===================
|
||||||
|
|
||||||
|
def render_prompt(player: Player) -> str
|
||||||
|
|
||||||
|
lives in a new module: src/mudlib/prompt.py
|
||||||
|
|
||||||
|
logic:
|
||||||
|
1. pick the template string based on player.mode
|
||||||
|
2. build a context dict from player state
|
||||||
|
3. fill template variables (str.format_map or similar)
|
||||||
|
4. convert color tags to ANSI (based on player.caps.color_depth)
|
||||||
|
5. return the final string
|
||||||
|
|
||||||
|
the server loop replaces its inline if/elif with:
|
||||||
|
|
||||||
|
from mudlib.prompt import render_prompt
|
||||||
|
_writer.write(render_prompt(player))
|
||||||
|
|
||||||
|
editor and IF modes short-circuit before template processing (they have
|
||||||
|
their own prompt logic that shouldn't change).
|
||||||
|
|
||||||
|
stamina gauge coloring: the prompt template itself can include
|
||||||
|
conditional color. simplest approach: the template variable {stamina_pct}
|
||||||
|
is just a number, but we provide a {stamina_gauge} that includes color:
|
||||||
|
|
||||||
|
>= 60%: {green}<{stamina_pct}%>{/}
|
||||||
|
30-59%: {yellow}<{stamina_pct}%>{/}
|
||||||
|
< 30%: {red}<{stamina_pct}%>{/}
|
||||||
|
|
||||||
|
the gauge is a computed variable, not raw data. the template for normal
|
||||||
|
mode becomes:
|
||||||
|
|
||||||
|
{stamina_gauge} {pl} >
|
||||||
|
|
||||||
|
with the gauge expanding to e.g. {green}<56%>{/} which then gets
|
||||||
|
colorized.
|
||||||
|
|
||||||
|
pl coloring: same idea. {pl_gauge} colors based on absolute value or
|
||||||
|
percentage of starting PL. details TBD — we might want max_pl on Entity
|
||||||
|
first.
|
||||||
|
|
||||||
|
|
||||||
|
implementation phases
|
||||||
|
=====================
|
||||||
|
|
||||||
|
phase 1: prompt module + normal mode prompt
|
||||||
|
- create src/mudlib/prompt.py with render_prompt()
|
||||||
|
- implement stamina_gauge and pl display
|
||||||
|
- wire into server loop (replace hardcoded "mud> ")
|
||||||
|
- tests for render_prompt with mock players
|
||||||
|
- editor and IF modes unchanged (short-circuit)
|
||||||
|
|
||||||
|
phase 2: combat mode prompt
|
||||||
|
- add combat context variables (move, opponent, state)
|
||||||
|
- combat prompt template
|
||||||
|
- tests with mock encounters
|
||||||
|
|
||||||
|
phase 3: color markup engine
|
||||||
|
- create src/mudlib/render/colors.py with colorize()
|
||||||
|
- {tag} to ANSI conversion, respects color_depth
|
||||||
|
- strip tags for no-color clients
|
||||||
|
- integrate into prompt renderer
|
||||||
|
- tests for colorize at each color depth
|
||||||
|
|
||||||
|
phase 4: per-player prompt customization (future)
|
||||||
|
- player.prompt_template stored in sqlite
|
||||||
|
- "prompt" command to set custom template
|
||||||
|
- default template per mode if player hasn't customized
|
||||||
|
- builder mode template (future: brush, color hex, etc.)
|
||||||
|
|
||||||
|
|
||||||
|
files touched
|
||||||
|
=============
|
||||||
|
|
||||||
|
new:
|
||||||
|
src/mudlib/prompt.py — the prompt renderer
|
||||||
|
src/mudlib/render/colors.py — color markup engine
|
||||||
|
tests/test_prompt.py — prompt tests
|
||||||
|
tests/test_colors.py — color markup tests
|
||||||
|
|
||||||
|
modified:
|
||||||
|
src/mudlib/server.py — replace inline prompt with render_prompt()
|
||||||
|
src/mudlib/entity.py — maybe add max_pl if we want PL gauge coloring
|
||||||
|
|
||||||
|
|
||||||
|
future ideas
|
||||||
|
============
|
||||||
|
|
||||||
|
- GMCP prompt data: send structured prompt info over GMCP so graphical
|
||||||
|
clients can render their own gauges. the text prompt is for raw telnet.
|
||||||
|
|
||||||
|
- builder mode prompt: when we add the world editor, show brush type,
|
||||||
|
current terrain char, and palette color.
|
||||||
|
[brush: T] [#1b5e20] build>
|
||||||
|
|
||||||
|
- rest/regen indicator: show a + or ~ when stamina is regenerating.
|
||||||
|
<56%+> 200 >
|
||||||
|
|
||||||
|
- compass rose: show available exits inline.
|
||||||
|
<56%> 200 [NSEW] >
|
||||||
|
|
||||||
|
- danger indicator: flash or color the prompt when PL drops low enough
|
||||||
|
that one more hit could KO.
|
||||||
|
|
||||||
|
- custom prompt command examples:
|
||||||
|
prompt {stamina_gauge} {pl} >
|
||||||
|
prompt {stamina_gauge} {pl} [{move}] >
|
||||||
|
prompt {name} [{mode}] >
|
||||||
|
|
||||||
|
- MXP/MNES clickable prompt elements (far future)
|
||||||
Loading…
Reference in a new issue