feat: ankh resurrection replacing the timed auto-respawn (#112) #129

Merged
marco merged 1 commit from feat/ankh-resurrection into main 2026-07-22 09:21:25 +02:00
Owner

Summary

Closes the death loop for the arena (#112). I didn't want a magic timer respawning you at a fixed point — I want the UO feel: you die, you're a ghost, and you walk back to an ankh and use it to return to life. So this replaces the timed auto-respawn entirely with a diegetic, player-driven, fully server-validated resurrection.

  • Ankh = an item with behavior. New IUsable capability on the auto-discovered BaseItem layer (mirrors IEquippable), Ankh : BaseItem, IUsable. Double-clicking a world item sends a new Use(itemId) intent; the server routes it to the item's OnUse in one Simulation.InvokeAsync and validates everything (item exists, is a placed ankh, you're a ghost, you're in range). A modified client can't resurrect a live player, from range, someone else, or via a non-ankh item.
  • Death/resurrection are intrinsic on the entity (PlayerMobile.Die() / Resurrect()), acting on the instance — no more _resurrections scheduling dict, ProcessResurrections, or ResurrectDelayTicks. GM /resurrect stays as the manual override; new GM /place <kind> places the ankh.
  • Ghost is delivered as reconciled appearance state. PlayerState now carries PlayerAppearance (ghost), PlayerJoined carries it (this fixes a latent bug: entering AoI of an existing ghost used to render them alive), and a new PlayerAppearanceChanged is reconciled to observers — PlayerDied/PlayerResurrected and their per-tick buffers are gone. This is deliberately the shared foundation for worn gear on other players (#18, next).
  • Ankh is a placed, persistent, unowned item (new ItemPlace.Placed + a world-save section mirroring creatures) and ghost state now persists across relog (no free resurrection by logging out).
  • Protocol bumped to 6. Design + Invariants Check recorded on #112 (reviewed via the critical-design-review skill).

Screenshots / recording

Captured by piloting the client via the debug harness (docs/debug-harness.md), driven as admin debug:

login debug
/place ankh  → target the tile (places a persistent ankh)
goto 26 18                    # step aside so the ankh is visible
/kill  → target self          # become a ghost
goto 24 18                    # walk the ghost onto the ankh
double-click the ankh tile    # Use → resurrect
Placed (alive) Ghost (death screen) Resurrected
placed ghost resurrected

The ankh renders as a placeholder diamond (its tint is derived from the kind name) and the resurrect sparkle FX is silent for now — both need the UO static/effect art extracted into the pack, a follow-up (the art id item_ankh = 0x1D98 is already registered in the extractor manifest). The gameplay is complete and server-authoritative regardless.

How it was tested

  • Unit tests against World (new): a creature-killed ghost resurrects in place by using an ankh (not at the map spawn); using the ankh while alive → NotDead and no state change; out of range → OutOfRange; a placed ankh survives a serialize/restore round-trip. Updated the interest-diff, protocol round-trip, persistence and GM-command tests for the new appearance model and the placed-items save section.
  • just test — all suites green (189 GameServer, 72 Client.Core, 54 Shared, 12 Auth, 14 AssetExtractor, 4 Assets).
  • just lint — build succeeds, 0 warnings.
  • Manually piloted end-to-end via the debug harness (the screenshots above).

Checklist

  • just lint passes (CSharpier + analyzers, zero warnings)
  • just test is green
  • The whole solution builds (client and tools included)
  • Multi-platform preserved (server on Win/macOS/Linux, client on Win/macOS) — pure .NET; no OS-specific APIs
  • Tests added/updated for this change
  • Linked the related issue (#112) and its Definition of Done is met (the timer path is gone; ankh resurrects in place; relog stays a ghost; /place persists across restart; server-validated)
## Summary Closes the death loop for the arena (#112). I didn't want a magic timer respawning you at a fixed point — I want the UO feel: you die, you're a ghost, and you walk back to an **ankh** and use it to return to life. So this replaces the timed auto-respawn entirely with a diegetic, player-driven, fully server-validated resurrection. - **Ankh = an item with behavior.** New `IUsable` capability on the auto-discovered `BaseItem` layer (mirrors `IEquippable`), `Ankh : BaseItem, IUsable`. Double-clicking a world item sends a new `Use(itemId)` intent; the server routes it to the item's `OnUse` in one `Simulation.InvokeAsync` and validates everything (item exists, is a placed ankh, you're a ghost, you're in range). A modified client can't resurrect a live player, from range, someone else, or via a non-ankh item. - **Death/resurrection are intrinsic on the entity** (`PlayerMobile.Die()` / `Resurrect()`), acting on the instance — no more `_resurrections` scheduling dict, `ProcessResurrections`, or `ResurrectDelayTicks`. GM `/resurrect` stays as the manual override; new GM `/place <kind>` places the ankh. - **Ghost is delivered as reconciled appearance state.** `PlayerState` now carries `PlayerAppearance` (ghost), `PlayerJoined` carries it (this fixes a latent bug: entering AoI of an existing ghost used to render them alive), and a new `PlayerAppearanceChanged` is reconciled to observers — `PlayerDied`/`PlayerResurrected` and their per-tick buffers are gone. This is deliberately the shared foundation for worn gear on other players (#18, next). - **Ankh is a placed, persistent, unowned item** (new `ItemPlace.Placed` + a world-save section mirroring creatures) and **ghost state now persists across relog** (no free resurrection by logging out). - Protocol bumped to **6**. Design + Invariants Check recorded on #112 (reviewed via the `critical-design-review` skill). ## Screenshots / recording Captured by piloting the client via the debug harness (`docs/debug-harness.md`), driven as admin `debug`: ``` login debug /place ankh → target the tile (places a persistent ankh) goto 26 18 # step aside so the ankh is visible /kill → target self # become a ghost goto 24 18 # walk the ghost onto the ankh double-click the ankh tile # Use → resurrect ``` | Placed (alive) | Ghost (death screen) | Resurrected | | --- | --- | --- | | ![placed](https://git.homelab.devncode.it/attachments/79f6718d-c046-4d46-9d85-2913b4dbc51b) | ![ghost](https://git.homelab.devncode.it/attachments/ecbaf41d-4a6d-4f03-9db2-ada05da12240) | ![resurrected](https://git.homelab.devncode.it/attachments/72440805-b49b-49dc-8705-6892f2f54fd8) | The ankh renders as a placeholder diamond (its tint is derived from the kind name) and the resurrect sparkle FX is silent for now — both need the UO static/effect art extracted into the pack, a follow-up (the art id `item_ankh = 0x1D98` is already registered in the extractor manifest). The gameplay is complete and server-authoritative regardless. ## How it was tested - **Unit tests** against `World` (new): a creature-killed ghost resurrects **in place** by using an ankh (not at the map spawn); using the ankh while alive → `NotDead` and no state change; out of range → `OutOfRange`; a placed ankh survives a serialize/restore round-trip. Updated the interest-diff, protocol round-trip, persistence and GM-command tests for the new appearance model and the placed-items save section. - `just test` — all suites green (189 GameServer, 72 Client.Core, 54 Shared, 12 Auth, 14 AssetExtractor, 4 Assets). - `just lint` — build succeeds, **0 warnings**. - Manually piloted end-to-end via the debug harness (the screenshots above). ## Checklist - [x] `just lint` passes (CSharpier + analyzers, zero warnings) - [x] `just test` is green - [x] The whole solution builds (client and tools included) - [x] Multi-platform preserved (server on Win/macOS/Linux, client on Win/macOS) — pure .NET; no OS-specific APIs - [x] Tests added/updated for this change - [x] Linked the related issue (#112) and its Definition of Done is met (the timer path is gone; ankh resurrects in place; relog stays a ghost; `/place` persists across restart; server-validated)
feat: ankh resurrection replacing the timed auto-respawn (#112)
All checks were successful
ci / Lint & Test (pull_request) Successful in 2m37s
531c1799ad
Death and resurrection become intrinsic entity transitions (PlayerMobile.Die/
Resurrect); the timed auto-respawn (_resurrections/ProcessResurrections/
ResurrectDelayTicks) is removed. A ghost returns by double-clicking an Ankh — a
placed, persistent Item with an IUsable OnUse hook, used via a new Use intent
validated server-side (ghost + range). GM /place places the ankh; GM /resurrect
stays as the manual override. Ghost state now persists across relog.

Ghost is delivered as reconciled appearance state: PlayerState carries
PlayerAppearance, PlayerJoined carries it (fixing the enter-AoI-sees-ghost-as-alive
bug), and PlayerAppearanceChanged is reconciled to known observers — PlayerDied/
PlayerResurrected and their tick buffers are gone. This is the shared foundation
for worn-gear appearance (#18).

Protocol bumped to 6.
marco merged commit cb5094a024 into main 2026-07-22 09:21:25 +02:00
marco deleted branch feat/ankh-resurrection 2026-07-22 09:21:25 +02:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
marco/IsoMmo!129
No description provided.