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
Preserve exclusive classification, per-viewer eligibility, and privacy rules. Go projects only a typed semantic treasure class; the renderer maps it to accessible shader/highlight presentation.
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
Why
Add a treasure-sense equipment ability that lets a player notice which visible monsters are carrying worthwhile loot before killing them. The sense should have three cumulative tiers:
Sense tier
Detects
1 — magical
Ordinary magical drops
2 — artifact
Artifacts, plus tier 1
3 — special
Eligible special/one-drop items, plus tiers 1–2
The classes must be exclusive per item. An artifact that is also marked magical requires tier 2, and a special drop that is also an artifact or magical requires tier 3. A monster with several different items may still produce the highest class the player can actually detect.
The effect should communicate only that a visible monster has qualifying loot. It must not identify the item, reveal its name or value, disclose inventory contents, bypass line of sight/invisibility, or promise a special drop for which that player is not eligible.
Existing code to build on
server/src/types/spawn_point.c rolls randomitems and resolves authored RANDOM_DROP chances into the monster's actual inventory before inserting the monster into the map. Map-loaded monsters similarly generate treasure while loading. Treasure sense can therefore inspect rolled loot rather than predict a treasure table.
server/src/server/treasure.c marks ordinary generated magic with FLAG_IS_MAGICAL; server/src/server/artifact.c records a generated artifact in object::artifact.
server/src/server/quest.c owns item-drop eligibility. Both repeatable “special drop” items and FLAG_ONE_DROP rewards are nested in QUEST_CONTAINER objects, and eligibility depends on the viewing player's quest container/inventory state.
server/src/server/object.c:object_drop_inventory() is the authority for what survives a monster death. It already excludes system, start-equipment, and no-drop objects and diverts quest containers through quest_handle().
server/src/server/living.c:living_update_player() already aggregates applied equipment properties into the player. A categorical sense level can use the same lifecycle and combine by maximum rather than addition.
CLIENT_CMD_MAP already carries per-object extended metadata. common/toolkit/socket.h defines MAP2_FLAG2_*; server/src/socket/request.c emits it per viewer; client/src/client/commands.c stores it in the depth-specific map cache.
The current SDL client can already render cached software sprite glow and outline effects in client/src/client/sprite.c. Door reveal demonstrates an independent outline layered into the map painter. The first version does not need to wait for SDL3.
The future GPU renderer and material work is tracked by atrinik/atrinik#129 and atrinik/client#21. Those can later replace the software highlight with bounded sparkles, particles, or a shader without changing the gameplay classification.
Proposed player experience
Introduce three increasingly rare equipment affixes/artifacts, with names and balance values finalized during content design. A possible progression is of treasure sensing, of treasure seeking, and of treasure mastery.
Put the first implementation on amulets so the ability has one clear equipment tradeoff and cannot be multiplied through two ring slots. Keep the engine property generic so future unique items, innate abilities, buffs, or other equipment types can grant it.
While an item is applied, visible loot-bearing monsters receive a client-side highlight based on the highest loot class that item lets this player detect:
magical: blue/cyan;
artifact: violet;
special: gold.
Use a steady outline or restrained glow in the SDL software renderer. Do not rely on color alone: add a small packaged sparkle/marker or another shape distinction if playtesting shows the three colors are not sufficiently accessible.
Render the cue only in the main world view, on the monster sprite, and only while the server is already sending that monster as visible. Do not add it to the region map or use it as an off-screen/through-wall radar.
If a monster has several qualifying drops, show one cue: the highest class the viewer's equipped tier permits. Do not expose item count.
Add a client option to reduce or disable treasure highlight animation while retaining a clear static cue.
Proposed implementation
1. Add a categorical equipment property
Add a bounded treasure_sense value to the server object model:
0: none
1: magical
2: artifact
3: special
Teach the authored-object loader/save-diff path in server/src/loaders/object.l about the field and regenerate object.c; expose it through the Python object binding and editor/checker metadata where object fields are enumerated.
In living_update_player(), reset the player's effective value from the player archetype and take the maximum value from applied items that actually contribute equipment stats. Do not sum tiers. Equipping, unequipping, or replacing the item must update the effective value immediately and make visible map layers eligible for a delta on the next map update.
Create three authored artifact definitions with increasing difficulty, rarity, item power, and value. The exact balance is content work, but all three should use the same engine field rather than three unrelated flags.
2. Centralize drop classification and player eligibility
Add a server helper that returns a loot-class bitmask for a carrier and viewer. It should inspect actual generated inventory and mirror death/drop behavior rather than use raw object value or treasure-list probability.
Classify each candidate in this order:
Special: an item owned by the current item-drop quest/one-drop path, or a future explicitly authored special-drop marker. Then apply the viewer-specific award eligibility check.
Magical: FLAG_IS_MAGICAL, unless already classified special or artifact.
Extract a pure, non-mutating item-drop eligibility function from quest.c and reuse it from both quest_handle() and treasure sensing. It must cover an unstarted quest, an already-carried repeatable special item, and a completed one-drop. An ineligible special item is ignored, never reclassified as artifact or magical even if it carries those markers. This keeps each player's cue truthful and prevents quest state from being inferred incorrectly. Party members evaluate the special class independently; the cue means “eligible if this viewer receives the applicable kill/loot credit,” not that party ownership is guaranteed.
Mirror or extract the droppable-item predicate from object_drop_inventory() so system objects, monster start equipment, FLAG_NO_DROP, spent runes, and other destroyed inventory do not create false highlights. Recurse only into containers whose contents would survive or are intentionally handled as item-drop quest contents.
Do not perform an unbounded recursive inventory walk for every visible monster, player, and tick. Cache the viewer-independent ordinary magical/artifact class after spawn-time treasure and RANDOM_DROP generation, excluding all special-path items from those lower classes, and invalidate/recompute it when a carrier's drop-bearing inventory changes. Keep the small player-dependent special eligibility check separate. Bound recursion and handle malformed/cyclic content defensively.
3. Send one viewer-specific class in the map layer
Add MAP2_FLAG2_TREASURE_SENSE using the next extended map-object bit. When serializing a visible, non-player MONSTER living layer, the server should:
calculate the viewer's effective sense tier;
find the highest eligible loot class at or below that tier;
set the flag only when the result is nonzero;
append one uint8 class value (1..3) in a documented position after the existing extended fields.
No item identity, count, value, artifact name, or quest name crosses the wire. Normal LOS, darkness, invisibility, OBJECT_IS_HIDDEN, stacked-level visibility, and map bounds remain authoritative.
Add a per-socket-layer cached class in the server MapCell, not only an extended bit. The existing layer cache stores the base flag byte and separately tracks some extended semantics; a face whose treasure class changes must produce a delta even when MAP2_FLAG_MORE was already set for target, glow, roof, or another feature. Clear the cached class when the layer disappears.
On the client, validate that the payload is exactly one known class, store it per real map layer/depth, clear it with the layer, and reject truncated or invalid map records without letting them desynchronize a later layer or physical-level block. Bump the shared socket version and document the field order and malformed-input behavior in doc/ADS/ADS-2.
4. Keep rendering independent from gameplay classification
Add a dedicated treasure-highlight field to the client MapCell; do not overwrite the monster's authored glow string. Map drawing should translate the stable class enum into client-packaged colors/styles and compose it with authored glow, lighting, animation, multi-part sprites, targeting/probe overlays, status icons, and door/building effects.
For the current software renderer, prefer the existing outline path or a bounded packaged marker. Avoid a new continuously changing per-frame effect state that multiplies sprite-cache entries across many monsters. Limit redraws and expanded damage bounds to highlighted sprites.
After atrinik/atrinik#129 and atrinik/client#21, retain the same map semantic and replace or augment the presentation with a packaged material such as sparse orbiting sparkles, an edge shimmer, or tier-specific particles. The server must send only the class enum; it must never select a shader, material path, or arbitrary effect parameters.
Tests and validation
Add server unit tests for exclusive classification: plain magical, artifact-plus-magical, special-plus-artifact-plus-magical, and a carrier with separate items from several classes.
Verify tier 1 cannot detect an artifact merely because it has FLAG_IS_MAGICAL, and tiers 1–2 cannot detect a special item merely because it is also magical/artifact.
Cover system, start-equipment, no-drop, rune, nested-container, empty, and inventory-mutation cases against the real drop predicate.
Cover repeatable special drops and one-drops for eligible, already-carried, not-started, completed, and independently evaluated party viewers.
Test applied versus unapplied sensor items, maximum-not-sum aggregation, all three tiers, equipment replacement, and immediate removal of the cue on unequip.
Add packet tests for class appearance, change, and removal with an unchanged face; coexistence with target/probe and authored glow flags; truncated payloads; invalid class values; exact level-block consumption; and stacked physical levels.
Manually inspect animated and multi-part monsters in light/dark scenes, fog and invisibility transitions, overlapping sprites, smooth lighting, target/status overlays, the minimap, and a stress scene with many highlighted monsters.
Measure CPU frame time, sprite-cache growth, map packet bytes/deltas, and server classification work with no sensor, each tier, and many visible monsters.
Build both legacy binaries, run the prepared server checks, regenerate only the affected authored resource aggregates, and run the relevant content/checker validation.
Acceptance criteria
Applied equipment grants exactly one effective treasure-sense tier, combined by maximum.
Tier 1 detects only ordinary magical drops; tier 2 also detects artifacts; tier 3 also detects eligible special/one-drops.
Special and artifact items cannot leak through a lower classification merely because they also carry lower-tier markers.
Highlights are based on already-rolled, actually droppable inventory and do not appear for destroyed/no-drop/start-equipment objects.
Special-drop highlights use the same player eligibility rules as the eventual award and update when that eligibility changes.
Only a bounded class enum is disclosed; item identity, count, value, quest name, and hidden monsters remain server-private.
Equipping/unequipping the ability or changing monster loot/eligibility sends a correct map-layer delta even when the face and other extended flags are unchanged.
The SDL software client provides a readable, accessible, composable cue now; GPU sparkle/shader polish can land later without changing gameplay or protocol semantics.
Map parsing is bounded and fail-closed, map/renderer caches clear correctly, and both client and server build and pass focused tests.
Non-goals
Showing exact loot, value estimates, item counts, or unidentified artifact names.
Predicting unrolled treasure-table outcomes or rerolling loot when sensed.
Revealing monsters outside ordinary visibility or on region maps.
Making GPU/SDL3 migration a prerequisite for the gameplay feature.
Protocol-epoch coordination
Coordinate this wire change through atrinik/atrinik#168's next classic protocol epoch with #26, atrinik/atrinik#141, atrinik/atrinik#156, atrinik/client#13, atrinik/client#9, atrinik/client#7, and #6 where practical. Do not reserve an isolated numeric version in advance. Land atrinik/atrinik#190's bounded packet primitives first where this payload uses them, then update all current producers, consumers, bots, fixtures, tests, and ADS-2 together.
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
Preserve exclusive classification, per-viewer eligibility, and privacy rules. Go projects only a typed semantic treasure class; the renderer maps it to accessible shader/highlight presentation.
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
Why
Add a treasure-sense equipment ability that lets a player notice which visible monsters are carrying worthwhile loot before killing them. The sense should have three cumulative tiers:
The classes must be exclusive per item. An artifact that is also marked magical requires tier 2, and a special drop that is also an artifact or magical requires tier 3. A monster with several different items may still produce the highest class the player can actually detect.
The effect should communicate only that a visible monster has qualifying loot. It must not identify the item, reveal its name or value, disclose inventory contents, bypass line of sight/invisibility, or promise a special drop for which that player is not eligible.
Existing code to build on
server/src/types/spawn_point.crollsrandomitemsand resolves authoredRANDOM_DROPchances into the monster's actual inventory before inserting the monster into the map. Map-loaded monsters similarly generate treasure while loading. Treasure sense can therefore inspect rolled loot rather than predict a treasure table.server/src/server/treasure.cmarks ordinary generated magic withFLAG_IS_MAGICAL;server/src/server/artifact.crecords a generated artifact inobject::artifact.server/src/server/quest.cowns item-drop eligibility. Both repeatable “special drop” items andFLAG_ONE_DROPrewards are nested inQUEST_CONTAINERobjects, and eligibility depends on the viewing player's quest container/inventory state.server/src/server/object.c:object_drop_inventory()is the authority for what survives a monster death. It already excludes system, start-equipment, and no-drop objects and diverts quest containers throughquest_handle().server/src/server/living.c:living_update_player()already aggregates applied equipment properties into the player. A categorical sense level can use the same lifecycle and combine by maximum rather than addition.CLIENT_CMD_MAPalready carries per-object extended metadata.common/toolkit/socket.hdefinesMAP2_FLAG2_*;server/src/socket/request.cemits it per viewer;client/src/client/commands.cstores it in the depth-specific map cache.client/src/client/sprite.c. Door reveal demonstrates an independent outline layered into the map painter. The first version does not need to wait for SDL3.Proposed player experience
Proposed implementation
1. Add a categorical equipment property
Add a bounded
treasure_sensevalue to the server object model:Teach the authored-object loader/save-diff path in
server/src/loaders/object.labout the field and regenerateobject.c; expose it through the Python object binding and editor/checker metadata where object fields are enumerated.In
living_update_player(), reset the player's effective value from the player archetype and take the maximum value from applied items that actually contribute equipment stats. Do not sum tiers. Equipping, unequipping, or replacing the item must update the effective value immediately and make visible map layers eligible for a delta on the next map update.Create three authored artifact definitions with increasing difficulty, rarity, item power, and value. The exact balance is content work, but all three should use the same engine field rather than three unrelated flags.
2. Centralize drop classification and player eligibility
Add a server helper that returns a loot-class bitmask for a carrier and viewer. It should inspect actual generated inventory and mirror death/drop behavior rather than use raw object value or treasure-list probability.
Classify each candidate in this order:
object::artifact != NULL, unless already classified special.FLAG_IS_MAGICAL, unless already classified special or artifact.Extract a pure, non-mutating item-drop eligibility function from
quest.cand reuse it from bothquest_handle()and treasure sensing. It must cover an unstarted quest, an already-carried repeatable special item, and a completed one-drop. An ineligible special item is ignored, never reclassified as artifact or magical even if it carries those markers. This keeps each player's cue truthful and prevents quest state from being inferred incorrectly. Party members evaluate the special class independently; the cue means “eligible if this viewer receives the applicable kill/loot credit,” not that party ownership is guaranteed.Mirror or extract the droppable-item predicate from
object_drop_inventory()so system objects, monster start equipment,FLAG_NO_DROP, spent runes, and other destroyed inventory do not create false highlights. Recurse only into containers whose contents would survive or are intentionally handled as item-drop quest contents.Do not perform an unbounded recursive inventory walk for every visible monster, player, and tick. Cache the viewer-independent ordinary magical/artifact class after spawn-time treasure and
RANDOM_DROPgeneration, excluding all special-path items from those lower classes, and invalidate/recompute it when a carrier's drop-bearing inventory changes. Keep the small player-dependent special eligibility check separate. Bound recursion and handle malformed/cyclic content defensively.3. Send one viewer-specific class in the map layer
Add
MAP2_FLAG2_TREASURE_SENSEusing the next extended map-object bit. When serializing a visible, non-playerMONSTERliving layer, the server should:uint8class value (1..3) in a documented position after the existing extended fields.No item identity, count, value, artifact name, or quest name crosses the wire. Normal LOS, darkness, invisibility,
OBJECT_IS_HIDDEN, stacked-level visibility, and map bounds remain authoritative.Add a per-socket-layer cached class in the server
MapCell, not only an extended bit. The existing layer cache stores the base flag byte and separately tracks some extended semantics; a face whose treasure class changes must produce a delta even whenMAP2_FLAG_MOREwas already set for target, glow, roof, or another feature. Clear the cached class when the layer disappears.On the client, validate that the payload is exactly one known class, store it per real map layer/depth, clear it with the layer, and reject truncated or invalid map records without letting them desynchronize a later layer or physical-level block. Bump the shared socket version and document the field order and malformed-input behavior in
doc/ADS/ADS-2.4. Keep rendering independent from gameplay classification
Add a dedicated treasure-highlight field to the client
MapCell; do not overwrite the monster's authoredglowstring. Map drawing should translate the stable class enum into client-packaged colors/styles and compose it with authored glow, lighting, animation, multi-part sprites, targeting/probe overlays, status icons, and door/building effects.For the current software renderer, prefer the existing outline path or a bounded packaged marker. Avoid a new continuously changing per-frame effect state that multiplies sprite-cache entries across many monsters. Limit redraws and expanded damage bounds to highlighted sprites.
After atrinik/atrinik#129 and atrinik/client#21, retain the same map semantic and replace or augment the presentation with a packaged material such as sparse orbiting sparkles, an edge shimmer, or tier-specific particles. The server must send only the class enum; it must never select a shader, material path, or arbitrary effect parameters.
Tests and validation
FLAG_IS_MAGICAL, and tiers 1–2 cannot detect a special item merely because it is also magical/artifact.Acceptance criteria
Non-goals
Protocol-epoch coordination
Coordinate this wire change through atrinik/atrinik#168's next classic protocol epoch with #26, atrinik/atrinik#141, atrinik/atrinik#156, atrinik/client#13, atrinik/client#9, atrinik/client#7, and #6 where practical. Do not reserve an isolated numeric version in advance. Land atrinik/atrinik#190's bounded packet primitives first where this payload uses them, then update all current producers, consumers, bots, fixtures, tests, and ADS-2 together.