Skip to content

Lots of improvements overall - #2

Open
bubthegreat wants to merge 105 commits into
quixadhal:masterfrom
bubthegreat:master
Open

Lots of improvements overall#2
bubthegreat wants to merge 105 commits into
quixadhal:masterfrom
bubthegreat:master

Conversation

@bubthegreat

@bubthegreat bubthegreat commented Dec 2, 2021

Copy link
Copy Markdown
  • Adds standard python packaging structure
  • Adds basic framework for testing
  • Adds pre-commit hooks to keep linting, docstrings, whitespace, formatting, etc consistent
  • Adds github workflows for pytest
  • Various bug fixes
  • Removes legacy code
  • Fixes some of the relative import issues with data
  • Adds entrypoints from pip install to run the server with rom24

bubthegreat and others added 30 commits August 11, 2026 17:49
Adds rom24.api: a stable surface content authors write against instead of deep
engine imports. @api.command/@api.spell register ctx-style handlers; CommandCtx/
SpellCtx expose curated output/lookup/combat/skill/timing/rng helpers plus a
documented-unstable ctx.engine escape hatch. Constants re-exported from merc.

Pilot conversions (coexist with old (ch, argument) style; dispatch, cmd_table,
and Living do_* binding unchanged):
- commands: do_say (output + escape hatch), do_kick (skill combat),
  do_bash (heavy combat, uses ctx.engine.state_checks for GET_AC)
- spells: spell_armor (defensive buff), spell_fireball (offensive damage)

Validated: 56 tests (4 new ctx tests). Live telnet run — say fires the wizard
speech + random progs, combat dispatches, do_bash gates correctly on class
skill, spells cast, no errors.
…tion

94 spell modules converted: @api.spell decorator (from the skill_type metadata)
replaces const.register_spell; signature spell_x(ctx) with sn/level/ch/victim/
target aliased from ctx. Bodies unchanged (pre-existing latent bugs preserved).
Added api.register for 1:1 command-name registration (used by the command wave).
96 spells register; 56 tests pass. spell_ventriloquate was already dead/
unregistered (signature-only conversion).
All 210 remaining command modules converted: def do_X(ctx) with ch/argument
aliased from ctx; each interp.register_command(cmd_type(...)) becomes one
api.register(name, do_X, pos/level/log/show[, default_arg]) — preserving every
alias and its exact metadata. Bodies unchanged (pre-existing bugs preserved).

- api.register gains default_arg (do_help's motd/imotd/rules/... entries).
- Edge cases handled by the conversion: multi-name aliases, self-param signatures
  (do_pick/do_rest), and do_save's cmd_table[...] assignment form.

245 commands register; boots 48 areas; 56 tests pass; live telnet smoke across
look/score/say/time/wear/get/movement clean. Commands + spells are now uniformly
ctx-style on the rom24.api surface (bodies migrate to curated ctx.* incrementally).
…mands

Fix broken-tail commands/spells; add e2e smoke tests
Content module system: Phase 1 (data packs) + Phase 2 (JSON areas)
…nversion

Undefined-name bugs (latent runtime NameErrors that only fire when the spell/
command runs) found via pyflakes and fixed:
- 20 spells used 'instance' without importing it -> added the import.
- spell_detect_poison: undefined 'obj' -> alias obj = victim (TAR_OBJ_INV target).
- spell_create_water: undefined LIQ_WATER -> 0 (water is liq_table index 0).
- spell_continual_light: 'item' -> 'light' (the looked-up object).
- spell_heat_metal: 'dam' used before assignment -> init dam = 0.
- spell_ventriloquate (dead/unregistered): target_name/is_exact_name resolved.
- do_log: undefined fLogAll -> module-level toggle + global.

Logic bugs:
- spell_dispel_magic: 'found' referenced before assignment -> init found = False.
- do_pick: check_improve('pick_lock') -> 'pick lock' (matched the skill name).
- do_oset: arg3.isdigit -> arg3.isdigit() (missing call: always-truthy method).

Guard: tests/test_no_undefined_names.py runs pyflakes over packs/core and fails
on any undefined name (added pyflakes dev dep). 57 tests pass; boots 48 areas.
Fix latent NameErrors + logic bugs surfaced by ctx conversion
Dockerfile referenced requirements.txt and setup.py (both removed in the uv/
pyproject migration), so docker build failed immediately. Now:
- copy pyproject.toml + README.md + src, editable-install from pyproject (pulls
  jsonschema/psutil; keeps game data under /pyrom/src/{packs,areas,data}).
- TCP healthcheck (the port is telnet, not HTTP — old curl HTTP check was always
  unhealthy).
Makefile install-deps used the missing requirements.txt -> uv sync; run/test use
uv run.

Verified: docker build succeeds; container boots 7623 instances, reports 'ready
to rock on port 1337', is healthy, and accepts a telnet connection.
Fix Dockerfile/Makefile for deploy (post-refactor)
…nct IPs)

Raw telnet can't be hostname-routed through an HTTP/TCP ingress (no Host header/
SNI), so instead of the port-offset ingress-nginx design, each env gets its own
MetalLB LoadBalancer IP on port 1337, resolved by DNS:
  prod    192.168.0.42  -> pyrom.bubtaylor.com
  dev     192.168.0.43  -> dev-pyrom.bubtaylor.com
  staging 192.168.0.44  -> staging-pyrom.bubtaylor.com

- base Service: ClusterIP -> LoadBalancer, externalTrafficPolicy: Local (keeps
  real client IP).
- overlays: pin each env's IP via the metallb.universe.tf/loadBalancerIPs
  annotation. The ingress-nginx TCP configmap + its manual patch are no longer
  needed.
Deploy: LoadBalancer per env (MetalLB, same port 1337)
…ite-back)

- Overlays pin bubthegreat/pyrom:0.0.1-alpha2 (off :latest).
- ApplicationSet annotated for Image Updater: auto-update within the pinned 0.0.x
  line (alphas allowed), argocd write-back; ignoreApplicationDifferences on
  kustomize images so the set doesn't revert the override.
- DEPLOYMENT.md: corrected routing reality (router forwards 1337-1339 -> gateway
  .20; MetalLB is LAN-only) + image auto-update + release flow.
…ateway TCPRoute

pyrom is now reached through homelab-gateway (tcp-1337 prod / tcp-1338 dev ->
pyrom-service), so the per-env MetalLB LoadBalancer IPs are redundant. Drop them
(ClusterIP) and remove the MetalLB pin patches. Validated: public IP :1337/:1338
serve the ROM banner through the gateway.
…crash-loop)

Pod was liveness-killed (exit 137), not OOM (271Mi/512Mi). The single-threaded
game loop briefly blocks accept() on login (player save to slow PVC), tripping
the 1s liveness timeout x3. And 29s boot > 15s initialDelay. Fix:
- startupProbe (up to ~150s) so probes wait for boot.
- liveness timeout 1s->5s, period 10s->20s, failureThreshold 3->6.
- CPU limit 500m->1000m (boot was CPU-starved at 29s), mem 512Mi->768Mi.
…bjects)

Behavior harness (make_pc/spawn_mob/spawn_item/run via interpret + capture).
Regression tests for the reported bugs: unknown->Huh, kill->corpse, slay->corpse
(Hassan), look-in-container. All PASS on a clean boot -> the reported failures
were not logic bugs but the liveness-kill crash-loop dropping responses (fixed
separately by the deploy probe/CPU changes).
…l bug)

CI was red because [tool.ty] used 'python_version', invalid in the current ty
schema, so ty crashed before checking. Fixed to [tool.ty.environment]
python-version. ty then found 468 diagnostics, ~461 pre-existing legacy-untyped
noise (dynamic JSON codec, attribute injection) -> disabled those rules with a
comment (re-enable as the port is typed incrementally). Fixed 2 real findings:
world_classes Exit '.key <= 0' when key can be None (TypeError guard), and
ignored the intentional living.unequip base-signature widening. ty now exits 0.
do_put's 'put all <container>' loop did 'for item in ch.inventory[:]' but
ch.inventory is a list of item IDs (ints), so item.name / WEIGHT_MULT(item) hit
'int has no attribute item_type', game_loop swallowed it, and the player got NO
response (matching the reported 'no response' + 'look in pit shows nothing' —
donations silently failed so the pit stayed empty). Resolve the id like do_drop:
'item = instance.items[item_id]'. Regression test drives 'put all pit' via the
harness (which propagates the exception game_loop hides).
Added a smoke test that runs every command via interpret() and fails on any
uncaught exception (the live game_loop swallows these -> player gets NO response,
exactly the reported symptom). Fixed the real ones it found:
- shop find_keeper: accessed keeper.vnum before is_npc() -> crashed on the PC
  ('Pc' has no vnum) for buy/list/sell/value. Guard is_npc first.
- do_heal: 'mob' referenced before assignment when no healer present -> init None.
- do_wimpy: int(arg) with no validation -> ValueError on 'wimpy all'. Validate.
- do_pose: choice = randint(0, level) could equal the pose-list length ->
  IndexError. Clamp to len-1.
- is_same_group: instance.characters[leader] KeyError on a stale/logged-off
  leader -> crashed gtell/group cmds. Use .get(leader, self).
- do_clone: passed vnums to create_mobile/create_item (want templates), used
  mob.contents (mobs have .inventory), clone_mobile copied PC-only practice/train.
  Still has a deeper put()->add_pc issue; denied in the smoke as a known issue.
Smoke passes for ~230 commands; 8 real crash bugs fixed.
Bumps image to 0.0.1-alpha3 with the do_put/heal/wimpy/pose/gtell/shop/clone
fixes + the gameplay smoke net. Overlays + base image pinned to the new tag.
'look pit' / 'look corpse' showed only the item's flavor text (or its
same-named extra_descr), never its contents — so after slaying Hassan a
player who 'look corpse'd saw nothing and assumed no corpse dropped.

Extract _show_container() (contents/fill display) shared by 'look in <x>'
and a new up-front container resolution in the bare-look path. The container
check takes precedence over the extra_descr match so a pit/corpse shows what
is inside.

Adds regression tests: slay leaves a corpse for 40 Midgaard mobs, and both
'look <container>' and 'look corpse' default to look-in.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_018VkUXo86FhkDaKouc9rpV6
Smoke only proves commands dispatch without raising. These assert the core
play loop actually works:

- movement: a valid exit changes rooms; a closed door blocks the move
- inventory: get/drop/give/wield/remove/wear move the specific item between
  room, inventory, mob, and equip slots (guards the item-id-vs-object bug class)
- combat: a round lowers victim hp; a kill grants xp; flee ends the fight

All green; no defects surfaced.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_018VkUXo86FhkDaKouc9rpV6
Writing shop tests surfaced that selling was completely non-functional —
four stacked bugs in the sell path, each silently swallowed by the game loop:

- shop_utils.get_cost compared an item's string item_type ("light") against a
  shop's numeric buy_type codes ({1,8,13,...}), so every item scored 0 and the
  keeper refused everything. Added merc.item_type_number (num -> string) and
  translate before comparing; also iterate buy_type.values() not its keys.
- do_sell read item.sell_extract (no such attribute) in two places -> AttributeError.
  Corrected to item.flags.sell_extract.
- do_sell called item.get() with no argument -> TypeError. Corrected to ch.get(item).
- shop_utils.obj_to_keeper set .instance_id on a None environment, checked the
  wrong flag, and removed from the wrong list. Rewrote around keeper.put(item).

New tests (all green):
- spells: cast spends mana, no-mana refused, attack spell damages, heal restores hp
- shop: list shows stock, buy adds item/deducts coin, sell pays coin/removes item
- containers: close/open toggles closed flag; put refused while closed, accepted while open

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_018VkUXo86FhkDaKouc9rpV6
- persistence: a deep save/load round-trip asserts coin, xp, permanent stats,
  carried inventory, and an equipped weapon all survive (the prior e2e check
  only covered name + level); PC death respawns at the altar, alive, leaving a
  corpse in the death room.
- area contract: tests/test_area_contract.py enforces that any area shipping
  progs also ships tests under src/areas/<name>/tests/. Midgaard was the only
  area with progs and had none, so this adds test_wizard_progs.py covering the
  wizard's on_speech and on_greet handlers.
- infra: moved the session-scoped booted_world fixture to a repo-root conftest
  so the tests/ suite and per-area tests share one world boot (boot_db is not
  idempotent); added src/areas to pytest testpaths so area tests are collected.

Full suite: 93 passed.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_018VkUXo86FhkDaKouc9rpV6
CI runs `ty check src`. Area tests under src/areas/<name>/tests/ import the
gameplay `helpers` module off sys.path (wired in the repo-root conftest), which
ty cannot resolve statically and reported as an error, failing CI. These are
pytest files, not shipped logic, so exclude src/areas/*/tests/ from ty. pytest
still collects them via testpaths.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_018VkUXo86FhkDaKouc9rpV6
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant