Implement a pure Go equipment-slot/property comparison query with identification filtering and a bounded structured response for Rust. Do not construct or parse server prose.
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.
Preserved product/design specification and historical implementation notes
Why
The client currently lets a player select and examine an item from either the main inventory or the ground, but comparing it with equipped gear is a manual process. Examine output is free-form chat text, and the client-side object structure contains only presentation metadata such as name, weight, type, flags, quality, and condition. The actual combat modifiers, protections, special flags, identification rules, and equipment interactions remain authoritative on the server.
Add one consistent Compare action for any selected item. For equippable items it should show the relevant currently equipped item and the meaningful differences. For items without a single equipment counterpart it should explain that clearly instead of silently doing nothing.
Existing code to build on
- client/src/gui/widgets/inventory.c already owns both inventory displays. INVENTORY_DISPLAY_MAIN and INVENTORY_DISPLAY_BELOW use the same selection, keyboard, and context-menu paths, so one menu action covers carried items, open-container contents, and items under the player.
- widget_inventory_get_selected() already resolves the selection, and the existing Examine action shows the correct place to add Compare.
- client/src/client/item.c object_find() can find objects in the interface, below inventory, open container, or player inventory.
- The client receives the tag for every PLAYER_EQUIP slot in cpl.equipment, and client/src/gui/widgets/playerdoll.c resolves those tags for display. These tags are useful for UI state, but they are not enough for a complete local comparison.
- server/src/socket/item.c esrv_get_ob_from_count() already restricts an item tag to the player, nested inventory, or the current map square. socket_command_item_examine() provides the correct authorization model for a comparison request from either inventory display.
- server/src/server/living.c living_update_player() contains the canonical object-type to PLAYER_EQUIP mapping and special behavior such as two ring slots and suppression of a shield by a two-handed melee or ranged weapon.
- server/src/server/item.c object_get_description() already owns identification-aware rendering of equipment properties such as stats, WC, damage, AC, speed caps, regeneration, block, absorb, attacks, protections, paths, flags, HP, mana, and item power.
- CLIENT_CMD_BOOK and client/src/gui/popups/book.c already provide a scrollable, selectable, markup-capable server-rendered view. The comparison can reuse that response and avoid adding a second client popup protocol initially.
Proposed player experience
- Add Compare to the inventory context menu beside Examine for both the main and below displays.
- Add a bindable internal action named ?COMPARE, parallel to ?EXAMINE. A default key is not required if no unclaimed intuitive binding exists.
- Open a read-only Item Comparison book view containing:
- candidate and equipped names, icons when their client objects are still available, and the equipment slot;
- aligned Current, Candidate, and Change values;
- quality, condition, requirements, weight, and every identified gameplay property that either item has;
- green/red direction only where better and worse are unambiguous, and neutral higher/lower or present/absent notation for tradeoffs;
- no synthetic overall gear score.
- If the slot is empty, show Nothing equipped in this slot and the candidate properties.
- If the candidate is already equipped, say so rather than comparing the object with itself.
- A ring candidate should show separate comparisons for the left and right ring slots so the player can choose which ring would be replaced.
- A two-handed candidate or currently equipped two-handed weapon should call out the affected weapon and shield slots. Do not display the same two-handed object as a second independent shield comparison merely because the player doll mirrors it into that position.
- Ammo, ranged weapons, lights, and skill items should use their canonical slots. An item such as a consumable or a slotless trinket should remain comparable as an action, but the result should state that there is no single equipped counterpart and show only information the player may inspect.
- Comparing must never pick up, move, apply, mark, lock, or otherwise mutate either object.
Proposed implementation
1. Add a focused request
Append SERVER_CMD_ITEM_COMPARE before SERVER_CMD_NROF in common/toolkit/socket.h and add its player-only handler to server/src/socket/server.c.
Request payload:
- candidate_tag: uint32, exactly one field in network byte order.
The client sender should live beside client_send_examine() in client/src/client/player.c. The server handler must reject truncated or trailing payload data, resolve the tag through esrv_get_ob_from_count(), and return a concise private error for a stale or inaccessible object instead of exposing another map square or player inventory.
Appending the command avoids unnecessary renumbering of existing commands and hard-coded bot constants. Update doc/ADS/ADS-2 with the exact payload, authorization scope, response behavior, and malformed-input handling. Add a matching bot constant or helper only if the bot is made a consumer of the feature.
2. Centralize equipment-slot resolution
Extract the object-type to equipment-slot decision from living_update_player() into a server helper used by both equipment refresh and comparison. The helper should return zero, one, or multiple compatible slots rather than duplicating the type chain in the socket handler.
It must cover every PLAYER_EQUIP entry, both ring slots, ranged and two-handed interactions, empty slots, and slotless applied items. Equipment updates and comparison tests should fail together if a new equipment type is added without a comparison mapping.
3. Compare typed properties on the server
Do not parse object_get_description() text in the client. Extract or add a bounded item-property collector on the server that applies the same identification and need_identify rules, then let object descriptions and comparisons render from that shared source where practical.
Each property should have a stable internal identity, label, value/unit, visibility, and comparison direction. Cover at least:
- Str, Dex, Con, Int, Pow;
- WC, damage, AC, block, absorb;
- melee or ranged delay and range;
- speed modifier and armour speed cap;
- HP, mana, HP/mana regeneration, sustenance;
- attack types and protections;
- item power, slaying, path attunement/repulsion/denial;
- boolean equipment flags such as lifesaving, reflection, stealth, flying, see invisible, infravision, and two-handed;
- level/skill requirement, quality, condition, and weight.
Use the same effective-value conventions as equipment application where possible, including magic and condition. Where the effect on final player stats is nonlinear or context-dependent, display the two item values without claiming an exact final-stat delta. A later loadout simulator can add predicted final stats after living_update_player() has a pure, non-mutating evaluation path; this issue should not temporarily toggle live object flags or mutate the player to calculate a preview.
Unidentified candidates must not reveal hidden properties through a value, row presence, color, delta, or equipped-item comparison. Public facts may still be shown exactly as Examine permits them.
4. Reuse the book response
Render a bounded markup document and send it with the existing CLIENT_CMD_BOOK command. Use the book, title, color, alignment, and obj markup already supported by the text renderer. Escape or otherwise safely render object-controlled names so item text cannot become active client markup.
Keep the result deterministic, omit rows where both values are absent, cap the generated document, and show a clear fallback if it cannot be rendered. A dedicated structured CLIENT_CMD_ITEM_COMPARE response and custom popup should be a follow-up only if interaction or localization requirements outgrow the server-rendered view.
Tests and validation
- Add server unit tests for every equipment type and slot, both rings, empty slots, already-equipped candidates, slotless/non-equippable items, and one-handed/two-handed weapon and shield combinations.
- Add property tests for positive, negative, absent, boolean, percentage, time, and capped values, including condition and magic handling.
- Verify identified, unidentified, previously applied, and no-identification-needed items expose exactly the allowed rows.
- Add socket tests proving that candidates in main inventory, a nested open container, and the current ground square work; stale tags, nearby map squares, and unowned inventories fail safely.
- Cover zero, truncated, and trailing request data and ensure response generation is bounded.
- Manually verify right-click and ?COMPARE from both inventory widgets, scrolling/copying a long result, an item disappearing before handling, and no mutation of item state.
- Build both legacy binaries because common/toolkit/socket.h is shared, then run the prepared server checks and git diff checks.
Acceptance criteria
Shared item-presentation consumer
Consume the typed item-presentation metadata and semantic helpers established by atrinik/client#7/#230. Item comparison and tooltips must not infer charge, rarity, status, or eligibility from display names and must not introduce a second overlay/status mapping.
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 pure Go equipment-slot/property comparison query with identification filtering and a bounded structured response for Rust. Do not construct or parse server prose.
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
The client currently lets a player select and examine an item from either the main inventory or the ground, but comparing it with equipped gear is a manual process. Examine output is free-form chat text, and the client-side object structure contains only presentation metadata such as name, weight, type, flags, quality, and condition. The actual combat modifiers, protections, special flags, identification rules, and equipment interactions remain authoritative on the server.
Add one consistent Compare action for any selected item. For equippable items it should show the relevant currently equipped item and the meaningful differences. For items without a single equipment counterpart it should explain that clearly instead of silently doing nothing.
Existing code to build on
Proposed player experience
Proposed implementation
1. Add a focused request
Append SERVER_CMD_ITEM_COMPARE before SERVER_CMD_NROF in common/toolkit/socket.h and add its player-only handler to server/src/socket/server.c.
Request payload:
The client sender should live beside client_send_examine() in client/src/client/player.c. The server handler must reject truncated or trailing payload data, resolve the tag through esrv_get_ob_from_count(), and return a concise private error for a stale or inaccessible object instead of exposing another map square or player inventory.
Appending the command avoids unnecessary renumbering of existing commands and hard-coded bot constants. Update doc/ADS/ADS-2 with the exact payload, authorization scope, response behavior, and malformed-input handling. Add a matching bot constant or helper only if the bot is made a consumer of the feature.
2. Centralize equipment-slot resolution
Extract the object-type to equipment-slot decision from living_update_player() into a server helper used by both equipment refresh and comparison. The helper should return zero, one, or multiple compatible slots rather than duplicating the type chain in the socket handler.
It must cover every PLAYER_EQUIP entry, both ring slots, ranged and two-handed interactions, empty slots, and slotless applied items. Equipment updates and comparison tests should fail together if a new equipment type is added without a comparison mapping.
3. Compare typed properties on the server
Do not parse object_get_description() text in the client. Extract or add a bounded item-property collector on the server that applies the same identification and need_identify rules, then let object descriptions and comparisons render from that shared source where practical.
Each property should have a stable internal identity, label, value/unit, visibility, and comparison direction. Cover at least:
Use the same effective-value conventions as equipment application where possible, including magic and condition. Where the effect on final player stats is nonlinear or context-dependent, display the two item values without claiming an exact final-stat delta. A later loadout simulator can add predicted final stats after living_update_player() has a pure, non-mutating evaluation path; this issue should not temporarily toggle live object flags or mutate the player to calculate a preview.
Unidentified candidates must not reveal hidden properties through a value, row presence, color, delta, or equipped-item comparison. Public facts may still be shown exactly as Examine permits them.
4. Reuse the book response
Render a bounded markup document and send it with the existing CLIENT_CMD_BOOK command. Use the book, title, color, alignment, and obj markup already supported by the text renderer. Escape or otherwise safely render object-controlled names so item text cannot become active client markup.
Keep the result deterministic, omit rows where both values are absent, cap the generated document, and show a clear fallback if it cannot be rendered. A dedicated structured CLIENT_CMD_ITEM_COMPARE response and custom popup should be a follow-up only if interaction or localization requirements outgrow the server-rendered view.
Tests and validation
Acceptance criteria
Shared item-presentation consumer
Consume the typed item-presentation metadata and semantic helpers established by atrinik/client#7/#230. Item comparison and tooltips must not infer charge, rarity, status, or eligibility from display names and must not introduce a second overlay/status mapping.