diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index b10103a..1e20953 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -16,7 +16,7 @@ jobs: env: Workspace: ${{ github.workspace }} - + steps: - uses: actions/checkout@v4 - name: Setup .NET @@ -33,3 +33,27 @@ jobs: name: Perpetuum-Server-v2-${{ github.sha }} path: ${{ env.Workspace }}/bin/x64/Release/net8.0 if: ${{ github.event_name == 'push'}} + + build-admintool-installer: + + runs-on: windows-latest + + env: + Workspace: ${{ github.workspace }} + + steps: + - uses: actions/checkout@v4 + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.x + - name: Restore dependencies + run: dotnet restore -p:Platform=x64 + - name: Build AdminTool installer + run: dotnet build src/Perpetuum.AdminToolInstaller/Perpetuum.AdminToolInstaller.wixproj --no-restore --configuration Release --verbosity quiet -p:Platform=x64 + - name: Upload AdminTool installer + uses: actions/upload-artifact@v4 + with: + name: Perpetuum-AdminTool-Installer-${{ github.sha }} + path: ${{ env.Workspace }}/src/Perpetuum.AdminToolInstaller/bin/x64/Release/en-US/*.msi + if: ${{ github.event_name == 'push' }} diff --git a/.gitignore b/.gitignore index 445e81e..36b1cc1 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,5 @@ bin/ Releases/ .claude/settings.local.json .planning/ +*.log +*_wpftmp.csproj diff --git a/CLAUDE.md b/CLAUDE.md index 53c015b..406c520 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -122,6 +122,35 @@ Claude MUST: --- +# Game Content Creation + +When creating or modifying gameplay entities (items, robots, effects, modules, tech tree nodes), Claude MUST consult: + +- `docs/content/claude_game_content_guide.md` + +This guide is the authoritative procedural reference for SQL content pipelines and dependency order. + +## Content Creation Rules + +Claude MUST: + +- Read the guide before generating any content SQL. +- Follow the entity lifecycle and dependency order defined in the guide (sections 2 and 24). +- Never hardcode definition or extension IDs — always resolve dynamically via `entitydefaults` / `extensions` lookups. +- Use naming conventions from the guide (section 3): `def_`, `_pr`, `_cprg`, `effect_`, `cf_` prefixes. +- Use idempotent SQL patterns: `MERGE`, `IF NOT EXISTS`, or `DELETE + INSERT` as appropriate per table. +- Generate full-chain content when possible — avoid partial generation. +- Run the validation checklist (section 26) before declaring content complete. +- Ask the user for existing database values when dynamic resolution requires live data not available in docs. + +Claude MUST NOT: + +- Hardcode IDs for definitions, extensions, aggregate fields, or tech tree nodes. +- Assume table relationships without verifying via `docs/db_structure/`. +- Generate partial content chains that leave items in an unresearchable, uncraftable, or inaccessible state. + +--- + # Required Workflow For any non-trivial task: @@ -337,7 +366,7 @@ When asked to: - "implement improvements" Claude should: -1. review backlog files +1. review backlog files, only check what you've been asked to, (e.g. issues or improvements), unless issues and improvements are depending on each other 2. prioritize unfinished HIGH priority items 3. prefer low-risk/high-impact work unless instructed otherwise 4. produce a short implementation plan diff --git a/PerpetuumServer2.sln b/PerpetuumServer2.sln index 31cd584..d9d55f4 100644 --- a/PerpetuumServer2.sln +++ b/PerpetuumServer2.sln @@ -17,6 +17,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Perpetuum.RequestHandlers", EndProject Project("{B7DD6F7E-DEF8-4E67-B5B7-07EF123DB6F0}") = "Perpetuum.ServerService2Installer", "src\Perpetuum.ServerService2Installer\Perpetuum.ServerService2Installer.wixproj", "{ABBCAAB8-AA31-4705-BDA8-B4EDDFFA7F4E}" EndProject +Project("{B7DD6F7E-DEF8-4E67-B5B7-07EF123DB6F0}") = "Perpetuum.AdminToolInstaller", "src\Perpetuum.AdminToolInstaller\Perpetuum.AdminToolInstaller.wixproj", "{D3F8A2B1-6C9E-4D7F-A5B8-2E0C4F9A3D6B}" +EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perpetuum.ServerService2", "src\Perpetuum.ServerService2\Perpetuum.ServerService2.csproj", "{A2FCE930-E013-4731-B354-F7DA00322D38}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Perpetuum.AdminTool", "src\Perpetuum.AdminTool\Perpetuum.AdminTool.csproj", "{A7D1E3C5-9F4B-42E8-8A6C-B5D7F1E9C2A0}" @@ -51,6 +53,10 @@ Global {ABBCAAB8-AA31-4705-BDA8-B4EDDFFA7F4E}.Debug|x64.Build.0 = Debug|x64 {ABBCAAB8-AA31-4705-BDA8-B4EDDFFA7F4E}.Release|x64.ActiveCfg = Release|x64 {ABBCAAB8-AA31-4705-BDA8-B4EDDFFA7F4E}.Release|x64.Build.0 = Release|x64 + {D3F8A2B1-6C9E-4D7F-A5B8-2E0C4F9A3D6B}.Debug|x64.ActiveCfg = Debug|x64 + {D3F8A2B1-6C9E-4D7F-A5B8-2E0C4F9A3D6B}.Debug|x64.Build.0 = Debug|x64 + {D3F8A2B1-6C9E-4D7F-A5B8-2E0C4F9A3D6B}.Release|x64.ActiveCfg = Release|x64 + {D3F8A2B1-6C9E-4D7F-A5B8-2E0C4F9A3D6B}.Release|x64.Build.0 = Release|x64 {A2FCE930-E013-4731-B354-F7DA00322D38}.Debug|x64.ActiveCfg = Debug|x64 {A2FCE930-E013-4731-B354-F7DA00322D38}.Debug|x64.Build.0 = Debug|x64 {A2FCE930-E013-4731-B354-F7DA00322D38}.Release|x64.ActiveCfg = Release|x64 @@ -70,6 +76,7 @@ Global {5992F619-7655-43F1-8A33-21B5877714A5} = {0BC991A1-133C-49ED-A141-80E2A906898B} {C1F1B228-8286-4207-9C51-09AB3FDFE935} = {0BC991A1-133C-49ED-A141-80E2A906898B} {ABBCAAB8-AA31-4705-BDA8-B4EDDFFA7F4E} = {0BC991A1-133C-49ED-A141-80E2A906898B} + {D3F8A2B1-6C9E-4D7F-A5B8-2E0C4F9A3D6B} = {0BC991A1-133C-49ED-A141-80E2A906898B} {A2FCE930-E013-4731-B354-F7DA00322D38} = {0BC991A1-133C-49ED-A141-80E2A906898B} {A7D1E3C5-9F4B-42E8-8A6C-B5D7F1E9C2A0} = {0BC991A1-133C-49ED-A141-80E2A906898B} EndGlobalSection diff --git a/docs/Patches/p36.1/Features/Seasons/migration.sql b/docs/Patches/p36.1/Features/Seasons/migration.sql new file mode 100644 index 0000000..1fa6ee7 --- /dev/null +++ b/docs/Patches/p36.1/Features/Seasons/migration.sql @@ -0,0 +1,4 @@ +-- IMPROVEMENT-009: Targeted Objectives (Mining & Harvesting) +-- Run once against the game database before deploying the updated server binary. + +ALTER TABLE season_objectives ADD target_definition_id INT NULL; diff --git a/docs/backlog/completed.md b/docs/backlog/completed.md index e69de29..ef12ee5 100644 --- a/docs/backlog/completed.md +++ b/docs/backlog/completed.md @@ -0,0 +1,411 @@ +# Completed Backlog + +--- + +## ISSUE-001 - Enforce UTC for seasons.date_start and seasons.date_end + +Status: DONE +Priority: HIGH +Area: Seasons / Database + +### Problem +All usages of `seasons.date_start` and `seasons.date_end` must be enforced to UTC. Currently there is no guaranteed UTC enforcement at the read/write boundary, which can cause incorrect season activation windows if server or client time zones differ. + +### Impact +Season start/end boundaries may be evaluated incorrectly under non-UTC system time, causing seasons to activate or expire at wrong times — affecting rewards, eligibility windows, and any logic gated on season date comparisons. + +### Proposed Fix +- Audit all C# code that reads `date_start` / `date_end` from the `seasons` table and ensure `DateTime.SpecifyKind(..., DateTimeKind.Utc)` or `DateTimeOffset` is applied on read. +- Audit all write paths (INSERT / UPDATE) to ensure values are converted to UTC before persistence. +- Audit stored procedures and views that reference `seasons.date_start` / `seasons.date_end` for any implicit local-time assumptions. +- Consider adding a DB constraint or documented convention that these columns are always UTC. + +### Notes +Related columns: `seasons.date_start`, `seasons.date_end`. +Any `DateTime.Now` comparisons against these values should become `DateTime.UtcNow`. + +--- + +## ISSUE-002 - Suppress leadership announcements when no active season exists + +Status: DONE +Priority: HIGH +Area: Seasons / Chat + +### Problem +Leadership (top-player/corporation) announcements are broadcast even when there is no active season. This results in meaningless or misleading notifications being sent to players outside of any season window. + +### Impact +Players receive leadership announcements during inactive periods, causing confusion about season state and degrading trust in the announcement system. + +### Proposed Fix +- Before broadcasting any leadership announcement, check whether an active season currently exists. +- If no season is active, skip the announcement entirely. +- Reuse the existing active-season lookup pattern (e.g. `SeasonService` / `GetCurrentSeason`) rather than introducing a new query. + +### Notes +Related to the announcements added in the chat announcement feature (feat: float points, chat announcements, NIC filtering, anti-farming). +Ensure the guard is applied to all leadership announcement sites, not just one code path. + +--- + +## ISSUE-003 - Training characters must be excluded from Seasons participation and rewards + +Status: DONE +Priority: CRITICAL +Area: Seasons / Characters + +### Problem +Characters in training (tutorial/training state) are not currently excluded from Season participation. They can accumulate season activity points and receive season rewards, which is unintended — training characters are not fully active players and should have no influence on season standings or reward distribution. + +### Impact +Training characters polluting season standings undermines competitive integrity. They may also consume reward resources (NIC, items) that should only go to active, graduated players. + +### Proposed Fix +- Identify the flag or state that marks a character as "in training" — locate the relevant character property or DB column. +- Add a training-character guard at all Season entry points: + - Activity point accumulation: skip recording any points for training characters. + - Leaderboard queries: exclude training characters from standings. + - Reward distribution: skip reward grants for training characters at season end. +- Prefer a single shared predicate (e.g. `character.IsInTraining`) checked at the boundary rather than scattered inline checks. +- Ensure the guard covers both real-time activity tracking and any batch/end-of-season processing. + +### Notes +Verify the exact field or state that identifies a training character before implementing — consult character schema in `docs/db_structure/`. +The exclusion must be silent from the training character's perspective — no error, just no season interaction. +If training characters can graduate mid-season, define whether they retroactively become eligible or only participate from graduation onward (recommend: from graduation onward, no backfill). + +--- + +## ISSUE-005 - RecordActivity IsInTraining() causes synchronous DB queries in combat hot path + +Status: DONE +Priority: MEDIUM +Area: Seasons / Performance + +### Problem +`RecordActivity` calls `Character.Get(characterId).IsInTraining()` which issues two synchronous `ExecuteScalar` DB queries per call with no caching. For low-frequency events (NPC kills, artifact finds, mission completes) this is acceptable. However, `DamageDone` and `DamageReceived` (added in IMPROVEMENT-005 Phase 2) wire `RecordActivity` into `Unit.OnDamageTaken`, which fires every weapon cycle in the zone update loop — potentially tens of times per second per engagement when a season is active and damage rates are configured. + +### Impact +When a season is active with DamageDone/DamageReceived rates configured, each combat hit incurs 2 synchronous DB round trips for training-character filtering. Under load this could degrade zone update performance. If no season is active, the early-exit at `_activeSeason == null` prevents DB access entirely. + +### Proposed Fix +Move the `IsInTraining()` check to after the rate lookup in `RecordActivity`, so it only runs when a matching rate exists for the activity type — this eliminates the DB cost entirely for activity types with no configured rate. Longer term, cache the `IsInTraining` result per character (it is immutable once a character graduates from training). + +### Notes +Introduced by IMPROVEMENT-005 (DamageDone/DamageReceived hooks). Other high-frequency hooks (ArmorRestored, EnergyDrain*, EnergyTransfer*) have the same exposure once rates are configured. +See `SeasonService.cs` `RecordActivity` method for the current check order. + +--- + +## ISSUE-008 - New Item: descriptiontoken incorrectly strips def_ prefix + +Status: DONE +Priority: HIGH +Area: Admin Tool / New Item Dialog + +### Problem +`BasicPanelViewModel.SuggestDescriptionToken` strips the `def_` prefix from `definitionname` before appending `_desc`. When `definitionname` is `def_my_item`, the suggested `descriptiontoken` becomes `my_item_desc` instead of the correct `def_my_item_desc`. + +### Impact +The auto-suggested description token will not match the actual game translation key convention, requiring the operator to manually correct it on every new item creation. + +### Proposed Fix +In `src/Perpetuum.AdminTool/NewItem/BasicPanelViewModel.cs`, change `SuggestDescriptionToken` to keep the `def_` prefix if present: + +```csharp +private string SuggestDescriptionToken(string defName) +{ + if (defName.EndsWith("_desc", StringComparison.OrdinalIgnoreCase)) + return defName; + return defName + "_desc"; +} +``` + +Also update the `_desc` doubling guard in the design spec (section 9) to match: the suffix check should apply to the full name, not the stripped name. + +### Notes +Affects Tab 1 (BasicPanel), Tab 2 (CalibrationPanel), and Tab 3 (PrototypePanel) since all three use the same `BasicPanelViewModel.SuggestDescriptionToken` method. + +--- + +## ISSUE-009 - New Item dialog ignores Apply mode, always writes directly to DB + +Status: DONE +Priority: CRITICAL +Area: Admin Tool / New Item Dialog + +### Problem +`NewItemDialogViewModel.SaveAsync` always calls `_changeApplier.ExecuteAsync([change])`, which +writes the generated SQL directly to the database. The current `ApplyMode` (`AppSession.CurrentMode`) +is never consulted. When the operator has selected `SqlScript` mode, the save still hits the DB +directly instead of producing a script file. + +### Impact +Any operator using `SqlScript` mode (the safer, review-before-apply workflow) is silently bypassed +every time they create a new item. Changes go live in the database immediately with no script +artifact, defeating the purpose of the apply-mode setting and removing the audit trail. + +### Proposed Fix +Pass `AppSession` (or at least a `Func` / the mode value at open-time) into +`NewItemDialogViewModel`. In `SaveAsync`, branch on the mode: + +- `DirectDb`: keep the existing `_changeApplier.ExecuteAsync([change])` call. +- `SqlScript`: call `SqlScriptBuilder.Build([change], authorEmail)` and write the resulting + script to `AppSettings.SqlOutputDirectory`, the same way `MainViewModel.SaveAsync` does it. + Show the output path in `SaveResultSummary`. Skip the live DB reload since nothing was committed. + +`EntitiesViewModel.OpenNewItemDialogAsync` constructs the dialog — it needs access to `AppSession` +(already injected into `MainViewModel`; wire it through to `EntitiesViewModel` via DI or pass it +as a constructor parameter). + +### Notes +`MainViewModel.SaveAsync` (lines ~174–200) is the reference implementation for the SqlScript branch. +`SqlScriptBuilder.Build` signature: `Build(IEnumerable changes, string? authorEmail)`. + +--- + +## ISSUE-010 - Entities tab: Stats section new-stat value input rejects negative and decimal values + +Status: DONE +Priority: MEDIUM +Area: Admin Tool / Entities / Stats + +### Problem +The "Add stat" value `TextBox` in `EntityDetailView.xaml` (line 137) is bound to +`EntityDetailViewModel.NewStatValue` (`double`) with `UpdateSourceTrigger=PropertyChanged`. +Because the binding tries to parse on every keystroke, intermediate input states such as `-` +(start of a negative number) or `1.` (start of a decimal) fail to convert and cause WPF to revert +the field to the last successfully parsed value (typically `0`). In practice this means only +positive integers can be reliably entered; negative values and fractional values reset to zero +mid-entry or on focus loss. + +### Impact +Operators cannot set stats with negative values (e.g. resistances, offsets) or sub-integer values +(e.g. 0.5 repair bonus) without the field snapping back to zero. The underlying +`EntityDetailViewModel.NewStatValue` property is correctly typed as `double`, so the constraint is +purely a UI binding issue. + +### Proposed Fix +Change `UpdateSourceTrigger=PropertyChanged` to `UpdateSourceTrigger=LostFocus` on the stat value +`TextBox` in `src/Perpetuum.AdminTool/Views/EntityDetailView.xaml` (line 137): + +```xml + +``` + +This lets the user complete the full value (including leading `-` or a decimal point) before WPF +attempts to parse. Optionally add `StringFormat={}{0:G}` and `ConverterCulture=en-US` to ensure +consistent decimal-point parsing regardless of the operator's Windows locale. + +### Notes +The stat `DataGrid` in the same view allows inline editing of existing stat values — verify that +column also accepts negative/decimal input correctly after the fix. + +--- + +## ISSUE-011 - New Item dialog broken when Entities tab has never been reloaded + +Status: DONE +Priority: HIGH +Area: Admin Tool / New Item Dialog / Entities + +### Problem +`NewItemDialogViewModel` depends on two data sources that are only populated when the user +explicitly clicks "Reload" on the Entities tab: + +- **`EntitiesViewModel.AllRows`** — passed as `existingRows` to `NewItemDialogViewModel`, + used to build `_existingRowsById`. When empty, selecting a clone source silently does nothing: + `LoadCloneAsync` calls `_existingRowsById.TryGetValue(definition, out var row)` and returns + immediately without populating any fields. +- **`EntitiesViewModel.Fields`** — passed as `aggregateFields` to `InitializeAsync`. When empty, + the Stats tab has no field pickers and the Property Modifiers tab has no options. + +The clone source *dropdown* appears populated (it draws from `LookupCache.Entities`, which IS +loaded on login via `MainViewModel.InitializeLookupsAsync`), so the operator sees a list of +entities to copy from but receives no feedback and no data when selecting one. + +### Impact +Opening "New Item..." before ever visiting the Entities tab produces a broken dialog: cloning +an existing entity does nothing, and the Stats and Property Modifiers tabs are completely empty. +An operator unaware of the required tab-visit order will assume the feature is broken. + +### Proposed Fix +In `MainViewModel.InitializeLookupsAsync` (or immediately after), also trigger +`Entities.ReloadAsync()` so that `AllRows` and `Fields` are populated at startup alongside the +`LookupCache`. This requires no new DB queries beyond what "Reload" already does. + +Alternatively, in `EntitiesViewModel.OpenNewItemDialogAsync`, guard with an early +`if (AllRows.Count == 0 || Fields.Count == 0) await ReloadAsync();` before opening the dialog, +so the required data is fetched on demand if missing. + +### Notes +`MainViewModel` constructor: `_ = InitializeLookupsAsync()` (line ~62) — the startup +refresh already calls `LookupCache.RefreshAllAsync` but does not call `Entities.ReloadAsync`. +`EntitiesViewModel.ReloadAsync` (line ~200) is the existing load path for both `AllRows` and +`Fields`; reuse it rather than introducing a separate aggregate-fields load. + +--- + +## ISSUE-012 - New Robot dialog: incorrect entity filtering in clone pickers + +Status: DONE +Priority: HIGH +Area: Admin Tool / Robots + +### Problem +The clone pickers in the New Robot dialog filter entities incorrectly: + +- **Part pickers (Head, Chassis, Leg, Inventory):** `BuildPartItems` currently excludes `hidden` entities in addition to filtering by `enabled` and category. Hidden entities should be clonable — operators need to reference them when creating variants of non-public parts. +- **Main robot picker:** `PackageItemPickItem.BuildFilteredList` filters against a broad `AllowedRoots` list that includes many non-robot categories (ammo, equipment, materials, etc.) and also excludes hidden entities. The main picker should be scoped to `cf_robots` + `enabled` only. + +### Impact +Operators cannot clone from hidden robot entities (e.g. prototype or internal variants), and the main picker surfaces non-robot entities as potential clone sources, causing confusion. + +### Proposed Fix +- **Part pickers (`BuildPartItems` in `NewRobotDialogViewModel`):** remove the `e.Hidden` exclusion — filter only by `e.Enabled && e.CategoryFlags != 0 && node.ContainsOrEquals(e.CategoryFlags)`. +- **Main picker:** replace `PackageItemPickItem.BuildFilteredList` usage in `InitializeAsync` (currently used for `EnabledItems`) with a dedicated filter scoped to `cf_robots` + `e.Enabled` only, no hidden exclusion. + +### Notes +`BuildPartItems` is in `NewRobotDialogViewModel.cs`. `EnabledItems` is populated in `InitializeAsync` via `NewItemRepository.LoadAsync` → `PackageItemPickItem.BuildFilteredList` — the main picker change required a new filtered list built directly from `_lookupCache.Entities` rather than changing the shared `BuildFilteredList` method. + +--- + +## IMPROVEMENT-001 - Recurring Seasons with Selectable Periodicity + +Status: DONE +Priority: HIGH +Area: Seasons + +### Description +Add the ability to mark a Season as recurring, with a configurable periodicity (e.g. weekly, monthly, custom interval). A recurring Season should auto-start at its `date_start` and automatically schedule the next iteration upon completion, without manual admin intervention. + +### Impact +Reduces operational overhead for regular competitive seasons. Enables a predictable cadence for players and removes the need to manually create and activate each season cycle. + +### Proposed Implementation +- Add `is_recurring` (bit) and `recurrence_period_days` (int, nullable) columns to the `seasons` table. +- On season end, the server-side season scheduler checks `is_recurring`; if true, clones the season with `date_start = previous date_end` and `date_end = date_start + recurrence_period_days`, then activates it. +- Auto-start logic: the existing season scheduler (or a new timed check) compares `date_start` against `DateTime.UtcNow` and activates eligible recurring seasons automatically. +- Admin tool should expose `is_recurring` and `recurrence_period_days` fields when creating or editing a season. +- Ensure the recurrence chain is bounded (e.g. optional `recurrence_end_date` or max iteration count) to prevent unbounded DB growth. + +### Notes +Depends on [[ISSUE-001]] — UTC enforcement on `date_start`/`date_end` must be in place before auto-start timing is reliable. +Periodicity options to support at minimum: daily, weekly, biweekly, monthly, custom (n days). + +--- + +## IMPROVEMENT-003 - Admin Tool: Item Designer + +Status: DONE +Priority: MEDIUM +Area: Admin Tool / Items + +### Description +Add an Item Designer feature to the Admin Tool that allows operators to create new game items from scratch. The designer should cover basic item parameters, configurable item stats, a side-by-side comparison view against an existing item, and translation entry for all supported locales. + +### Impact +Currently creating new items requires direct DB manipulation and knowledge of multiple interrelated tables. A guided UI reduces the risk of malformed items, lowers the barrier for content authors, and speeds up content iteration. + +### Proposed Implementation +- **Basic Parameters panel** — item name (internal key), category, type, volume, mass, tier, icon, flags (marketable, stackable, etc.). +- **Stats panel** — dynamic list of stat key/value pairs drawn from the known `entitydefaults` / `aggregatevalues` schema; support adding, editing, and removing stat rows with type validation. +- **Comparison panel** — item picker to load an existing item alongside the new item; display both sets of parameters and stats in a diff-style view so the designer can use an existing item as a reference template. +- **Translations panel** — entry fields for item display name and description per supported locale; pre-populate from the selected reference item if one is chosen. +- **Save flow** — validate required fields, then write to the relevant tables (`entitydefaults`, `aggregatevalues`, `translation`, etc.) in a single transaction; report success or validation errors inline. +- Consider a "Clone from existing" shortcut that pre-fills all panels from a chosen item, reducing the common case of creating a variant. + +### Notes +Requires understanding of the full item definition schema — consult `docs/db_structure/` before implementation. +Translation keys must follow existing naming conventions to avoid collisions. +The comparison/reference view is a UX aid only; it must not overwrite the new item's data silently. + +--- + +## IMPROVEMENT-004 - Admin Tool: Robot Designer + +Status: DONE +Priority: MEDIUM +Area: Admin Tool / Robots + +### Description +Add a Robot Designer feature to the Admin Tool that allows operators to create new robots from scratch. The designer covers selecting a robot template, configuring basic robot parameters, setting stats for the robot chassis and each robot part (head, leg, chassis, inventory), a side-by-side comparison view against an existing robot, and translation entry for all supported locales. + +### Impact +Creating new robots currently requires direct manipulation of multiple interrelated DB tables (robot definition, parts, slots, stats, translations). A guided UI reduces the risk of malformed robot definitions, lowers the barrier for content authors, and speeds up robot content iteration — especially important given robot complexity relative to generic items. + +### Proposed Implementation +- **Template panel** — select a robot template (chassis archetype) that pre-defines the part layout (number of head/leg/chassis/inventory slots, turret/missile/aux slot counts). Templates should be drawn from existing robot definitions. +- **Basic Parameters panel** — robot name (internal key), faction, tier, icon, size class, flags (marketable, constructable, etc.). +- **Robot Stats panel** — stats applied to the robot entity itself (speed, sensor, accumulator, etc.), drawn from the known `aggregatevalues` schema for robot entities. +- **Parts Stats panel** — per-part (head, leg, chassis, inventory) stat configuration; each part is a separate sub-entity with its own `entitydefaults` / `aggregatevalues` rows. Support adding, editing, and removing stat rows per part with type validation. +- **Comparison panel** — robot picker to load an existing robot alongside the new definition; display chassis + all parts parameters and stats in a diff-style view for reference. Must not auto-apply reference values to the new robot. +- **Translations panel** — display name and description per supported locale for the robot and each named part; pre-populate from the selected reference robot if one is chosen. +- **Save flow** — validate all required fields across robot and parts, then write the full robot definition (robot entity, part entities, slot assignments, stats, translations) in a single transaction; report success or validation errors inline. +- Consider a "Clone from existing robot" shortcut that pre-fills all panels from a chosen robot, covering the common variant/reskin workflow. + +### Notes +Robot definitions span multiple tables — consult `docs/db_structure/` thoroughly before implementation; pay attention to part ownership and slot assignment relationships. +Translation keys for robot and parts must follow existing naming conventions. +The template selection step is critical: slot counts and part types are structurally fixed by the template and must not be violated by subsequent panel edits. +See [[IMPROVEMENT-003]] for the related Item Designer — shared UI patterns (stats panel, translations panel, comparison panel) should be extracted as reusable components. + +--- + +## IMPROVEMENT-011 - NPC fleeing state reduces max speed by 25% + +Status: DONE +Priority: CRITICAL +Area: NPCs / AI + +### Description +When an NPC enters the fleeing state its maximum speed should be capped at 75% of its normal maximum speed. The cap must be lifted and the original max speed fully restored as soon as the NPC exits the fleeing state. + +### Impact +Without this penalty a fleeing NPC moves at full speed, making it trivially easy to escape combat. Applying a speed reduction creates a meaningful tactical consequence for the fleeing state and improves gameplay authenticity. + +### Proposed Implementation +- Locate the code path that transitions an NPC into the fleeing state (likely in the AI state machine or NPC behaviour handler). +- On entering fleeing: record the NPC's current max speed, then apply a multiplier of `0.75` to the effective max speed. +- On exiting fleeing: restore the recorded original max speed, regardless of the exit reason (combat re-engagement, death, target lost, etc.). +- Prefer a modifier/buff approach consistent with how other temporary stat changes are applied to NPCs — avoid overwriting the base definition value directly. +- Ensure the speed is recalculated immediately on state transition so the change takes effect within the same update tick. + +### Notes +Verify how max speed is stored and applied for NPCs — consult NPC AI and movement subsystems before implementing. +The 75% cap applies to max speed only; acceleration and other movement parameters are unaffected unless a future improvement specifies otherwise. +Edge case: if the NPC is already speed-debuffed by a player effect, the fleeing cap should compose correctly with existing modifiers rather than overriding them. + +--- + +## IMPROVEMENT-018 - New Robot dialog UX improvements + +Status: DONE +Priority: HIGH +Area: Admin Tool / Robots + +### Description + +Three UX improvements to the New Robot dialog (IMPROVEMENT-004): + +1. **IsRobot default true** — The `IsRobot` checkbox on the Basic tab should be checked by default when the New Robot dialog opens, since the dialog is purpose-built for robots. + +2. **Per-part Clone from pickers** — Head, Chassis, Leg, and Inventory tabs each need a "Clone from" ComboBox (same pattern as the main entity clone picker on the dialog header). Selecting an existing part entity pre-fills that tab's stats rows with the source entity's `aggregatevalues`, with an inline "Original" column in the stats DataGrid showing the cloned values for comparison (same pattern as `StatsPanelViewModel.LoadFromClone`). + +3. **Category-filtered part pickers** — Each part's clone picker only lists entities whose `CategoryFlags` matches the relevant flag (and its descendants) using the existing `CategoryFlagsNode.ContainsOrEquals` logic: + - Main robot picker → `cf_robots` (`0x0000000000000001`) + - Head picker → `cf_robot_head` (`0x0000000000000150`) + - Chassis picker → `cf_robot_chassis` (`0x0000000000000250`) + - Leg picker → `cf_robot_leg` (`0x0000000000000350`) + - Inventory picker → `cf_robot_inventory` (`0x0000000000030915`) + +### Impact + +Without `IsRobot` defaulting to true, operators must manually check it every time — the dialog name implies it. Without per-part cloning, operators must manually enter all stats for each part from scratch, which is error-prone and slow for robots that share a part family. The category filter ensures the picker only surfaces relevant entities rather than the full 1000+ entity list. + +### Notes +- `CategoryFlagsNode.ContainsOrEquals` handles both exact match and descendant matching, so sub-types within each category are included automatically. +- `StatsPanelViewModel.LoadFromClone` already supports the "Original" column display — no changes needed to that class. +- The main entity clone picker (existing) is not affected; it continues to use the robots-only `BuildRobotItems` filter (see ISSUE-012). diff --git a/docs/backlog/improvements.md b/docs/backlog/improvements.md index 77ce4bd..37e6939 100644 --- a/docs/backlog/improvements.md +++ b/docs/backlog/improvements.md @@ -1,25 +1,6 @@ -## IMPROVEMENT-001 - Recurring Seasons with Selectable Periodicity +# Last ID used -Status: TODO -Priority: HIGH -Area: Seasons - -### Description -Add the ability to mark a Season as recurring, with a configurable periodicity (e.g. weekly, monthly, custom interval). A recurring Season should auto-start at its `date_start` and automatically schedule the next iteration upon completion, without manual admin intervention. - -### Impact -Reduces operational overhead for regular competitive seasons. Enables a predictable cadence for players and removes the need to manually create and activate each season cycle. - -### Proposed Implementation -- Add `is_recurring` (bit) and `recurrence_period_days` (int, nullable) columns to the `seasons` table. -- On season end, the server-side season scheduler checks `is_recurring`; if true, clones the season with `date_start = previous date_end` and `date_end = date_start + recurrence_period_days`, then activates it. -- Auto-start logic: the existing season scheduler (or a new timed check) compares `date_start` against `DateTime.UtcNow` and activates eligible recurring seasons automatically. -- Admin tool should expose `is_recurring` and `recurrence_period_days` fields when creating or editing a season. -- Ensure the recurrence chain is bounded (e.g. optional `recurrence_end_date` or max iteration count) to prevent unbounded DB growth. - -### Notes -Depends on [[ISSUE-001]] — UTC enforcement on `date_start`/`date_end` must be in place before auto-start timing is reliable. -Periodicity options to support at minimum: daily, weekly, biweekly, monthly, custom (n days). +023 ## IMPROVEMENT-002 - Refactor Hardcoded System Characters and Channels @@ -45,88 +26,46 @@ Hardcoded strings are fragile: a rename or new deployment environment requires h Audit starting points: seasons announcement code, chat subsystem, any admin tool chat/broadcast helpers. Keep backward compatibility with existing DB channel records — constants should match stored names unless a migration is also performed. -## IMPROVEMENT-003 - Admin Tool: Item Designer - -Status: TODO -Priority: MEDIUM -Area: Admin Tool / Items - -### Description -Add an Item Designer feature to the Admin Tool that allows operators to create new game items from scratch. The designer should cover basic item parameters, configurable item stats, a side-by-side comparison view against an existing item, and translation entry for all supported locales. - -### Impact -Currently creating new items requires direct DB manipulation and knowledge of multiple interrelated tables. A guided UI reduces the risk of malformed items, lowers the barrier for content authors, and speeds up content iteration. - -### Proposed Implementation -- **Basic Parameters panel** — item name (internal key), category, type, volume, mass, tier, icon, flags (marketable, stackable, etc.). -- **Stats panel** — dynamic list of stat key/value pairs drawn from the known `entitydefaults` / `aggregatevalues` schema; support adding, editing, and removing stat rows with type validation. -- **Comparison panel** — item picker to load an existing item alongside the new item; display both sets of parameters and stats in a diff-style view so the designer can use an existing item as a reference template. -- **Translations panel** — entry fields for item display name and description per supported locale; pre-populate from the selected reference item if one is chosen. -- **Save flow** — validate required fields, then write to the relevant tables (`entitydefaults`, `aggregatevalues`, `translation`, etc.) in a single transaction; report success or validation errors inline. -- Consider a "Clone from existing" shortcut that pre-fills all panels from a chosen item, reducing the common case of creating a variant. - -### Notes -Requires understanding of the full item definition schema — consult `docs/db_structure/` before implementation. -Translation keys must follow existing naming conventions to avoid collisions. -The comparison/reference view is a UX aid only; it must not overwrite the new item's data silently. - -## IMPROVEMENT-004 - Admin Tool: Robot Designer - -Status: TODO -Priority: MEDIUM -Area: Admin Tool / Robots - -### Description -Add a Robot Designer feature to the Admin Tool that allows operators to create new robots from scratch. The designer covers selecting a robot template, configuring basic robot parameters, setting stats for the robot chassis and each robot part (head, leg, chassis, inventory), a side-by-side comparison view against an existing robot, and translation entry for all supported locales. - -### Impact -Creating new robots currently requires direct manipulation of multiple interrelated DB tables (robot definition, parts, slots, stats, translations). A guided UI reduces the risk of malformed robot definitions, lowers the barrier for content authors, and speeds up robot content iteration — especially important given robot complexity relative to generic items. - -### Proposed Implementation -- **Template panel** — select a robot template (chassis archetype) that pre-defines the part layout (number of head/leg/chassis/inventory slots, turret/missile/aux slot counts). Templates should be drawn from existing robot definitions. -- **Basic Parameters panel** — robot name (internal key), faction, tier, icon, size class, flags (marketable, constructable, etc.). -- **Robot Stats panel** — stats applied to the robot entity itself (speed, sensor, accumulator, etc.), drawn from the known `aggregatevalues` schema for robot entities. -- **Parts Stats panel** — per-part (head, leg, chassis, inventory) stat configuration; each part is a separate sub-entity with its own `entitydefaults` / `aggregatevalues` rows. Support adding, editing, and removing stat rows per part with type validation. -- **Comparison panel** — robot picker to load an existing robot alongside the new definition; display chassis + all parts parameters and stats in a diff-style view for reference. Must not auto-apply reference values to the new robot. -- **Translations panel** — display name and description per supported locale for the robot and each named part; pre-populate from the selected reference robot if one is chosen. -- **Save flow** — validate all required fields across robot and parts, then write the full robot definition (robot entity, part entities, slot assignments, stats, translations) in a single transaction; report success or validation errors inline. -- Consider a "Clone from existing robot" shortcut that pre-fills all panels from a chosen robot, covering the common variant/reskin workflow. - -### Notes -Robot definitions span multiple tables — consult `docs/db_structure/` thoroughly before implementation; pay attention to part ownership and slot assignment relationships. -Translation keys for robot and parts must follow existing naming conventions. -The template selection step is critical: slot counts and part types are structurally fixed by the template and must not be violated by subsequent panel edits. -See [[IMPROVEMENT-003]] for the related Item Designer — shared UI patterns (stats panel, translations panel, comparison panel) should be extracted as reusable components. +--- ## IMPROVEMENT-005 - Seasons: Additional Activity Types -Status: TODO +Status: DONE Priority: MEDIUM Area: Seasons / Activities ### Description -Expand the Seasons activity tracking system with new activity types beyond the current set. Candidate types include: production runs, artifacting, module or deployable usage, and island visitation. Each new type should integrate with the existing scoring and point-accumulation pipeline. +Expand the Seasons activity tracking system with 12 new activity types implemented in two phases. All types integrate with the existing `RecordActivity` pipeline with no DB schema changes. -### Impact -A broader set of tracked activities makes seasons more engaging for a wider range of playstyles (industrialists, explorers, etc.), not just combat-focused players. It also provides more levers for season designers to tune the competitive balance of each season's objectives. +### Phase 1 — Non-combat types (enum values 9–13) +- `Prototyping` (9) — hook in ProductionProcessor.cs at job completion, branch on job type +- `ReverseEngineering` (10) — same hook, different job type branch +- `Production` (11) — same hook, combined items + robots +- `ArtifactFound` (12) — hook in ArtifactScanner.cs after EP boost call; amount = 1 +- `EpEarned` (13) — hook all `AddExtensionPointsBoostAndLog` call sites + passive EP accumulation path -### Proposed Implementation -- **Production** — award points when a production job completes; parameterisable by item category, tier, or quantity produced. -- **Artifacting** — award points on successful artifact scan/loot events; parameterisable by artifact tier or island type. -- **Module / Deployable Usage** — award points when a specific module type or deployable is activated/deployed; parameterisable by module category or deployable type. -- **Island Visitation** — award points the first time (or each time, configurable) a character enters a specific island or island category (alpha/beta/gamma) within a season. -- Each new activity type should follow the existing activity handler pattern: a discrete handler class, registration in the activity type registry, and a corresponding `season_activity_types` DB record. -- Point values and activity parameters should remain data-driven (DB/config) rather than hardcoded, consistent with existing activity types. -- Ensure anti-farming guards (cooldowns, per-session caps) can be configured per activity type, consistent with [[IMPROVEMENT-001]] recurring season design. +### Phase 2 — Combat types (enum values 14–20) +- `DamageDone` (14) / `DamageReceived` (15) — hook in TakeDamage/ApplyDamageResult; amount = HP dealt +- `ArmorRestored` (16) — hook repair module application; character = repairer; amount = HP restored +- `EnergyDrainDealt` (17) / `EnergyDrainReceived` (18) — neutralizer + drainer modules; amount = energy removed +- `EnergyTransferDealt` (19) / `EnergyTransferReceived` (20) — transfer module; amount = energy transferred + +### Anti-farming +Handled via `unit_scale` in rates (set high for high-frequency types). Training character filter applies automatically. No new cap infrastructure needed. + +### Spec +`docs/superpowers/specs/2026-05-16-improvement-005-additional-activity-types-design.md` ### Notes -Audit existing activity tracking hooks in the production, scanning, module, and zone subsystems before wiring new event sources — prefer tapping existing domain events over introducing new ones. -Island visitation tracking must be zone-thread-safe; consult zone update loop constraints in `docs/CONCERNS.md`. -Anti-farming considerations are especially important for high-frequency events (module usage, production) — caps must be configurable at the season level. +Distance Travelled was deferred — see [[IMPROVEMENT-015]]. +Verify passive EP accumulation call site (AccountManager.cs or dedicated scheduler) before wiring EpEarned. +Confirm NPCs do not have character IDs that would cause accidental season point accumulation on DamageReceived. + +--- ## IMPROVEMENT-006 - Daily Objectives -Status: TODO +Status: DONE Priority: MEDIUM Area: Seasons / Objectives @@ -136,19 +75,16 @@ Introduce daily objectives: a set of objectives that reset and re-issue automati ### Impact Daily objectives provide a regular engagement loop that encourages players to log in consistently, broadening the appeal of the seasons system beyond one-time or long-horizon goals. -### Proposed Implementation -- Audit existing objective types, completion tracking, and reward pipeline — identify what can be reused verbatim vs. what needs extension. -- Add a `recurrence` flag (or subtype) to the objective definition that marks an objective as daily-recurring. -- Implement a daily reset scheduler: at UTC midnight (or a configurable daily reset time), mark all completed daily objectives as eligible for re-issue and create new completion records for the new day. -- Per-character completion state must be scoped to the current day's window so prior-day completions do not block re-issuance. -- Daily objectives should be configurable per season: which objective types appear, their targets, and their point/reward values. -- Ensure the reset scheduler is idempotent — a server restart mid-day must not re-issue objectives already issued for that day. +### Implementation +Extended `season_objectives` with `is_daily` (bit) and `package_id` (int, nullable). Added `day_window` (date, sentinel `1900-01-01` for regular, `UtcNow.Date` for daily) to `season_objective_progress` and rebuilt its PK to `(character_id, season_id, objective_id, day_window)`. No reset scheduler needed — fresh row per day via existing MERGE. Optional reward package delivered on daily completion via `InsertRedeemableItems`. Admin Tool gains Is Daily checkbox column, Reward Package combobox column, and All/One-time/Daily filter. Branch: `p36.1`. ### Notes Depends on [[ISSUE-001]] — daily reset boundary must use UTC to be consistent across deployments. See [[IMPROVEMENT-005]] for new activity types that could back daily objective targets. See [[IMPROVEMENT-001]] for recurring season design — daily objectives are a finer-grained recurrence within a season. -Reset time should be operator-configurable (default UTC midnight) rather than hardcoded. +Reset time is hardcoded UTC midnight (configurable reset time deferred). + +--- ## IMPROVEMENT-007 - NPC Rank System @@ -173,6 +109,8 @@ Provides a clear, queryable signal for distinguishing NPC threat levels without Keep the rank scale small and stable — it will be referenced by season activity configs and potentially loot rules, so changes after rollout are costly. If season activity types need to filter by NPC rank (see [[IMPROVEMENT-005]]), the rank value must be accessible at the point where kill events are emitted. +--- + ## IMPROVEMENT-008 - NPC Role System Status: TODO @@ -198,11 +136,14 @@ Role and rank (see [[IMPROVEMENT-007]]) are complementary attributes — impleme If season activity types need to filter by NPC role (see [[IMPROVEMENT-005]]), role must be accessible at the point where kill events are emitted. Keep the initial role set conservative; adding roles later is cheaper than changing existing ones after downstream systems reference them. +--- + ## IMPROVEMENT-009 - Targeted Objectives -Status: TODO +Status: DONE Priority: LOW Area: Seasons / Objectives +Spec: `docs/superpowers/specs/2026-05-19-improvement-009-targeted-objectives-design.md` ### Description Extend the objective system to support targeted objectives, where a specific subject must be matched for progress to count. The target is activity-type-dependent — for example, a mining objective can target a specific ore type ("Mine 100 000 Colixium"), a kill objective can target an NPC role ("Kill 50 Combat NPCs") or rank, a production objective can target an item category, and so on. @@ -228,6 +169,34 @@ Depends on [[IMPROVEMENT-007]] and [[IMPROVEMENT-008]] for NPC rank/role filteri Target filter should be stored as structured data (e.g. JSON column or normalised filter table) rather than freeform strings to allow reliable matching and Admin Tool rendering. Keep the filter evaluation path lightweight — it runs on every matching game event and must not introduce blocking or excessive allocation in hot paths. +--- + +## IMPROVEMENT-012 - Seasons Tiers tab: on-the-fly save generating a single change script + +Status: DONE +Priority: HIGH +Area: Seasons / Admin Tool +Spec: `docs/superpowers/specs/2026-05-16-improvement-012-tiers-tab-queue-save-design.md` + +### Description +The Tiers tab in the Seasons Admin Tool currently uses a different save mechanic from the Activity Rates and Objectives tabs. Activity Rates and Objectives already support on-the-fly editing that produces a single consolidated change script per save. The Tiers tab should adopt the same pattern so all three tabs behave consistently. + +### Impact +Inconsistent save mechanics increase operator confusion and risk: a different save flow for Tiers may require multiple manual steps or produce partial scripts, making season adjustments error-prone and harder to audit compared to the Activity Rates / Objectives workflow. + +### Proposed Implementation +- Audit how Activity Rates and Objectives generate their single change script on save — identify the shared pattern (diff computation, script generation, transaction wrapper). +- Refactor or extend that pattern to cover tier definitions (name, point threshold, reward). +- Tiers tab save flow: compute a diff between the current persisted tier state and the edited in-memory state, then emit a single SQL/migration script covering all inserts, updates, and deletes in one transaction. +- The generated script should follow the same format and conventions as those produced by Activity Rates and Objectives saves, so all three can be reviewed and applied uniformly. +- Ensure that editing tiers, activity rates, and objectives in the same session and saving each produces independently coherent scripts — no cross-tab state leakage. + +### Notes +See [[IMPROVEMENT-010]] — the Scoring Balancing tab depends on tiers being editable inline; consistent save mechanics here unblock a clean implementation of that tab. +Preserve existing tier DB schema — this improvement changes the save UI mechanic only, not the underlying data model. + +--- + ## IMPROVEMENT-010 - Seasons Scoring Balancing Tab Status: TODO @@ -253,3 +222,362 @@ The activities-to-objective computation is a display convenience; the authoritat Depends on [[IMPROVEMENT-005]] for the full set of activity types surfaced in the rates panel. Depends on [[IMPROVEMENT-009]] for targeted objectives appearing in the objectives panel with their filter displayed. Edits made here must write through the same save paths used by the individual objective and activity rate editors — no parallel write logic. + +--- + +## IMPROVEMENT-013 - Daily objectives grant their own reward packages on completion + +Status: TODO +Priority: MEDIUM +Area: Seasons / Objectives + +### Description +When a player completes a daily objective they should receive a dedicated reward package, separate from and in addition to any season point accumulation. Each daily objective should have a configurable reward package (items, NIC, or other reward types) that is granted immediately on completion. + +### Impact +Without per-completion rewards, daily objectives only contribute points toward season tiers — offering no immediate gratification. Instant reward packages make daily objectives more compelling, encourage consistent daily engagement, and allow designers to tune short-term incentives independently of long-term tier progression. + +### Proposed Implementation +- Extend the daily objective definition to include an optional `reward_package_id` (or equivalent structured reward payload) specifying what is granted on completion. +- On objective completion, trigger the reward grant pipeline with the associated package — reuse the existing reward distribution mechanism (used for season tier rewards or similar) rather than introducing a new path. +- Reward packages should be configurable per objective and per season; different daily objectives within the same season may grant different packages. +- If an objective has no reward package configured, completion behaves as today (points only) — no breaking change to existing objectives. +- Admin Tool: surface the reward package field in the daily objective editor. + +### Notes +Depends on [[IMPROVEMENT-006]] — daily objectives infrastructure must exist before per-completion rewards can be wired in. +Reward packages must be granted exactly once per completion per character per day — idempotency is critical given the daily reset cycle. +Consult the existing tier reward grant path for the reward package schema and delivery mechanism before designing the new hook. + +--- + +## IMPROVEMENT-014 - Standalone daily objectives/missions outside of Seasons + +Status: TODO +Priority: LOW +Area: Objectives / Missions + +### Description +Introduce a daily objective (or daily mission) system that operates independently of the Seasons system. These objectives generate no season points and have no season dependency — they simply reset daily and grant reward packages on completion, available to all players at all times regardless of whether a season is active. + +### Impact +Season-tied daily objectives are only meaningful during an active season, leaving a gap in daily engagement loops during off-season periods. A standalone daily objective system provides consistent daily incentives year-round, retains player engagement between seasons, and caters to players who are not focused on competitive season rankings. + +### Proposed Implementation +- Design the standalone daily objective system as a distinct subsystem from Seasons — it should not depend on a season being active, should not write to season activity or point tables, and should have its own objective definitions, completion tracking, and daily reset scheduling. +- Reuse the daily reset scheduler and objective completion/reward grant mechanisms from [[IMPROVEMENT-006]] and [[IMPROVEMENT-013]] where possible — extract shared infrastructure rather than duplicating it. +- Objective definitions: activity type, target filter (optional, see [[IMPROVEMENT-009]] patterns), completion threshold, reward package. +- Completion tracking: per-character, scoped to the current day's reset window; idempotent reset at UTC midnight (or configurable reset time). +- Reward grant: on completion, deliver the configured reward package via the existing reward distribution path — no points emitted. +- Admin Tool: a dedicated section for managing standalone daily objective templates (create, edit, enable/disable, assign reward packages); separate from the Seasons objective editor. + +### Notes +The absence of point generation is intentional and must be enforced — these objectives must not accidentally write to any season scoring table. +If the daily reset infrastructure from [[IMPROVEMENT-006]] is not yet built, this system should share that implementation rather than introducing a parallel reset scheduler. +Consider whether standalone daily objectives should be visible in the same in-game UI as season daily objectives, or in a separate panel — a clear UX distinction prevents player confusion about what generates season points. + +--- + +## IMPROVEMENT-015 - Seasons: Distance Travelled Activity Type + +Status: TODO +Priority: LOW +Area: Seasons / Activities + +### Problem +Distance travelled was scoped out of [[IMPROVEMENT-005]] due to zone-thread-safety concerns. There is no existing hook point for movement/distance metrics in the zone update loop, and per-movement-event `RecordActivity` calls would be too frequent. + +### Impact +Without this type, season designers cannot reward exploration or movement-intensive playstyles. It is a lower-priority gap since the 12 types from IMPROVEMENT-005 already cover most playstyle categories. + +### Proposed Fix +- Instrument the zone movement system to accumulate distance per character over a configurable tick interval (e.g. every 5 seconds) +- At the end of each interval, emit a single `RecordActivity(characterId, DistanceTravelled, accumulatedDistance)` call +- The accumulator must be zone-thread-safe — stored per-unit alongside other movement state, written only from the zone update loop +- Amount unit: metres (or internal distance units); `unit_scale` in rates handles point conversion + +### Notes +Accumulation interval should be configurable to avoid excessive DB writes in high-population zones. +Must not introduce blocking or allocation in the hot movement path — accumulate, don't write inline. +Consult `docs/CONCERNS.md` zone update loop constraints before implementation. + +--- + +## IMPROVEMENT-016 - Admin Tool: ChangeQueue deduplication + +Status: TODO +Priority: LOW +Area: Admin Tool / Editing + +### Description +The `ChangeQueue` does not deduplicate queued changes. If the user clicks "Queue Save" on the same row multiple times, multiple SQL statements for the same entity accumulate in the script. The last write wins at commit time, so correctness is preserved, but the script is noisier than necessary and harder to audit. + +### Impact +Low. The issue only manifests if a user repeatedly clicks "Queue Save" on the same row within a session. Scripts remain correct; they are just verbose. Affects all tabs that use "Queue Save": Activity Rates, Objectives, Tiers (after IMPROVEMENT-012). + +### Proposed Fix +- Give each queued change a stable key composed of table + primary key (e.g. `"season_tiers:{seasonId}:{tierId}"`). +- When a change with the same key is added, replace the existing entry rather than appending. +- Keep the existing `ObservableCollection` as the backing store; deduplicate on `Add`. +- Update `IPendingChange` with an optional `Key` property; `RawSqlChange` exposes it; `ChangeQueue.Add` checks for collision. + +### Notes +Depends on [[IMPROVEMENT-012]] being complete — Tiers tab must use the queue before deduplication applies to it. +Key must be stable across multiple `Queue Save` clicks on the same row, not a generated GUID. +Destructive changes (DELETE) should also replace any prior non-destructive change for the same key. + +--- + +## IMPROVEMENT-017 - New Item script filename includes definition name + +Status: DONE +Priority: LOW +Area: Admin Tool / New Item Dialog +Spec: `docs/superpowers/specs/2026-05-18-improvement-017-script-filename-prefixes-design.md` + +### Description +When saving a new item in SqlScript mode, the output `.sql` file is named +`admintool__