You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This issue is implemented in the fresh MIT-licensed Go server under the replacement program. Its gameplay and content-design decisions remain authoritative. C, CPython, classic packet, file-path, and enum details in the preserved specification are historical evidence only; do not copy, translate, or structurally port GPL implementation code.
Replacement implementation contract
Implement a Go training-target component and bounded per-player deterministic measurement sessions. Preserve progression exclusion, post-mitigation accounting, reset, concurrency, and result rules.
The server remains authoritative, consumes versioned compiled content, and exposes bounded generated Game Protocol 1 messages. Pure rules may use a specifically approved typed CEL environment. Starlark is not part of this issue unless the separate residual-scripting decision explicitly approves it.
Required verification
Preserve every observable rule, balance decision, disclosure boundary, and anti-exploit invariant from the specification below.
Add deterministic Go unit/property tests and wrapper-managed scenario coverage at the appropriate integration boundary.
Add bounded malformed-input and persistence-failure cases where this feature accepts content, network, or stored data.
Add Go/Rust protocol conformance fixtures for every new cross-process field; the client must not reconstruct authoritative rules from prose.
Demonstrate that implementation and tests contain no copied GPL source/test material and execute no runtime Python.
Preserved product/design specification and historical implementation notes
Summary
Turn Atrinik's existing Incuna target dummies into an authoritative, repeatable DPS test. A player's first damaging hit should begin a fixed-duration test window; the server should total that player's final post-mitigation damage from melee, archery, spells, damage-over-time effects, and other player-owned attack objects, then privately report total damage, elapsed time, and DPS.
The first version can use ordinary player messages and the existing floating damage numbers, so it does not require a new client command or wire-format change.
What already exists
There is already a useful content starting point in maps/shattered_islands/world_3_83 (Incuna):
two spawn points at (9, 7) and (9, 8) create rat_a monsters renamed to target dummy;
they use the existing target.131 artwork;
no_attack 1, exp 0, maxhp 1000000, and con 100 make them passive, unrewarding, durable, and regenerative;
arch/outdoor/target/target.arc and its animation/images already provide the target artwork, although that archetype itself is currently a non-living type-79 prop.
The authoritative damage point also already exists. In server/src/server/attack.c, attack_hit():
resolves object_owner(hitter) so arrows, spell effects, and other owned attack objects are credited to their player;
applies slaying, blocking/absorption, per-attack-type proportions, and target protections;
sums the final result in maxdam;
updates CONTR(hitter_owner)->stat_damage_dealt and op->last_damage before subtracting HP.
op->last_damage is serialized by server/src/socket/request.c; the client already renders it as an orange floating number through ANIM_DAMAGE in client/src/gui/widgets/map.c.
The existing dummies do not calculate DPS, can eventually die, and inflate the persistent stat_damage_dealt counter. Spell testing is also awkward: Incuna has map-wide no_harm 1, and server/src/server/spell_util.c cancels harmful spells based on the caster's tile unless a local map_infostand_still area toggles that rule. The tiles immediately around the current dummies are outside the existing override ending at x = 7, so the training lane is not consistently usable for harmful magic.
Proposed design
1. Create a reusable authored training-dummy archetype
Replace the inline rat_a overrides with a dedicated living archetype, for example training_dummy, using the existing target artwork.
Give it an explicit source-data marker such as dps_target 1 rather than detecting its display name, face, monster race, or map path. Keep the marker data-driven so another map can place the same dummy later.
The baseline dummy should have a documented reference defense profile:
passive and stationary;
targetable by melee, arrows, offensive spells, and player-owned effects;
zero XP, no drops/corpse, no faction/reputation effect, and no kill credit;
no blocking/absorption and known AC/protections so results are reproducible;
effectively immortal without using FLAG_INVULNERABLE, because that flag makes attack_hit() return zero before damage is calculated.
A DPS-target hit should still calculate and display its real final damage, but skip HP loss/death after calculation. This is safer than relying on a very large HP pool and regeneration.
Convert both Incuna placements to the shared archetype and author a clearly bounded training area in which harmful spells are permitted. Keep PvP disabled so opening offensive spellcasting for the lane cannot let players damage each other.
2. Record one private session per player
Add transient DPS-session state to player, keyed to the target pointer plus object count so a reused object slot cannot continue an old session. Suggested state:
target and target count;
monotonic start/deadline timestamps;
accumulated final damage as double;
optional sample/hit count if useful for diagnostics.
Use datetime_monotonic_ms() rather than pticks / MAX_TICKS. DPS is a real-time measurement and should remain correct if /speed changes or a game-loop tick is delayed.
Record at the point where maxdam is final, before the normal HP cap/subtraction. Do not derive a session from the lifetime stat_damage_dealt counter: it is not target-scoped, has no timestamps, and is sent to the external statistics service only at logout.
Keep sessions player-local. Two players attacking the same dummy must receive independent totals, and a player switching to another marked dummy should end/reset the old session rather than merge targets.
3. Use a fixed test window and existing messages
Recommended initial UX:
the first non-zero final damage starts a 10-second test and privately prints DPS test started: 10 seconds;
all damage credited to that player against that same dummy during the window is included;
at 5 seconds, optionally show a private progress line;
at 10 seconds, report DPS test complete: 12,345 damage in 10.0s — 1,234.5 DPS;
the next damaging hit after completion starts a fresh test;
changing dummy, leaving the map, or logging out clears/finalizes transient state safely;
zero-damage hits do not start a test.
A fixed window makes different weapon/skill/spell loadouts directly comparable and avoids ambiguous “time from first hit to last hit” results for single-hit attacks. The duration could later become configurable (for example 10/30/60 seconds) or receive a dedicated client widget, but neither is necessary for the first useful implementation.
Do not broadcast results from the dummy: shared training areas should not leak or mix player results. Ordinary chat/info output is sufficient for the MVP and avoids a protocol/client dependency.
4. Keep test damage out of persistent gameplay statistics
Damage against an object marked dps_target should not increment stat_damage_dealt, kill statistics, skill/character XP, faction reputation, loot, quests, achievements, or similar progression. Floating damage numbers and the transient DPS total should still use the calculated value.
This prevents repeated tests from polluting the logout statistics datagrams emitted by server/src/server/statistics.c.
Why not implement this only as a Python object event?
The current EVENT_ATTACK hook in attack_object() is triggered on the attacker before the attack roll and before protections, blocking, attack-type splitting, or damage application. It receives the attacker's base stats.dam/WC, not the final target damage. A target-local script therefore cannot accurately measure the value the player sees, and it does not naturally cover all paths that call attack_hit() directly.
The smallest accurate implementation is an isolated C helper called after maxdam is known. If a general post-damage plugin event is desired for other gameplay systems, that can be designed separately, but this feature should not report pre-mitigation estimates as DPS.
Acceptance criteria
Incuna has at least one reusable authored training dummy using the existing target artwork rather than an inline renamed rat.
Players can damage it with melee, archery, harmful spells, and player-owned delayed/projectile effects from an obvious training area.
The dummy never attacks, moves, dies, drops items, grants XP/kill/quest/faction credit, or permanently loses HP.
The server starts a fixed-duration private DPS session on the first non-zero final damage.
Total damage is the sum of final post-mitigation maxdam credited to that player for that dummy, including mixed attack types.
Concurrent players on one dummy receive independent results.
Target switching and object destruction/reuse cannot merge or corrupt sessions.
Timing uses a monotonic clock and is not affected by server tick-rate changes.
The final private message includes total damage, measured duration, and DPS with sensible rounding.
Dummy damage remains visible through the existing floating damage animation but does not increment persistent player damage statistics.
No legacy protocol or client change is required for the initial implementation.
Validation
Extend server/src/tests/unit/server/attack.c with focused tests for direct player damage, player-owned projectile/effect damage, post-protection totals, zero damage, concurrent players, target switching, expiry, and exclusion from stat_damage_dealt.
Test dummy immortality and the absence of kill/XP/faction side effects.
Run the focused map checker on maps/shattered_islands/world_3_83, collect the affected archetype/resources, and build/run the server tests.
In a runtime smoke test, perform separate 10-second melee, bow, direct-spell, and damage-over-time tests and verify private totals against the displayed final damage numbers.
Verify that a second player can test simultaneously and that offensive effects cannot damage players in the training area.
Relevant source areas
maps/shattered_islands/world_3_83: current inline target dummies and training-area map flags
arch/outdoor/target/: existing target archetype, animation, and artwork
server/src/server/attack.c: final damage, ownership credit, persistent damage statistics, HP loss, and death
server/src/include/player.h: transient per-player session state
Important
This issue is implemented in the fresh MIT-licensed Go server under the replacement program. Its gameplay and content-design decisions remain authoritative. C, CPython, classic packet, file-path, and enum details in the preserved specification are historical evidence only; do not copy, translate, or structurally port GPL implementation code.
Replacement implementation contract
Implement a Go training-target component and bounded per-player deterministic measurement sessions. Preserve progression exclusion, post-mitigation accounting, reset, concurrency, and result rules.
The server remains authoritative, consumes versioned compiled content, and exposes bounded generated Game Protocol 1 messages. Pure rules may use a specifically approved typed CEL environment. Starlark is not part of this issue unless the separate residual-scripting decision explicitly approves it.
Required verification
Preserved product/design specification and historical implementation notes
Summary
Turn Atrinik's existing Incuna target dummies into an authoritative, repeatable DPS test. A player's first damaging hit should begin a fixed-duration test window; the server should total that player's final post-mitigation damage from melee, archery, spells, damage-over-time effects, and other player-owned attack objects, then privately report total damage, elapsed time, and DPS.
The first version can use ordinary player messages and the existing floating damage numbers, so it does not require a new client command or wire-format change.
What already exists
There is already a useful content starting point in
maps/shattered_islands/world_3_83(Incuna):(9, 7)and(9, 8)createrat_amonsters renamed totarget dummy;target.131artwork;no_attack 1,exp 0,maxhp 1000000, andcon 100make them passive, unrewarding, durable, and regenerative;arch/outdoor/target/target.arcand its animation/images already provide the target artwork, although that archetype itself is currently a non-living type-79 prop.The authoritative damage point also already exists. In
server/src/server/attack.c,attack_hit():object_owner(hitter)so arrows, spell effects, and other owned attack objects are credited to their player;maxdam;CONTR(hitter_owner)->stat_damage_dealtandop->last_damagebefore subtracting HP.op->last_damageis serialized byserver/src/socket/request.c; the client already renders it as an orange floating number throughANIM_DAMAGEinclient/src/gui/widgets/map.c.The existing dummies do not calculate DPS, can eventually die, and inflate the persistent
stat_damage_dealtcounter. Spell testing is also awkward: Incuna has map-wideno_harm 1, andserver/src/server/spell_util.ccancels harmful spells based on the caster's tile unless a localmap_infostand_stillarea toggles that rule. The tiles immediately around the current dummies are outside the existing override ending atx = 7, so the training lane is not consistently usable for harmful magic.Proposed design
1. Create a reusable authored training-dummy archetype
Replace the inline
rat_aoverrides with a dedicated living archetype, for exampletraining_dummy, using the existing target artwork.Give it an explicit source-data marker such as
dps_target 1rather than detecting its display name, face, monster race, or map path. Keep the marker data-driven so another map can place the same dummy later.The baseline dummy should have a documented reference defense profile:
FLAG_INVULNERABLE, because that flag makesattack_hit()return zero before damage is calculated.A DPS-target hit should still calculate and display its real final damage, but skip HP loss/death after calculation. This is safer than relying on a very large HP pool and regeneration.
Convert both Incuna placements to the shared archetype and author a clearly bounded training area in which harmful spells are permitted. Keep PvP disabled so opening offensive spellcasting for the lane cannot let players damage each other.
2. Record one private session per player
Add transient DPS-session state to
player, keyed to the target pointer plus object count so a reused object slot cannot continue an old session. Suggested state:double;Use
datetime_monotonic_ms()rather thanpticks / MAX_TICKS. DPS is a real-time measurement and should remain correct if/speedchanges or a game-loop tick is delayed.Record at the point where
maxdamis final, before the normal HP cap/subtraction. Do not derive a session from the lifetimestat_damage_dealtcounter: it is not target-scoped, has no timestamps, and is sent to the external statistics service only at logout.Keep sessions player-local. Two players attacking the same dummy must receive independent totals, and a player switching to another marked dummy should end/reset the old session rather than merge targets.
3. Use a fixed test window and existing messages
Recommended initial UX:
DPS test started: 10 seconds;DPS test complete: 12,345 damage in 10.0s — 1,234.5 DPS;A fixed window makes different weapon/skill/spell loadouts directly comparable and avoids ambiguous “time from first hit to last hit” results for single-hit attacks. The duration could later become configurable (for example 10/30/60 seconds) or receive a dedicated client widget, but neither is necessary for the first useful implementation.
Do not broadcast results from the dummy: shared training areas should not leak or mix player results. Ordinary chat/info output is sufficient for the MVP and avoids a protocol/client dependency.
4. Keep test damage out of persistent gameplay statistics
Damage against an object marked
dps_targetshould not incrementstat_damage_dealt, kill statistics, skill/character XP, faction reputation, loot, quests, achievements, or similar progression. Floating damage numbers and the transient DPS total should still use the calculated value.This prevents repeated tests from polluting the logout statistics datagrams emitted by
server/src/server/statistics.c.Why not implement this only as a Python object event?
The current
EVENT_ATTACKhook inattack_object()is triggered on the attacker before the attack roll and before protections, blocking, attack-type splitting, or damage application. It receives the attacker's basestats.dam/WC, not the final target damage. A target-local script therefore cannot accurately measure the value the player sees, and it does not naturally cover all paths that callattack_hit()directly.The smallest accurate implementation is an isolated C helper called after
maxdamis known. If a general post-damage plugin event is desired for other gameplay systems, that can be designed separately, but this feature should not report pre-mitigation estimates as DPS.Acceptance criteria
maxdamcredited to that player for that dummy, including mixed attack types.Validation
server/src/tests/unit/server/attack.cwith focused tests for direct player damage, player-owned projectile/effect damage, post-protection totals, zero damage, concurrent players, target switching, expiry, and exclusion fromstat_damage_dealt.maps/shattered_islands/world_3_83, collect the affected archetype/resources, and build/run the server tests.Relevant source areas
maps/shattered_islands/world_3_83: current inline target dummies and training-area map flagsarch/outdoor/target/: existing target archetype, animation, and artworkserver/src/server/attack.c: final damage, ownership credit, persistent damage statistics, HP loss, and deathserver/src/include/player.h: transient per-player session stateserver/src/types/player.c: session deadline/finalization lifecycleserver/src/server/spell_util.c: Incuna harmful-spell gatingserver/src/server/statistics.c: logout statistics that training damage must not inflateserver/src/socket/request.candclient/src/gui/widgets/map.c: existing floating damage displayserver/src/tests/unit/server/attack.c: focused combat unit tests