Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
b599d25
fix: remove buyOrderDeposit, buyOrderPayBack, TransportAssignmentSubm…
Sellafield May 25, 2026
be60cbc
docs: mark ISSUE-022 as DONE
Sellafield May 25, 2026
5c1009e
docs: add ISSUE-022 design spec and implementation plan
Sellafield May 25, 2026
73d6825
fix: replace DataGridCheckBoxColumn with template column for IsDaily …
Sellafield May 25, 2026
cc9cb6f
docs: add IMPROVEMENT-029 design spec for Discord pinned announcements
Sellafield May 25, 2026
e989b7f
docs: add IMPROVEMENT-029 implementation plan for Discord pinned anno…
Sellafield May 25, 2026
fd0410e
feat: add discord_pin_state table migration (IMPROVEMENT-029)
Sellafield May 25, 2026
daeb7cf
feat: add PinSlot enum and DiscordPinnableMessage (IMPROVEMENT-029)
Sellafield May 25, 2026
be352fd
feat: add DiscordPinStateRepository (IMPROVEMENT-029)
Sellafield May 25, 2026
a500983
fix: use ulong.TryParse in DiscordPinStateRepository.Get (IMPROVEMENT…
Sellafield May 25, 2026
887ee8c
feat: add PinnedAnnouncement to ChannelManager (IMPROVEMENT-029)
Sellafield May 25, 2026
03034ba
feat: handle DiscordPinnableMessage with pin/unpin logic in EventList…
Sellafield May 25, 2026
297b7db
fix: guard Task.Run body against SendMessageAsync failure in EventLis…
Sellafield May 25, 2026
a8fbd4b
feat: register DiscordPinStateRepository in Autofac (IMPROVEMENT-029)
Sellafield May 25, 2026
75e6773
feat: pin daily pool and leaderboard announcements in Discord (IMPROV…
Sellafield May 25, 2026
30ed11a
fix: address final review issues for IMPROVEMENT-029
Sellafield May 25, 2026
daff981
docs: add code graph check steps to plan and CLAUDE.md Required Workflow
Sellafield May 25, 2026
d5015a4
docs: add IMPROVEMENT-030 AutoMarket overhaul spec and backlog entry
Sellafield May 27, 2026
b21bf63
feat(db): add automarket_config table (IMPROVEMENT-030)
Sellafield May 28, 2026
48bdd5a
feat(db): add is_pvp column to resources_gathered tables (IMPROVEMENT…
Sellafield May 28, 2026
532bf74
feat(db): add @is_pvp param to sp_RecordResourceGathered (IMPROVEMENT…
Sellafield May 28, 2026
3937e63
feat(db): include is_pvp in consolidate_statistics MERGE key (IMPROVE…
Sellafield May 28, 2026
e5ff4dd
feat: pass @is_pvp to sp_RecordResourceGathered from all gather modul…
Sellafield May 28, 2026
8d4b2be
feat(db): rewrite recalculate_raw_material_prices with supply/demand …
Sellafield May 28, 2026
88e0774
fix(db): add NULL guard for demand, remove unused pve_qty in recalcul…
Sellafield May 28, 2026
b6a5169
feat(db): remove raw_material_prices fallback from v_all_production_c…
Sellafield May 28, 2026
44ca77b
feat(db): add budget cap, fractional qty, set-based inserts to usp_Re…
Sellafield May 28, 2026
0e1b731
feat: reduce AutoMarket refresh to 1 day and offload DB ops from main…
Sellafield May 28, 2026
660abfd
fix: add concurrent-execution guards to MarketAutoOrdersManager async…
Sellafield May 28, 2026
ae9ac8c
docs: mark raw_material_prices as deprecated (IMPROVEMENT-030)
Sellafield May 28, 2026
3fd85e3
docs: mark IMPROVEMENT-030 as DONE
Sellafield May 28, 2026
a633779
fix: refactor v_all_production_costs CROSS JOIN, improve EventListene…
Sellafield May 28, 2026
04d6910
docs: add ISSUE-024 automarket crafter viability design spec
Sellafield May 28, 2026
168a754
docs: add ISSUE-024 crafter viability implementation plan
Sellafield May 28, 2026
a418fd8
feat(db): add rawmat_purchased table and ISSUE-024 automarket_config …
Sellafield May 28, 2026
50ef627
feat(db): add sp_RecordRawMatPurchased; extend recalculate_raw_materi…
Sellafield May 28, 2026
641e54c
feat(db): update usp_RefreshAutoMarketOrders — sell margins, rawmat b…
Sellafield May 28, 2026
361c958
feat: track raw material AutoMarket purchases in Market.cs for daily …
Sellafield May 28, 2026
7d0b943
docs: mark ISSUE-024 as DONE
Sellafield May 28, 2026
ff96ce3
docs: add IMPROVEMENT-031 AdminTool AutoMarket panel design spec
Sellafield May 28, 2026
5a67a9a
docs: add IMPROVEMENT-031 AutoMarket AdminTool implementation plan
Sellafield May 28, 2026
7b73843
feat: add AutoMarket AdminTool row model types and label map
Sellafield May 28, 2026
11b78f5
feat: add AutoMarketRepository with Config and Trade List queries
Sellafield May 28, 2026
f7fa884
feat: add AutoMarketRepository statistics queries (NIC flow, pricing …
Sellafield May 28, 2026
27661ff
fix: use Math.Round when converting NIC income floats to long in Load…
Sellafield May 28, 2026
32c3484
feat: add AutoMarketRepository orders query and RefreshNow SP execution
Sellafield May 28, 2026
aebb18d
refactor: remove dead productionItems query from LoadOrdersAsync
Sellafield May 28, 2026
4ef1ae5
feat: add AutoMarketConfigViewModel
Sellafield May 28, 2026
ab2bff5
feat: add AddAutoMarketItemViewModel for item picker dialog
Sellafield May 28, 2026
a064c86
feat: add AutoMarketTradeListViewModel (stub window for Task 12)
Sellafield May 28, 2026
b9513a8
feat: add Statistics, Orders, and root AutoMarketViewModel
Sellafield May 28, 2026
cb707e5
fix: add missing OnIsLoading/Refreshing CanExecute notification handl…
Sellafield May 28, 2026
ec7bf7c
feat: add AutoMarketView shell and AutoMarketConfigView XAML (stubs f…
Sellafield May 28, 2026
2088a83
feat: add AutoMarketTradeListView and AddAutoMarketItemWindow (remove…
Sellafield May 28, 2026
e90ed25
feat: add AutoMarketStatisticsView and AutoMarketOrdersView XAML (rem…
Sellafield May 28, 2026
db3b498
feat: wire AutoMarket panel into AdminTool MainViewModel and MainWindow
Sellafield May 28, 2026
f3162ae
fix: resolve decimal cast error and translate resource names in AutoM…
Sellafield May 28, 2026
2f21d99
chore: remove accidental worktree gitlink and ignore .claude/worktrees/
Sellafield May 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,6 @@ Releases/
# graphify-dotnet generated output (regenerates on build)
docs/graph/
graphify-out/

# Claude Code worktrees
.claude/worktrees/
74 changes: 74 additions & 0 deletions CHANGELOG-p36.3.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
Perpetuum Server 2 — Patch p36.3 Changelog
==========================================
Date: 2026-05-24


NEW FEATURES
------------

Equipment Set Bonuses (IMPROVEMENT-025)
Pilots now receive automatic stat bonuses when equipping multiple items
belonging to the same named equipment set (e.g. set_striker).

- New database schema: equipmentsets and equipmentsetbonuses tables store
set definitions and their per-piece bonus thresholds.
- New effect type: effect_equipment_set_bonus — a dedicated effect category
that carries set bonus modifiers so the client can display them correctly.
- EquipmentSetRepository loads set data from the database on startup and
keeps it in an in-memory cache for fast zone-side access.
- EquipmentSetBonusCalculator evaluates which sets are active on a robot
(enough equipped pieces to qualify for each bonus tier) and returns the
resulting modifier stack.
- SetBonusEffectApplicator runs on every Robot.OnUpdate tick: it compares
the current active sets to what is already applied, removes stale effects,
and adds new ones — ensuring the effect list stays in sync with the
robot's loadout at all times.
- Robot stat pipeline extended to include set bonus modifiers alongside
existing armor, shield, and module modifiers.
- Pilot set content SQL added for the Striker set (set_striker).

Equipment Set Bonus Effect Display (IMPROVEMENT-027)
The set bonus effect shown in the client's effect list now embeds the
actual numeric modifier values instead of showing a generic label.

- EquipmentSetBonusResult now carries the full list of EffectModifier
entries produced by the active set bonuses.
- SetBonusEffectApplicator builds a human-readable description from those
modifiers (e.g. "+5% armor max, +3% weapon cycle time") and injects it
into the effect display name.
- Robot.OnUpdate updated to pass the modifier data through to the applicator.

Daily Objectives Announcement — Extended Format (unlabelled)
The server's daily objectives broadcast now includes each objective's
description text alongside its name, giving pilots more context about what
to do without having to open the objectives panel.

Before: " — Destroy 10 NPC units"
After: " — Destroy 10 NPC units: Eliminate NPC robots anywhere on Nia."

Daily Objectives on Cold Boot (IMPROVEMENT-024)
The server now announces today's daily objective pool to the season channel
automatically when it starts up, so pilots who log in after a restart are
not left in the dark.

Today's Daily Objectives in Admin Tool (IMPROVEMENT-024)
The Admin Tool Statistics tab gains a "Today's Daily Objectives" section
that lists the current pool with activity type, required quantity, and
bonus season point value. Data is loaded asynchronously from the database.


BUG FIXES
---------

Market NIC Tracking Missing for Direct Purchases (ISSUE-020)
Direct market purchases handled by MarketBuyItem (buy-now from a sell
order) were not recording NicSpent or NicEarned season activity, so those
trades were invisible to the season tracking system. The bug affected all
three purchase branches:
- Player-to-player sell orders
- Vendor orders with infinite stock
- Vendor orders with finite stock
Matching RecordActivity calls have been added to each branch, consistent
with the existing hooks in FulfillBuyOrderInstantly.


2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ For any non-trivial task:
4. Check `docs/graph/GRAPH_REPORT.md` for God Nodes (high-risk symbols); run `.\tools\query-graph.ps1 <ClassName> -Direction in` to enumerate direct dependents — a null result is normal (most classes have no detected importers) and does not mean the change is safe (if `graph.json` is absent, skip and continue to step 5)
5. Understand existing patterns
6. Evaluate runtime implications
7. Produce a short implementation plan
7. Produce a short implementation plan — for any task that modifies an interface or a widely-used class, the plan must include an explicit step to run `.\tools\query-graph.ps1 <ClassName> -Direction in` before touching that file
8. Then implement

---
Expand Down
165 changes: 163 additions & 2 deletions docs/backlog/improvements.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Last ID used

029
031

## IMPROVEMENT-002 - Refactor Hardcoded System Characters and Channels

Expand Down Expand Up @@ -416,7 +416,7 @@ Deleting a set should warn if modules are still assigned to it.

## IMPROVEMENT-029 - Pin Daily Activity Announcements in Discord

Status: TODO
Status: DONE
Priority: HIGH
Area: Seasons / Announcements / Discord Integration

Expand All @@ -442,3 +442,164 @@ When a daily activity announcement is dispatched to the integrated Discord chann
Requires the Discord bot/webhook integration to have the `Manage Messages` permission in the target channel.
If the current integration uses an incoming webhook rather than a bot token, pinning is not possible via webhooks — a bot token with the `Manage Messages` permission will be required. Assess the current integration type before implementing.
The last pinned message ID can be stored in memory across restarts only if a restart always re-announces; otherwise persist it (a single-row config table or a flat file entry is sufficient).

---

## IMPROVEMENT-030 - AutoMarket Overhaul: NIC Injection Control, Dynamic Risk-Aware Pricing, and Performance Refactor

Status: DONE
Priority: HIGH
Area: Economy / AutoMarket / Database

### Problem

The AutoMarket has three interconnected problems that together drive hyperinflation:

1. **Plasma buy orders are a NIC faucet.** Every plasma sale to the bot calls `PayOutToSeller`, which creates NIC from nothing — there is no vendor wallet being drained. The buy quantity equals 100% of all plasma gathered in the past 7 days (`cdp.gathered`), making the bot procyclical: more farming → larger buy orders → more NIC created. No daily spending limit exists.

2. **Raw material prices are backwards and static.** `recalculate_raw_material_prices` distributes plasma NIC proportionally to gather volume, which means more supply → higher price (opposite of supply/demand). The static `raw_material_prices` fallback table requires manual maintenance and ignores zone risk — alpha and gamma materials are priced identically per the formula.

3. **Performance and thread-safety concerns.** `usp_RefreshAutoMarketOrders` uses four SQL cursors for order placement (row-by-row, slow). `MarketAutoOrdersManager` fires blocking DB operations synchronously from the process loop. `resources_gathered` lacks zone origin data.

### Impact

Inflation continues unchecked while the AutoMarket runs. Raw material prices do not reflect actual gather difficulty or zone risk, making the crafting economy unrealistic. Cursor-based SQL and blocking process-loop operations are latent performance risks.

### Proposed Fix

**Part A — NIC Injection Control:**
- New `automarket_config` table for all configurable parameters (anchor fraction, buy quantity fraction, daily budget).
- `usp_RefreshAutoMarketOrders`: multiply plasma buy quantity by `plasma_buy_qty_fraction` (default 0.60); add hard daily NIC budget cap derived from `plasma_sold.income`.
- `MarketAutoOrdersManager`: change refresh interval from 3 days to 1 day.

**Part B — Zone-Aware Gather Tracking:**
- Add `is_pvp BIT NOT NULL DEFAULT 0` to `resources_gathered_daily` and `resources_gathered`.
- Add `@is_pvp BIT = 0` parameter to `sp_RecordResourceGathered`; update `consolidate_statistics` to preserve it in the merge key.
- Update 5 C# gather call sites (`DrillerModule`, `HarvesterModule`, `LargeDrillerModule`, `LargeHarvesterModule`, `LootContainer`) to pass `!zone.Configuration.Protected`.

**Part C — Dynamic Risk-Aware Raw Material Pricing:**
- Rewrite `recalculate_raw_material_prices` with a new formula: `price = plasma_anchor × supply_demand_ratio × pvp_risk_multiplier`. Plasma anchor = live alpha plasma price × configurable fraction (default 0.15). Supply/demand ratio clamped 0.25–4.0. Risk multiplier 1.0 (all PvE) to 2.0 (all PvP); ungathered materials default to max scarcity + max risk.
- Remove the `raw_material_prices` fallback from `v_all_production_costs`. The table is deprecated but left in place.

**Part D — Performance and Thread-Safety Refactoring:**
- Analyze `MarketAutoOrdersManager.Update(time)`: determine process thread ownership; if blocking DB calls on the main process loop are confirmed, offload via `Task.Run` with proper exception handling following existing codebase patterns.
- Replace SQL cursors in `usp_RefreshAutoMarketOrders` with set-based `INSERT ... SELECT` where analysis confirms a performance benefit. Evaluate DELETE-all + INSERT-all vs. MERGE for the order refresh pattern.
- Assess lock contention between frequent `sp_RecordResourceGathered` inserts and `consolidate_statistics` MERGE under load.

### Implementation Notes

Completed in branch p36.4. All code changes committed to server runtime. Operator must execute the following SQL DDL against live database before new logic takes effect:

**Schema changes (Part B):**
1. `ALTER TABLE resources_gathered_daily ADD is_pvp BIT NOT NULL DEFAULT 0`
2. `ALTER TABLE resources_gathered ADD is_pvp BIT NOT NULL DEFAULT 0`

**Configuration table (Part A):**
3. `CREATE TABLE automarket_config (id INT PRIMARY KEY, plasma_buy_qty_fraction DECIMAL(5,4), daily_nic_budget BIGINT, plasma_anchor_fraction DECIMAL(5,4))`
4. Insert default row: `INSERT INTO automarket_config VALUES (1, 0.60, [calculate from current gather], 0.15)`

**Stored procedure changes (Parts A, B, C):**
5. `ALTER PROCEDURE sp_RecordResourceGathered` — add `@is_pvp BIT = 0` parameter
6. `ALTER PROCEDURE consolidate_statistics` — add `is_pvp` to GROUP BY and MERGE key
7. `ALTER PROCEDURE recalculate_raw_material_prices` — rewrite with new formula (see design spec)
8. `ALTER PROCEDURE usp_RefreshAutoMarketOrders` — apply budget cap and set-based inserts

**View changes (Part C):**
9. `ALTER VIEW v_all_production_costs` — remove `raw_material_prices` dependency, use dynamic pricing from procedure

**Execution notes:**
- Schema changes 1-2 are safe (backward-compatible defaults).
- Execute configuration table creation (3-4) before stored procedure changes.
- Procedures 5-9 must be executed in order: schema → config → procedures → view.
- No data migration required; existing tables and values remain unchanged.
- After DDL execution, refresh server cache (`gameConfig.ConfigManager` or admin command) to load `automarket_config`.

### Notes

Full design spec: `docs/superpowers/specs/2026-05-27-automarket-overhaul-design.md`

The `raw_material_prices` table is not dropped — only removed from active query paths — to preserve historical reference and allow rollback.
The `@is_pvp` parameter on `sp_RecordResourceGathered` defaults to `0`, so any call site not yet updated silently falls back to PvE treatment rather than failing.
Part D refactoring is scoped to analysis + targeted fixes only; broad restructuring of the market engine is out of scope.

---

## IMPROVEMENT-031 - AdminTool: AutoMarket Management and Statistics

Status: DONE
Priority: HIGH
Area: Admin Tool / Economy / AutoMarket

### Description

Add a dedicated **AutoMarket** panel to the AdminTool with four tabs: Config, Trade List, Statistics, and Orders. Operators currently have no in-tool way to tune AutoMarket parameters, manage the item trade list, or inspect economy health — all changes require direct DB access.

Follows the Seasons panel pattern: single nav entry, tabbed ViewModel, MVVM + ChangeQueue. No new server-side API is needed except one thin request handler for the manual refresh trigger.

### Tab 1 — Config

Editable grid of all `automarket_config` parameters with human-readable labels:
`plasma_anchor_fraction`, `plasma_buy_qty_fraction`, `daily_plasma_budget_nic`, `daily_rawmat_budget_nic`, `product_sell_margin`, `raw_mat_sell_multiplier`, `product_buyback_margin`, `resource_ds_ratio_min`, `resource_ds_ratio_max`.

Changes are queued via `ChangeQueue` and committed through the existing SQL script / direct-apply pipeline.

A **Refresh Now** toolbar button sends a server request to immediately trigger `MarketAutoOrdersManager` — requires one new thin request handler wired via the existing `Commands.cs` / Autofac pattern.

### Tab 2 — Trade List

Editable grid of `market_orders_configuration` rows. Columns: translated item name, definition name (read-only), amount (editable). Translated names via the existing translations system; falls back to `definitionname`.

- **Add item** — searchable item picker backed by `entitydefaults`, filterable by translated or internal name.
- **Remove item** — warns if the item is a dependency of others (via `v_required_raw_materials`).
- **Queue Save** per row — follows the ChangeQueue deduplication pattern ([[IMPROVEMENT-016]]).

A read-only sub-panel below the grid shows the derived raw materials that will be generated from the current trade list (via `v_required_raw_materials`), also with translated names.

### Tab 3 — Statistics

Read-only dashboard, refreshes on demand.

- **NIC Flow** — plasma NIC in and rawmat NIC out for today / last 7 days / total (from `plasma_sold` and `rawmat_purchased`); net delta per period; today's spend vs daily cap shown as a ratio.
- **Pricing Trace** — per raw material: translated name, plasma anchor input, supply/demand ratio, PvP risk multiplier, resulting price. Explains why each material is priced as it is.
- **Gather Breakdown** — per raw material: gather volume over last 7 days split by PvP vs PvE (from `resources_gathered_daily.is_pvp`). Validates risk multiplier inputs.

### Tab 4 — Orders

Read-only live snapshot of all active AutoMarket orders. Columns: translated item name, order type (Buy / Sell / Buyback), price, amount, translated market/base name, category (Plasma / Raw Material / Production Item). Filterable by order type and category.

Market/base names use translated display names via the existing translations system, with fallback to internal name.

### Impact

Without this panel, every config change, trade list edit, and economy health check requires direct DB access. The AdminTool gives operators a safe, auditable surface for the most frequently tuned AutoMarket levers introduced in [[IMPROVEMENT-030]] and [[ISSUE-024]].

### Proposed Implementation

**Server side:**
- Add one new `Commands.cs` entry and request handler (`AutoMarketRefreshHandler` or similar) that calls `MarketAutoOrdersManager` refresh method directly.
- Register via Autofac following existing handler patterns.

**AdminTool:**
- `AutoMarketViewModel` — root VM, owns tab VMs, wires Refresh Now command via server request.
- `AutoMarketConfigViewModel` — loads `automarket_config`; editable rows; ChangeQueue integration.
- `AutoMarketTradeListViewModel` — loads `market_orders_configuration`; item picker dialog; derived raw material sub-panel; ChangeQueue integration.
- `AutoMarketStatisticsViewModel` — loads NIC flow aggregates, pricing trace, gather breakdown; refresh-on-demand.
- `AutoMarketOrdersViewModel` — loads live market order snapshot; filter support; refresh-on-demand.
- Corresponding XAML Views for each VM.
- Wire `AutoMarketViewModel` into `MainViewModel` following the same pattern as `SeasonsViewModel`.

**No new DB tables required.** All data comes from existing tables and views introduced in IMPROVEMENT-030 and ISSUE-024.

### Notes

Translations: use the existing translations system throughout (item names, market/base names). Fall back to internal names if no translation exists — never show raw definition IDs to the operator.
ChangeQueue deduplication for Config and Trade List tabs — see [[IMPROVEMENT-016]].
The derived raw materials sub-panel in Trade List is read-only and does not generate ChangeQueue entries.
The Refresh Now button should be disabled while a refresh is in progress and should surface any server-side error to the operator.
Pricing Trace data source: query the last computed values from `resource_market_prices` (or equivalent output of `recalculate_raw_material_prices`) — no live re-computation in the AdminTool.

### Implementation

Implemented via plan `docs/superpowers/plans/2026-05-28-automarket-admintool.md` (14 tasks, branch p36.4).
Refresh Now calls SPs directly from AdminTool DB connection (no server-side handler needed).
`{x:Static}` binding on source-generator types causes MC1000 BAML errors — worked around with instance forwarder properties on `AutoMarketOrdersViewModel`.
Loading
Loading