Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
4566d4b
Add IMPROVEMENT-040 design spec: AutoMarket raw material decoupling
Sellafield Jun 10, 2026
d3dd262
IMPROVEMENT-040: implementation plan
Sellafield Jun 10, 2026
715a43d
IMPROVEMENT-040: migration SQL, sp_RecordRawMatWeeklyPurchased, schem…
Sellafield Jun 10, 2026
9e2cb16
IMPROVEMENT-040: fix MERGE HOLDLOCK, TOC anchors, dbo prefix
Sellafield Jun 10, 2026
b3b8f72
IMPROVEMENT-040: rename v_required_raw_materials doc, update v_all_pr…
Sellafield Jun 10, 2026
80e0486
IMPROVEMENT-040: update stale view ref in usp_RefreshAutoMarketOrders…
Sellafield Jun 10, 2026
6ed9927
IMPROVEMENT-040: recalculate_raw_material_prices uses entitydefaults …
Sellafield Jun 10, 2026
667bd37
IMPROVEMENT-040: usp_RefreshAutoMarketOrders — #covered_rawmats repla…
Sellafield Jun 10, 2026
00cc657
IMPROVEMENT-040: record weekly raw mat purchase in Market.FulfillSell…
Sellafield Jun 10, 2026
836d45c
IMPROVEMENT-040: labels update, PricingTraceRow new columns, AutoMark…
Sellafield Jun 10, 2026
187bd5e
IMPROVEMENT-040: repository — rename view ref, expand PricingTrace, a…
Sellafield Jun 10, 2026
dce2309
IMPROVEMENT-040: Statistics Pricing Trace — add Bought/Week and Weekl…
Sellafield Jun 10, 2026
f8e0fef
IMPROVEMENT-040: Raw Materials VM and View
Sellafield Jun 10, 2026
54d3d7d
IMPROVEMENT-040: wire Raw Materials tab into AutoMarketViewModel and …
Sellafield Jun 10, 2026
1d1dfa9
IMPROVEMENT-040: migration deploy note, document no-TransactionScope …
Sellafield Jun 10, 2026
bad0966
IMPROVEMENT-040: mark DONE in backlog
Sellafield Jun 10, 2026
b507246
P36.6: narrow raw material category filter to cf_ore, cf_liquid, cf_o…
Sellafield Jun 10, 2026
54cbdd6
IMPROVEMENT-041: add design spec for corp tag + top-10 corp wealth panel
Sellafield Jun 10, 2026
0581d30
IMPROVEMENT-041: add implementation plan
Sellafield Jun 10, 2026
bc1c599
IMPROVEMENT-041: extend money supply data models
Sellafield Jun 10, 2026
59121e8
IMPROVEMENT-041: update money supply repository queries
Sellafield Jun 10, 2026
bf442b1
IMPROVEMENT-041: add Top10CorpRows to money supply view model
Sellafield Jun 10, 2026
5df8772
IMPROVEMENT-041: add corp tag column and top-10 corps section to mone…
Sellafield Jun 10, 2026
e046da3
IMPROVEMENT-041: mark DONE in backlog
Sellafield Jun 10, 2026
10028d3
IMPROVEMENT-041: fix bottom margin on corp wealth DataGrid
Sellafield Jun 10, 2026
c3e04f5
IMPROVEMENT-042: add design spec for per-item order type control on t…
Sellafield Jun 10, 2026
3549e5c
IMPROVEMENT-042: add implementation plan for per-item order type control
Sellafield Jun 10, 2026
aaa7285
IMPROVEMENT-042: migration — add create_sell/buyback_orders to market…
Sellafield Jun 10, 2026
d136c3e
IMPROVEMENT-042: update SP doc snapshot — filter Steps 3 and 6 by ord…
Sellafield Jun 10, 2026
2e302bb
IMPROVEMENT-042: add CreateSellOrders / CreateBuybackOrders to trade …
Sellafield Jun 10, 2026
4957523
IMPROVEMENT-042: expand LoadTradeListAsync to read order type flags
Sellafield Jun 10, 2026
3fa9cbf
IMPROVEMENT-042: include order type flags in QueueSave UPDATE and Add…
Sellafield Jun 10, 2026
f0f1272
IMPROVEMENT-042: add Sell Orders / Buyback Orders checkbox columns to…
Sellafield Jun 10, 2026
10f9b43
IMPROVEMENT-042: mark DONE in backlog
Sellafield Jun 10, 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
157 changes: 156 additions & 1 deletion docs/backlog/improvements.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,161 @@
# Last ID used

039
042

## IMPROVEMENT-042 - AutoMarket: Per-Item Order Type Control on Trade List

Status: DONE
Priority: CRITICAL
Area: AutoMarket / Economy

### Implementation Summary

Implemented on branch `p36.6`.

- **DB migration:** `docs/db_structure/migrations/IMPROVEMENT-042-trade-list-order-type.sql` — adds `create_sell_orders BIT NOT NULL DEFAULT 1` and `create_buyback_orders BIT NOT NULL DEFAULT 1` to `market_orders_configuration` (idempotent, both default to 1 to preserve existing behaviour); updates `usp_RefreshAutoMarketOrders` with `WHERE moc.create_sell_orders = 1` on Step 3 and `WHERE moc.create_buyback_orders = 1` on Step 6.
- **SP doc snapshot:** `docs/db_structure/stored_procedures/dbo.usp_RefreshAutoMarketOrders.StoredProcedure.sql` updated to match.
- **`AutoMarketTradeListRow`:** added `CreateSellOrders bool`, `CreateBuybackOrders bool` observable properties; `OriginalCreate*` originals for dirty tracking; `IsDirty` updated to cover all three fields.
- **`AutoMarketRepository.LoadTradeListAsync`:** SELECT expanded to include both new columns; row construction reads and sets all four new properties.
- **`AutoMarketTradeListViewModel.QueueSave`:** UPDATE SQL now includes all three SET columns; originals reset after queuing. `AddItem`: new row defaults both flags to `true`.
- **`AutoMarketTradeListView.xaml`:** two `DataGridTemplateColumn` checkbox columns added between Amount and Queue Save — "Sell Orders" (bound to `CreateSellOrders`) and "Buyback Orders" (bound to `CreateBuybackOrders`).

Design spec: `docs/superpowers/specs/2026-06-10-trade-list-order-type-design.md`
Implementation plan: `docs/superpowers/plans/2026-06-10-trade-list-order-type.md`

### Problem

The AutoMarket trade list currently creates both buy and sell orders for every configured item. There is no way to control per item whether the system should place a buy order, a sell order, both, or neither. This matters for items where only one direction makes economic sense (e.g. sinks that should only be bought back, or items that should only be sold to players but not repurchased).

### Notes

- Default value must be `Both` so existing trade list entries are unaffected after migration.
- Similar in spirit to the per-item override pattern introduced in IMPROVEMENT-040 for raw materials.

---

## IMPROVEMENT-041 - AdminTool Economy: Corporation Tag on Money Supply + Top-10 Wealthiest Corporations

Status: DONE
Priority: HIGH
Area: AdminTool / Economy

### Implementation Summary

Implemented on branch `p36.6`.

- **`EconomyWealthRow`:** added `CorpTag` property (empty string for unguilded/default-corp characters).
- **`EconomyCorporationWealthRow`:** new model with `Rank`, `Name`, `Tag`, `MemberCount`, `CorpWallet`, `MemberAggregate`, `Combined` (computed).
- **`EconomyMoneySupplyData`:** added `Top10CorpRows`.
- **`EconomyMoneySupplyRepository`:** `LoadTop10Async` updated to use a correlated subquery for corp tag (avoids row duplication from non-unique `corporationmembers.memberid`); new `LoadTop10CorpAsync` queries all non-default active corps, ordered by combined wealth.
- **`EconomyMoneySupplyViewModel`:** added `Top10CorpRows` collection, populated in `RefreshAsync`.
- **`EconomyMoneySupplyView.xaml`:** `Corp` column added to character DataGrid; new Top-10 Corporations DataGrid appended.
- No schema changes. No server-side code touched.

### Problem

The Money Supply panel shows top-10 wealthiest characters but lacks context about their corporation membership. Additionally, there is no equivalent view for corporations — the wealthiest corporations and their composition are invisible.

### Proposed Fix

1. **Character money supply table** — add a `Corporation Tag` column showing the 4-character corp tag (or blank if NPC/unguilded) next to each character row.
2. **New top-10 wealthiest corporations section** — query the sum of all member wallets (and/or the corp wallet itself) per corporation; display corporation name, tag, and member count.

### Notes

- Confirm whether "wealthiest corporation" means the corporate wallet balance, the aggregate of member wallets, or both.
- Identify the relevant DB tables/views (`corporations`, `characters`, `wallet` or equivalent) before writing queries.
- Keep to existing AdminTool MVVM patterns — thin VM, no business logic leakage.

---

## IMPROVEMENT-040 - AutoMarket: Decouple Raw Material Coverage from Trade List

Status: DONE
Priority: CRITICAL
Area: AutoMarket / Economy

### Implementation Summary

Implemented on branch `p36.6` (commits `715a43d`–`1d1dfa9`).

- **DB migration:** `docs/db_structure/migrations/IMPROVEMENT-040-rawmat-decoupling.sql` — creates `automarket_rawmat_overrides`, `automarket_rawmat_weekly_tracking`, inserts `weekly_rawmat_cap_default = 500000000`, adds `IX_rmp_on_name`, renames view, creates `sp_RecordRawMatWeeklyPurchased`
- **View rename:** `v_required_raw_materials` → `v_trade_list_raw_material_demand` (demand signal only)
- **`v_all_production_costs`:** `raw_resources` CTE now scans entitydefaults directly (cf_raw_material bitmask)
- **`recalculate_raw_material_prices`:** material enumeration expanded to all cf_raw_material items
- **`usp_RefreshAutoMarketOrders`:** `#covered_rawmats` replaces `#raw_materials`; Steps 4+5 are cap-driven
- **`Market.cs`:** `sp_RecordRawMatWeeklyPurchased` called at 3 `FulfillSellOrderInstantly` sites
- **AdminTool:** Raw Materials tab (VM + View), Statistics Pricing Trace columns, repository updates
- **Recipe-graph demand signal:** analysed and rejected — C-only approach (gather-volume proxy) chosen; max scarcity for ungathered materials is self-correcting on a low-population server

### Problem

The current AutoMarket system identifies raw materials exclusively by recursively exploding items in `market_orders_configuration` (the trade list). This creates tight coupling: materials for items outside the trade list get no market support, and any newly added craftable item requires a manual trade list update before its raw material supply chain becomes active. The trade list's role is also overloaded — it currently drives both finished product orders and raw material demand calculations.

### Proposed Architecture

Decouple raw material coverage from the trade list:

- **Raw materials** — identified from `entitydefaults` (not from the trade list). Prices calculated independently. Infinite-style buy/sell orders placed for all qualifying materials with a **configurable weekly cap per material** (see Impact Analysis below).
- **Trade list** — scoped to finished product buy/sell/buyback orders only. Product prices derived from raw material prices (cost-plus), not set independently.

This inverts the current dependency:

```
Current: trade list → raw material identification → raw material prices → orders
Proposed: entitydefaults → raw material prices → orders (capped)
trade list + raw material prices → product prices → orders
```

### Raw Material Coverage Filter

Use `entitydefaults` to enumerate qualifying raw materials. Filter criteria (exact requirements TBD during implementation):

- `enabled = 1`
- `hidden = 0`
- Category matches raw material category flag(s) — filtered by category ID exact match or category tree traversal (children of raw material category nodes)

Avoids coverage explosion from legacy/unobtainable items while automatically including newly added materials that meet the criteria.

### Price Calculation

Retain and extend the existing formula from IMPROVEMENT-030:

```
price = plasma_anchor × supply_demand_ratio × pvp_risk_multiplier
```

**PvP risk multiplier:** Preserved as-is. Materials gathered predominantly in PvP zones retain their risk premium.

**Supply/demand ratio:** Retain the existing formula (`daily_demand / daily_supply_avg`, clamped to `[ds_ratio_min, ds_ratio_max]`). Investigate whether adding recipe-graph-derived demand (from the `components` table) as a supplementary signal to the S/D ratio improves pricing accuracy. If the analysis shows negligible benefit (e.g. because recipe demand is already implicit in gather volume on a functioning server), this addition may be skipped. Document the decision.

**Recalculation cadence:** Daily, same as the existing 24-hour refresh cycle introduced in IMPROVEMENT-030. Startup-only recalculation was considered and rejected — prices must track the live economy between restarts.

### Weekly Cap Per Material — Impact Analysis Required

Replace the current arrangement (fixed 10,000,000 quantity for sell orders; budget-capped buy orders) with a **configurable weekly quantity cap per material**. Before implementation, analyze:

1. **NIC injection bound** — what weekly cap value keeps raw material buy-side NIC injection comparable to or lower than the current `daily_rawmat_budget_nic` regime?
2. **Supply adequacy** — does a weekly cap prevent the market from running dry for high-demand materials during active play periods?
3. **Per-material vs global cap** — whether a single global cap or per-category/per-material overrides are needed for balance.
4. **Interaction with daily budget** — determine whether the weekly cap replaces or works alongside the existing daily NIC budget guard.

The daily NIC budget cap must remain as a hard guardrail until the impact analysis confirms the weekly cap is safe.

### Affected Systems

- `recalculate_raw_material_prices` stored procedure — extend material enumeration to use `entitydefaults` filter
- `usp_RefreshAutoMarketOrders` — step 4 (raw material buy orders) and step 5 (raw material sell orders) reworked
- `v_required_raw_materials` view — may be retired or repurposed as a product-cost calculation helper
- `automarket_config` table — add `weekly_rawmat_cap_per_material` and category filter parameters
- AdminTool AutoMarket panel (IMPROVEMENT-031) — expose new cap config and coverage filter parameters

### Notes

- Cross-reference IMPROVEMENT-030 (AutoMarket overhaul) — builds on its pricing formula and config table.
- Cross-reference IMPROVEMENT-031 (AutoMarket AdminTool) — Config tab and Statistics tab need updates for new parameters.
- Cross-reference IMPROVEMENT-035 (player order signal) — raw material coverage expansion increases the surface area where player orders could manipulate S/D ratios; revisit IMPROVEMENT-035 deferral conditions after this is shipped.
- The recipe-graph demand signal analysis (S/D ratio extension) should be done before coding the pricing procedure — if the analysis is inconclusive or shows risk, skip it and document why.
- Category flag filter criteria (exact category IDs and whether to include children) must be confirmed against `entitydefaults` live data before generating the SQL filter.

## IMPROVEMENT-039 - Add economy health statistics beyond NIC flow reporting

Expand Down
38 changes: 37 additions & 1 deletion docs/db_structure/database_schema_documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ Generated from DBML structure.
- [artifacttypes](#artifacttypes)
- [attributeFlags](#attributeflags)
- [automarket_config](#automarket-config)
- [automarket_rawmat_overrides](#automarket-rawmat-overrides)
- [automarket_rawmat_weekly_tracking](#automarket-rawmat-weekly-tracking)
- [automarket_unbought_resources](#automarket-unbought-resources)
- [automarket_unsold_leftovers](#automarket-unsold-leftovers)
- [beams](#beams)
Expand Down Expand Up @@ -1014,7 +1016,41 @@ Generated from DBML structure.
| `product_sell_margin` | `1.2` | Product sell orders priced at production_cost × this value (was 1.0). Creates headroom for player crafters to undercut AutoMarket. |
| `raw_mat_sell_multiplier` | `1.5` | Raw material sell orders priced at production_cost × this value (was 2.0). Reduces input cost barrier for crafters. |
| `product_buyback_margin` | `0.80` | AutoMarket buys production items back at production_cost × this value. Guarantees crafters an exit price floor. |
| `daily_rawmat_budget_nic` | `5000000` | Max NIC paid for raw material buy order fulfillments per UTC calendar day. Caps NIC injection from AutoMarket raw material purchases. |
| `daily_rawmat_budget_nic` | `5000000` | Max NIC spent on raw material buy orders per UTC calendar day (0 = unlimited). |
| `weekly_rawmat_cap_default` | `500000000` | Default weekly buy quantity cap per raw material. 0 = unlimited. |

---

## automarket_rawmat_overrides

**Schema:** `dbo`

Per-material overrides for AutoMarket raw material coverage. Materials with no row use global defaults from `automarket_config`.

### Columns

| Column | Definition |
|---|---|
| `definitionname` | `varchar(100) [not null, pk]` |
| `weekly_cap_override` | `int [null]` — NULL = use global default; 0 = unlimited |
| `create_buy_orders` | `bit [not null, default: 1]` |
| `create_sell_orders` | `bit [not null, default: 1]` |

---

## automarket_rawmat_weekly_tracking

**Schema:** `dbo`

Tracks units of each raw material purchased via AutoMarket buy orders per week. Written by `sp_RecordRawMatWeeklyPurchased`. Rolled up by `usp_RefreshAutoMarketOrders` to enforce the per-material weekly cap. Cleaned up at 90-day rolling window.

### Columns

| Column | Definition |
|---|---|
| `week_start` | `date [not null, pk]` — Monday of the ISO week |
| `definitionname` | `varchar(100) [not null, pk]` |
| `qty_purchased` | `bigint [not null, default: 0]` |

---

Expand Down
106 changes: 106 additions & 0 deletions docs/db_structure/migrations/IMPROVEMENT-040-rawmat-decoupling.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
-- docs/db_structure/migrations/IMPROVEMENT-040-rawmat-decoupling.sql
-- IMPROVEMENT-040: AutoMarket Raw Material Decoupling
-- Run against perpetuumsa while server is ONLINE (no data migration needed).
-- Apply in order — objects are dependencies of later steps.
-- DEPLOY ORDER: After running this migration, ALSO apply (in order):
-- 1. docs/db_structure/views/v_trade_list_raw_material_demand.sql (CREATE OR ALTER VIEW)
-- 2. docs/db_structure/views/v_all_production_costs.sql (CREATE OR ALTER VIEW)
-- 3. docs/db_structure/stored_procedures/dbo.recalculate_raw_material_prices.StoredProcedure.sql
-- 4. docs/db_structure/stored_procedures/dbo.usp_RefreshAutoMarketOrders.StoredProcedure.sql
-- 5. docs/db_structure/stored_procedures/dbo.sp_RecordRawMatWeeklyPurchased.StoredProcedure.sql

USE [perpetuumsa];
GO

--------------------------------------------------------------------
-- 1. New table: per-material AutoMarket overrides
--------------------------------------------------------------------
IF OBJECT_ID('dbo.automarket_rawmat_overrides', 'U') IS NULL
BEGIN
CREATE TABLE dbo.automarket_rawmat_overrides (
definitionname VARCHAR(100) NOT NULL,
weekly_cap_override INT NULL, -- NULL = use global default; 0 = unlimited
create_buy_orders BIT NOT NULL DEFAULT 1,
create_sell_orders BIT NOT NULL DEFAULT 1,
CONSTRAINT PK_rawmat_overrides PRIMARY KEY CLUSTERED (definitionname)
);
PRINT 'Created automarket_rawmat_overrides';
END
GO

--------------------------------------------------------------------
-- 2. New table: weekly quantity tracking per raw material
--------------------------------------------------------------------
IF OBJECT_ID('dbo.automarket_rawmat_weekly_tracking', 'U') IS NULL
BEGIN
CREATE TABLE dbo.automarket_rawmat_weekly_tracking (
week_start DATE NOT NULL,
definitionname VARCHAR(100) NOT NULL,
qty_purchased BIGINT NOT NULL DEFAULT 0,
CONSTRAINT PK_rawmat_weekly PRIMARY KEY CLUSTERED (week_start, definitionname)
);
PRINT 'Created automarket_rawmat_weekly_tracking';
END
GO

--------------------------------------------------------------------
-- 3. New automarket_config row: weekly_rawmat_cap_default
--------------------------------------------------------------------
IF NOT EXISTS (SELECT 1 FROM dbo.automarket_config WHERE param_name = 'weekly_rawmat_cap_default')
BEGIN
INSERT INTO dbo.automarket_config (param_name, param_value)
VALUES ('weekly_rawmat_cap_default', 500000000);
PRINT 'Inserted weekly_rawmat_cap_default into automarket_config';
END
GO

--------------------------------------------------------------------
-- 4. Index on resource_market_prices(calculated_on, resource_name)
-- Required for efficient MERGE in recalculate_raw_material_prices
-- after material list expands.
--------------------------------------------------------------------
IF NOT EXISTS (
SELECT 1 FROM sys.indexes
WHERE object_id = OBJECT_ID('dbo.resource_market_prices')
AND name = 'IX_rmp_on_name'
)
BEGIN
CREATE NONCLUSTERED INDEX IX_rmp_on_name
ON dbo.resource_market_prices (calculated_on, resource_name);
PRINT 'Created IX_rmp_on_name on resource_market_prices';
END
GO

--------------------------------------------------------------------
-- 5. Rename view: v_required_raw_materials → v_trade_list_raw_material_demand
--------------------------------------------------------------------
IF OBJECT_ID('dbo.v_required_raw_materials', 'V') IS NOT NULL
AND OBJECT_ID('dbo.v_trade_list_raw_material_demand', 'V') IS NULL
BEGIN
EXEC sp_rename 'dbo.v_required_raw_materials', 'v_trade_list_raw_material_demand';
PRINT 'Renamed v_required_raw_materials to v_trade_list_raw_material_demand';
END
GO

--------------------------------------------------------------------
-- 6. New stored procedure: sp_RecordRawMatWeeklyPurchased
--------------------------------------------------------------------
CREATE OR ALTER PROCEDURE [dbo].[sp_RecordRawMatWeeklyPurchased]
@week_start DATE,
@definitionname VARCHAR(100),
@quantity BIGINT
AS
BEGIN
SET NOCOUNT ON;
MERGE dbo.automarket_rawmat_weekly_tracking WITH (HOLDLOCK) AS target
USING (SELECT @week_start, @definitionname, @quantity)
AS source(week_start, definitionname, qty_purchased)
ON target.week_start = source.week_start
AND target.definitionname = source.definitionname
WHEN MATCHED THEN
UPDATE SET qty_purchased = target.qty_purchased + source.qty_purchased
WHEN NOT MATCHED THEN
INSERT (week_start, definitionname, qty_purchased)
VALUES (source.week_start, source.definitionname, source.qty_purchased);
END;
GO
Loading
Loading