feat(isac): link skill chips to abilities

This commit is contained in:
JB
2026-07-26 17:04:47 -04:00
parent 6722665f5c
commit b1656c430f
19 changed files with 681 additions and 424 deletions
+40 -44
View File
@@ -1,9 +1,9 @@
# Container, staging, and production deployment
SHD.build is packaged as a Node 24 container using the official SvelteKit Node
adapter. The image contains application code, migration and notification
scripts, and native runtime dependencies. It does not contain an environment
file, database, backups, or user uploads.
SHD.build runs in a Node 24 container through the official SvelteKit Node
adapter. The image includes application code, migration and notification
scripts, and native runtime dependencies. Environment files, databases,
backups, and user uploads remain outside the image.
## Persistent data
@@ -15,10 +15,9 @@ mounted at `/app/data`. It contains:
- normalized user avatars under `avatars/`;
- uploaded game-data icons under `game-icons/`.
Never remove this volume during routine deployments. `docker compose down`
without `--volumes` preserves it. Before any manual backup or restore, stop
both the `app` and `scheduler` services because both access the embedded
database.
Keep this volume during routine deployments. `docker compose down` without
`--volumes` preserves it. Before a manual backup or restore, stop the `app`
and `scheduler` services because both access the embedded database.
## Staging environment
@@ -46,9 +45,9 @@ port on the host interfaces so a containerized reverse proxy or tunnel can
reach it; firewall the port to trusted LAN/tunnel sources when the host is
reachable from other networks.
The deployment decrypts `.env.staging` on the trusted staging runner into a
mode-`0600` runtime file. The decryption key is held by Gitea Actions, not
committed or included in the container image.
The trusted staging runner decrypts `.env.staging` into a mode-`0600` runtime
file. Gitea Actions holds the decryption key; Git and the container image do
not.
The staging container disables default-user seeding. On a brand-new staging
database, the existing bootstrap policy grants Admin to the first real account
@@ -93,9 +92,9 @@ In `https://git.jb.codes/jb/shd.build/settings/actions`:
3. Optionally add repository variable `STAGE_PORT`. It defaults to `5569`.
4. Ensure Actions are enabled and the `stage-deploy` runner is online.
The workflow at `.gitea/workflows/deploy-staging.yml` runs on every push to
`main` and can also be started manually. It does not need registry credentials
because the trusted staging runner builds a commit-tagged image locally.
`.gitea/workflows/deploy-staging.yml` runs for pushes to `main` and manual
dispatches. The trusted staging runner builds each commit-tagged image locally,
so the workflow needs no registry credentials.
## Deployment lifecycle
@@ -110,8 +109,7 @@ The workflow:
7. waits for `/api/health/database` to report healthy;
8. retains the logs and exits unsuccessfully if health verification fails.
The short outage is limited to backup, migration, and container startup. This
follows the database-preservation policy while minimizing data-entry downtime.
The outage covers backup, migration, and container startup.
The scheduler calls a bearer-authenticated endpoint on the application service
once per minute. Scheduled message delivery therefore uses the web process's
existing embedded-database connection instead of opening the locked database
@@ -120,11 +118,10 @@ consolidation, and email digest checks and is not published through a host port.
## Initial development-data bootstrap
The first staging release can be initialized from the current development
database, including users, roles, builds, guides, proposals, audit history,
avatars, and uploaded game-data icons. This is deliberately a one-time flow:
the staging importer refuses to run after users, builds, audits, or proposals
exist in its target database.
Initialize the first staging release from the development database, including
users, roles, builds, guides, proposals, audit history, avatars, and uploaded
game-data icons. The staging importer refuses a target database that contains
users, builds, audits, or proposals.
First stop this project's development server. The embedded database must not be
open while its complete transfer bundle is created:
@@ -134,8 +131,8 @@ npm run stage:bootstrap:export
```
The command writes a timestamped, ignored JSON bundle under `data/backups/`.
It contains user records and password hashes, so transfer it over an encrypted
channel and never add it to Git. Copy it to the staging host, then run:
The bundle includes user records and password hashes. Transfer it over an
encrypted channel, keep it out of Git, then copy it to the staging host and run:
```sh
/opt/shd-build-stage/bootstrap-staging.sh \
@@ -160,8 +157,8 @@ Actions. It briefly stops staging database writers, exports only canonical
builder-source tables plus uploaded game-data icons, restarts staging, and
publishes a `shd-stage-isac-data` artifact for 14 days.
Download `isac-stage.json` from that workflow run. Then stop this project's
development server and run:
Download `isac-stage.json` from that workflow run, stop this project's
development server, and run:
```sh
npm run isac:import -- /path/to/isac-stage.json --confirm-replace-isac-data
@@ -206,10 +203,10 @@ environment, port, and deployment workflow:
- Database: `/app/data/shd-build-production.db`
- Persistent volume: `shd-build-production-data`
Routine pushes never import or replace production data. The production deploy
stops only its application and scheduler, creates a timestamped JSON backup,
applies repeatable migrations, restarts, and verifies database health. Never
run `docker compose down --volumes` against production.
Routine pushes leave production data unchanged. The production deploy stops its
application and scheduler, creates a timestamped JSON backup, applies repeatable
migrations, restarts, and verifies database health. Keep
`docker compose down --volumes` away from production.
Add these repository Action secrets:
@@ -234,8 +231,8 @@ reuse the staging scheduler token.
### Initial production bootstrap
The one-time production bootstrap clones current development data and uploads.
Stop this project's development server before creating the bundle:
The production bootstrap clones current development data and uploads. Stop this
project's development server before creating the bundle:
```sh
npm run production:bootstrap:export
@@ -269,9 +266,8 @@ notifications, settings, and audit history untouched.
Manually run **Replace staging ISAC data from production** in Gitea Actions.
The workflow exports a consistent production snapshot, restarts production,
then stops staging and imports it. The staging importer creates both full and
ISAC-only safety backups before replacement. This is intentionally manual and
never runs as part of either deployment.
then stops staging and imports it. The staging importer creates full and
ISAC-only safety backups before replacement. Deployments do not run this flow.
### Promote managed images without replacing other data
@@ -280,9 +276,9 @@ development. Export a bundle after finishing local uploads and metadata edits:
Set the server-only `REMOVE_BG_API_KEY` in each encrypted environment that
accepts ISAC uploads. It is sent only from the server in the `X-Api-Key` header.
Icon uploads that already contain transparency do not call remove.bg; opaque
icon sources use its preview output and then continue through local trimming,
white normalization, WebP conversion, and storage.
Transparent icon uploads skip remove.bg. Opaque sources use its preview output,
then pass through local trimming, white normalization, WebP conversion, and
storage.
```sh
npm run images:export
@@ -311,13 +307,13 @@ docker compose -f compose.production.yml run --rm \
docker compose -f compose.production.yml up -d app scheduler
```
Each import creates a full target database backup before changing anything.
New stable IDs are inserted, identical existing assets are skipped, and a
different existing record blocks the import. After reviewing an intentional
metadata or file replacement, add `--update-existing`. Imported changes append
`image.imported` or `image.import_updated` audit events. Remove the transferred
bundle after both environments have been verified because it may contain
unpublished visual assets and creator identifiers.
Each import creates a full target database backup before changes. The importer
adds new stable IDs, skips identical assets, and blocks conflicting records.
After reviewing an intentional metadata or file replacement, add
`--update-existing`. Imported changes append `image.imported` or
`image.import_updated` audit events. Remove the transferred bundle after
verifying both environments because it may contain unpublished visual assets
and creator identifiers.
The `.env.staging` and `.env.production` npm import commands are intended for
maintenance performed directly in an environment where those database and
+92 -92
View File
@@ -2,14 +2,13 @@
This document defines the visual language for SHD.build, the independent
Division Resurgence community for builds, guides, game data, news, and
discussion. New pages should feel like part of the same tactical intelligence
network as the admin dashboard, while public pages may use more space and larger
editorial typography.
discussion. New pages use the same tactical intelligence network as the admin
dashboard. Public pages may use more space and larger editorial typography.
## Design principles
1. **Tactical, not military cosplay.** Use operational language, compact information density, restrained status indicators, and precise alignment. Avoid camouflage, distressed textures, or decorative weapon imagery in utility interfaces.
2. **Data first.** Hierarchy comes from grouping, typography, borders, and spacing. Admin tools should resemble a dependable control surface rather than a collection of marketing cards.
1. **Tactical.** Use operational language, compact information density, restrained status indicators, and precise alignment. Avoid camouflage, distressed textures, and decorative weapon imagery in utility interfaces.
2. **Data first.** Grouping, typography, borders, and spacing establish hierarchy. Admin tools use a dependable control surface.
3. **Orange means action.** SHD orange identifies primary actions, active navigation, focus, and important values. It should not cover large areas.
4. **State is always visible.** Loading, disabled, active, archived, configured, error, and success states need both visual and textual indicators.
5. **Square and engineered.** Panels and controls use square corners, fine borders, tight spacing, and deliberate grid alignment.
@@ -20,42 +19,41 @@ editorial typography.
### Routed error console
Expected HTTP errors and unexpected server faults use one shared SHD exception
console inside the normal application shell. The console always displays the
console inside the application shell. The console displays the
numeric status, an operational title, a safe explanation, request path, node,
and context-appropriate recovery controls. Common statuses receive distinct
language: authentication for 401, clearance for 403, lost location for 404,
conflict for 409, saturation for 429, and service/fault language for 5xx.
Orange identifies recoverable request errors and actions; red identifies server
faults. Never display stack traces, database messages, credentials, request
queries, or raw unexpected exceptions to users. Unexpected server faults receive
a short reference ID from the global error handler for support correlation while
the complete error remains server-side. If the root application shell itself
cannot render, `src/error.html` provides a dependency-free SHD fallback surface.
faults. Do not display stack traces, database messages, credentials, request
queries, or raw unexpected exceptions. The global error handler gives each
unexpected server fault a short support reference ID and keeps the full error
server-side. If the root application shell cannot render, `src/error.html`
provides a dependency-free SHD fallback surface.
Error controls retain square geometry, focus-visible treatment, pressed motion,
plain-language labels, and stacked mobile recovery actions.
### Contact relay and legal requests
Public support uses the SHD Contact Relay surface: a broad transmission form
paired with a compact destination guide. Two-to-six destinations use the shared
segmented-choice pattern. Signed-in identity details may be prefilled, but the
public form remains available to people who cannot sign in. Discord community
access is a distinct provider-colored card; it supplements rather than replaces
the durable contact channel.
Public support uses the SHD Contact Relay: a broad transmission form and a
compact destination guide. Two-to-six destinations use the shared
segmented-choice pattern. Signed-in identity details may be prefilled; people
who cannot sign in retain access to the public form. Discord community access
uses a distinct provider-colored card alongside the durable contact channel.
The Contact hero explicitly resets the global site-header height: semantic page
`header` elements must use content-driven height and spacing rather than inherit
the 76-pixel navigation-shell constraint. Its title, description, and Discord
The Contact hero resets the global site-header height: semantic page `header`
elements use content-driven height and spacing instead of the 76-pixel
navigation-shell constraint. Its title, description, and Discord
card align within a minimum 230-pixel desktop band before the form grid begins.
The Contact page itself remains content-height-driven so high-resolution browser
zoom does not multiply a viewport minimum into a large empty region above the
footer.
The application frame is a full-viewport vertical flex stack. The shared footer
uses automatic top margin, so it rests against the bottom edge on short routes
and follows normal document flow after longer content. Individual pages must
not add spacer heights solely to position the footer. The frame compensates for
The application frame uses a full-viewport vertical flex stack. The shared
footer uses automatic top margin. It rests against the bottom edge on short
routes and follows document flow after longer content. Do not add spacer heights
only to position the footer. The frame compensates for
`--shd-display-scale` when calculating its viewport minimum so high-resolution
browser zoom does not push a short-page footer below the visible edge.
@@ -73,8 +71,8 @@ color the complete product name orange.
### Community platform positioning
SHD.build is a full Division Resurgence community, not solely a loadout tool.
Primary product language and global navigation must represent five durable
SHD.build is a Division Resurgence community. Primary product language and
global navigation represent five durable
channels: News, Database, Builds, Guides, and Builder. Loadout-specific terms
remain appropriate inside build routes, but the global brand descriptor,
homepage metadata, hero, sign-in introduction, and footer describe the broader
@@ -211,12 +209,10 @@ The canonical tokens live in `src/app.css` under `:root`.
### High-resolution display scaling
The application uses restrained, stepped interface scaling when the effective
CSS viewport is substantially wider than a conventional desktop. At 2200px the
complete interface scales to 115%, at 2800px to 130%, and at 3400px to 150%.
This preserves the established relationship between typography, controls,
tables, spacing, hit targets, and fixed-position surfaces instead of enlarging
text alone.
The application uses restrained stepped interface scaling on wide CSS
viewports. At 2200px, the interface scales to 115%; at 2800px, to 130%; and at
3400px, to 150%. This maintains the established relationship among typography,
controls, tables, spacing, hit targets, and fixed-position surfaces.
Use the shared `--shd-display-scale` token and the root-level `zoom` treatment;
do not add module-specific 4K font-size overrides. Responsive layouts below
@@ -233,7 +229,7 @@ Use the spacing tokens for new components. Prefer `8px`, `12px`, and `20px` gaps
## Typography
SHD.build self-hosts two intentional type voices through Fontsource packages:
SHD.build self-hosts two type voices through Fontsource packages:
- **Barlow** is the operational family for display headings, navigation,
controls, data, labels, metadata, and general interface copy. Use the shared
@@ -243,10 +239,9 @@ SHD.build self-hosts two intentional type voices through Fontsource packages:
`--shd-font-editorial`; do not apply it to controls, tables, metrics, or
telemetry labels.
Only the used Barlow weights (400, 600, 700, 800, and 900) and Source Serif 4
weights (400 and 600) are loaded. Both families are self-hosted, preserve
fallback stacks, and render immediately without a third-party font request.
The two-voice rule remains semantic: Barlow operates the system; Source Serif 4
Load Barlow weights 400, 600, 700, 800, and 900, plus Source Serif 4 weights
400 and 600. Both families are self-hosted, keep fallback stacks, and render
without a third-party font request. Barlow operates the system; Source Serif 4
explains it.
- Page and module headings are uppercase, heavy, and tightly spaced.
@@ -265,7 +260,7 @@ explains it.
### Global mobile navigation
At viewports up to 900px, replace the hidden desktop link rail and account
controls with a clearly labeled square `MENU` control in the shared site
controls with a labeled square `MENU` control in the shared site
header. Activating it opens a right-side SHD navigation panel over a dismissible
backdrop. The panel includes Home, Discover, Builder, Guides, Sign In or My
Profile, and ISAC when authorized; authenticated agents also receive identity
@@ -289,7 +284,7 @@ squeezed beside long module headings on mobile.
### Public pages
- Maximum content width: approximately 1400–1640px.
- Larger headings and more generous vertical rhythm are appropriate.
- Public pages may use larger headings and more generous vertical rhythm.
- Preserve the grid background and orange accents.
- Catalogue and editorial pages use `catalogue-hero` for an angular introductory
panel and may pair it with a compact operational control bar.
@@ -377,7 +372,8 @@ squeezed beside long module headings on mobile.
white Apple mark on black, Discord uses its unmodified white symbol on
Blurple, and Steam uses its white linked-circle mark on Steam navy.
- Providers that are not configured are disabled, desaturated, and marked
`PLANNED`; they must not navigate, submit, or visually imply availability.
`PLANNED`; they must not navigate, submit, or imply availability through
styling.
- When each provider is implemented, replace its placeholder with the
provider-rendered control or current pre-approved sign-in artwork where the
provider requires it. In particular, use Google Identity Services or its
@@ -560,18 +556,17 @@ Do not communicate state by color alone; include a label, icon, or pattern.
5. Use the shared tokens instead of introducing near-duplicate colors.
6. Verify keyboard focus, pending behavior, empty states, mobile layout, and archived data behavior.
If a new interface needs a genuinely new pattern, document it here when introducing it.
Document a new interface pattern here when you introduce it.
## Design-system governance
Significant visual and interaction changes must update this document and the
shared implementation in the same change. A change is significant when it
introduces or materially revises a reusable token, component pattern, layout,
responsive rule, accessibility behavior, motion behavior, or state convention.
Update this document and the shared implementation with each significant visual
or interaction change. A change qualifies when it introduces or revises a
reusable token, component pattern, layout, responsive rule, accessibility
behavior, motion behavior, or state convention.
Small alignment corrections and isolated visual bug fixes do not require a
documentation change unless they establish a rule that other interfaces should
follow.
Small alignment corrections and isolated visual bug fixes need no documentation
change unless they establish a reusable rule.
Repository agents and contributors should follow the maintenance checklist in
`AGENTS.md`.
@@ -753,7 +748,7 @@ numbers.
When only one page exists, omit page navigation and row-count presets; retain
only the useful result range and any registry search. Do not render disabled
pagination controls simply to preserve the rail's shape.
pagination controls to preserve the rail's shape.
Registries with a meaningful name, title, or agent-identity column place the
shared server-side search inside this pagination rail. Results update after a
@@ -787,13 +782,13 @@ bookmarkable, and expose the selected state with both orange emphasis and
textually identifiable active states. Every card explicitly says `FILTER →`
or `SELECTED`; do not rely on KPI-card styling alone to communicate that the
metrics are controls. Counts describe the full search scope,
not merely the visible page. Do not use aggregate facts that cannot filter the
affected registry—replace them with actionable subsets.
not merely the visible page. Replace aggregate facts that cannot filter the
affected registry with actionable subsets.
### Registry row interaction
Desktop registry rows are passive data containers unless the entire row
actually opens a detail view. Do not apply a whole-row hover surface when only
Desktop registry rows are passive data containers unless the entire row opens
a detail view. Do not apply a whole-row hover surface when only
the trailing Edit or View action is interactive. Render that action as a
compact bordered control with hover, focus-visible, and pressed states so its
interaction boundary is unmistakable.
@@ -812,7 +807,7 @@ and normalization checks remain authoritative.
### Moderated data states
ISAC must clearly distinguish direct editing, contributor proposals, and read-only
ISAC must distinguish direct editing, contributor proposals, and read-only
access. Use a compact access banner above the module rather than changing the
registry layout. Pending proposal counts appear as orange numeric indicators in
data-navigation items. Proposal lifecycle states use text as well as color:
@@ -1077,7 +1072,7 @@ copy; do not apply the system-transmission frame to agent comments.
Inbox entries expose two distinct actions. `VIEW DETAILS` opens the complete
message when preview copy is truncated to two lines. A separate bordered orange
response action navigates directly to the known workflow and uses specific
response action opens the known workflow and uses specific
language such as `UPLOAD NEW AVATAR`, `REVISE CONTRIBUTION`, or
`VIEW BUILD & REPLY`; never substitute a generic destination label when the
notification type is known. The same response label appears in the detail
@@ -1119,20 +1114,18 @@ unfocusable and unchanged.
may omit visible text when a canonical, unambiguous game icon exists; they must
retain an accessible label and hover title. Text-only segments may omit the
default square indicator when the selected background, orange text, and lower
rail already communicate state clearly. Do not remove both visible text and an
rail communicate state. Do not remove both visible text and an
icon from the same option.
### Skill Chip data console
Skill Chip editing follows the dense ISAC staged-definition pattern: identity
and icon first, the 2-piece numeric bonus second, mutually exclusive primary
and secondary roll pools next, and the 3-piece talent last. Superior blue and
High-end yellow communicate the only supported qualities; orange remains
reserved for actions and current interactive state. A four-step segmented
preview exposes the linear zero-to-three High-end interpolation without storing
duplicate values. At narrow widths the roll pools and talent columns stack,
preview segments become a two-by-two grid, and interactive targets remain at
least 44 pixels tall.
Skill Chip editing uses the dense ISAC staged-definition pattern: identity and
icon, 2-piece numeric bonus, mutually exclusive primary and secondary roll
pools, then 3-piece talent. Superior blue and High-end yellow identify the
supported qualities; reserve orange for actions and the current interactive
state. A four-step segmented preview shows the linear zero-to-three High-end
interpolation without duplicate values. At narrow widths, stack roll-pool and
talent columns, use a two-by-two preview grid, and retain 44-pixel targets.
### ISAC record identity controls
@@ -1145,12 +1138,12 @@ drop-zone to fill spare identity-row space.
Icon-enabled consecutive-entry editors pass a successful-add result token into
the shared upload control. Advancing that token clears the native file input,
temporary preview URL, filename, drag/paste feedback, and validation state.
This reset occurs only after a validated add succeeds; failed saves preserve
the selected image, and successful edits close the editor normally.
Reset only after a validated add succeeds. Failed saves preserve the selected
image; successful edits close the editor.
The primary `name` field in every ISAC data editor is a header-level input:
46 pixels tall with larger, heavier text. This creates a consistent first
reading point for long registries while keeping secondary metadata compact.
The primary `name` field in each ISAC data editor is a 46-pixel header-level
input with larger, heavier text. It gives long registries a consistent first
reading point while keeping secondary metadata compact.
`SearchableAssignment` is the standard relationship picker for long attribute,
talent, trigger, and compatibility lists. Multiple mode manages additive sets;
@@ -1621,9 +1614,9 @@ second. Use a compact angular command band rather than a cinematic display
hero: keep the archive identity, state how many registries are online, state the
record total, and present contribution as a bordered secondary action.
Place one discovery bar directly below the command band. Its label presents
browsing and global search as equal paths—select a registry or search all
data—and its search field searches across all public registry names and useful
Place one discovery bar below the command band. Its label gives users two equal
paths: select a registry or search all data. Its search field searches across
all public registry names and useful
descriptive data such as Talent text, Damage Types, item slots, qualities, and
Brand bonus Attributes. Do not stack a separate registry-heading bar above or
below it. Results name both the matching record and its registry, then link into
@@ -1640,12 +1633,11 @@ workflow-centric states are not exposed as primary public labels.
### ISAC progression-tree composer
Use the Specialization workbench pattern for ordered game systems whose
meaning depends on both hierarchy and spatial placement. Keep the registry,
selected parent identity, child navigation, readiness telemetry, and visual
tree in one operational surface. Editors still open above the registry and
follow the standard ISAC lifecycle; the workbench is navigation and context,
not a replacement modal.
Use the Specialization workbench pattern for ordered game systems that depend
on hierarchy and spatial placement. Keep the registry, selected parent identity,
child navigation, readiness telemetry, and visual tree in one operational
surface. Editors open above the registry and follow the standard ISAC lifecycle.
Use the workbench for navigation and context.
The tree uses a stable 12-column track per row. Row and column are explicit
numeric fields so placement is keyboard operable, auditable, and portable;
@@ -1662,20 +1654,19 @@ the orange identity rail plus the explicit `1 / 1 · FREE` state; pending,
archived, incomplete, and builder-ready states must remain textual as well as
colored.
Treat allocation scope as first-class telemetry. Specialization progression is
unlocked once for a specialization, but every focus receives the full point
allocation independently. Show both the point total and that scope near the
tree; do not imply that focuses draw from one shared spend pool. A focus is
builder-ready only after it has three distinct active abilities, contiguous
tree rows, at least one paid node, and exactly the configured available point
total.
Show allocation scope as first-class telemetry. A specialization unlocks once;
each focus receives its own full point allocation. Show the point total and
scope near the tree. A focus becomes builder-ready after it has three distinct
active abilities, each ability has exactly three active Skill Chip definitions,
tree rows are contiguous, at least one paid node exists, and the tree uses the
configured available point total.
Compose focus identity from the uploaded specialization artwork and the
canonical Firepower, Toughness, or Engineering icon. Keep the specialization
art primary and the attribute mark as a smaller corner badge. Ability slots 1
and 3 are mirrored as free tree nodes; slot 2 is labeled Combat Skills-only and
must not appear in the upgrade tree. Universal grenade and medical-kit actions
are not specialization abilities.
Compose focus identity from uploaded specialization artwork and the canonical
Firepower, Toughness, or Engineering icon. Keep the specialization art primary
and place the attribute mark in a smaller corner badge. Mirror ability slots 1
and 3 as free tree nodes. Label slot 2 Combat Skills-only and exclude it from
the upgrade tree. Universal grenade and medical-kit actions are outside the
specialization ability set.
Use `StructuredEffectEditor` whenever an ISAC record needs an ordered set of
calculation-ready effects. It provides Attribute, Ability when applicable, and
@@ -1686,6 +1677,15 @@ do not yet support. Oversight previews must render effect and rank summaries as
structured cards. Serialized JSON belongs only in the expandable raw-evidence
panel.
Exclusive child-record relationships use a searchable assignment block directly
after the record identity. The header names the parent type, states the
relationship and capacity in plain language, and shows current usage as `N / N`.
The registry repeats the assigned parent beside each child so exclusivity can be
audited without opening the editor. Legacy records may remain visibly
`UNASSIGNED`, but creation, restoration, and successful edits require a valid
active parent. Capacity rules are enforced on the server and never communicated
by color alone.
Rank cards present the complete rank description, structured effects, point
cost, Credits, and material cost together. Specialized Alpha, Beta, and Gamma
components use the shared blue quality treatment and may be combined in any
+32 -36
View File
@@ -1,25 +1,23 @@
# Weapon and gear stat level scaling
This document records the empirically observed rules for adjusting weapon and
gear stat ranges by item level. The rules apply independently to both endpoints
of a configured range.
Use these observed rules to adjust weapon and gear stat ranges by item level.
Apply each rule to both endpoints of a configured range.
These formulas are based on in-game observations rather than published game
mechanics. Preserve source observations when adding new samples and revise the
constants if a larger data set disproves them.
In-game observations, rather than published game mechanics, support these
formulas. Preserve source observations with new samples and revise constants
when a larger data set disproves them.
## Numeric representation and display
Stat ranges must retain their unrounded decimal values. The integer shown by the
game is consistent with truncation:
Store stat ranges as unrounded decimals. The game display truncates them:
```text
displayedValue = floor(rawValue)
```
Do not use `ceil`, and do not repeatedly level an already displayed integer.
Both approaches accumulate errors. A level-1 value displayed as `0` can still
have a positive fractional raw value that becomes visible at a higher level.
Avoid `ceil` and repeated leveling of displayed integers. Both accumulate
errors. A level-1 display of `0` can hide a positive fractional raw value that
appears at a higher level.
For example, a standard gear item displayed `0 ENG` at level 1 and `2 ENG` at
level 42. With a level-42 multiplier of `3.01`, a raw base near `0.7` explains
@@ -30,12 +28,12 @@ floor(0.7) = 0
floor(0.7 × 3.01) = 2
```
When only displayed integers are available, a prediction can be off by one.
That is expected information loss, not evidence for rounding upward.
Predictions from displayed integers can miss by one because truncation loses
information. That error does not indicate rounding upward.
## Shared progression model
The stable portion of both progressions is linear, not compounded:
The stable portion of both progressions is linear:
```text
rawStat(level) = hiddenBase × multiplier(level)
@@ -48,8 +46,8 @@ fixed `5%` of the hidden base:
rawStat(level + 1) - rawStat(level) = hiddenBase × 0.05
```
Never calculate a new level as `previousDisplayedValue × 1.05`; that describes
compound growth and does not match the observations.
Do not calculate a new level as `previousDisplayedValue × 1.05`. That compound
growth model conflicts with the observations.
For a range `[minimum, maximum]`, calculate each endpoint separately:
@@ -58,7 +56,7 @@ scaledMinimum = hiddenMinimum × multiplier(level)
scaledMaximum = hiddenMaximum × multiplier(level)
```
Only truncate when producing a game-matching display value.
Truncate at the game-display boundary.
## Gear
@@ -68,9 +66,9 @@ The observed stable gear multiplier is:
gearMultiplier(level) = 0.91 + 0.05 × level
```
This rule is validated for levels 10 through 50 across Standard, Specialized,
and High-end samples. Quality changes the hidden stat ranges; the observed
late-level multiplier does not change with quality.
Samples across Standard, Specialized, and High-end qualities validate this rule
for levels 10 through 50. Quality changes hidden stat ranges; the observed
late-level multiplier remains the same.
Given hidden level-independent endpoints:
@@ -115,11 +113,10 @@ ENG = floor(0.7 × 3.01) = 2
### Early gear levels
Levels 1 through 9 show a quality-dependent transition and do not follow the
stable multiplier directly. Do not extrapolate that transition from a single
level-1-to-level-2 change. Until the early curves are sampled more completely,
store authoritative ranges for those levels or mark calculated results as
estimates.
Levels 1 through 9 show a quality-dependent transition outside the stable
multiplier. Do not extrapolate it from one level-1-to-level-2 change. Until
more samples define the early curves, store authoritative ranges or label
calculated results as estimates.
## Weapons
@@ -179,9 +176,9 @@ multiplier, then derive the combined pool bounds by multiplying each endpoint
by three. Redistribution changes the split between the three cores without
changing the selected item's total allocation.
The `1.14` offset is provisional and is currently validated only for the
level-40-to-level-50 interval. Preserve enough decimal precision to revise it
without migrating rounded data.
The `1.14` offset remains provisional; observations only validate it from level
40 to 50. Retain enough decimal precision to revise it without migrating
rounded data.
### Weapon example
@@ -192,9 +189,9 @@ A weapon changed from `753 DMG` at level 40 to `873 DMG` at level 50:
floor(872.95) = 872
```
The displayed-source calculation misses by one because `753` is already
truncated. Applying the ratio to the unrounded source value produces the
observed `873`. This is why calculations must use stored decimal endpoints.
The calculation misses by one because `753` is truncated. The ratio applied to
the unrounded source value produces `873`, so calculations require stored
decimal endpoints.
The observed changes over those ten levels were linear:
@@ -207,11 +204,10 @@ ENG: 45 → 52 (about 0.7 per level)
### Early weapon levels
Do not apply the provisional late-level weapon offset to level 1. An Exotic
sample's displayed level-1 values and level-50 values fit a `2.91` endpoint
ratio for most stats, which is incompatible with simply extending the
level-40-to-level-50 offset backward. Weapons therefore also appear to have an
early transition or another quality-dependent input.
Do not apply the provisional late-level weapon offset at level 1. An Exotic
sample's level-1 and level-50 displays fit a `2.91` endpoint ratio for most
stats, which conflicts with extending the level-40-to-level-50 offset
backward. Weapons appear to have an early transition or quality-dependent input.
Until more intermediate samples are available:
+43 -37
View File
@@ -1,6 +1,6 @@
# SHD.build architecture
This project is intentionally being built in vertical slices.
The project is built in vertical slices.
## Planned stack
@@ -18,7 +18,8 @@ This project is intentionally being built in vertical slices.
- Editor: manage builder source data.
- Admin: manage users, roles, source data, and audit history.
Authorization must be checked on the server for every mutation. Hiding an admin link is not authorization.
Server code checks authorization for each mutation. Hiding an admin link does
not grant authorization.
Weapon and gear range calculations must preserve unrounded decimal endpoints
and follow the empirically validated progression rules in
@@ -26,16 +27,15 @@ and follow the empirically validated progression rules in
linear rather than compounded and game-matching integer values are truncated
only at the presentation boundary.
Local credentials are stored as Argon2id PHC strings with a unique random salt
per password. Current work factors follow OWASP's 19 MiB / 2 iteration /
single-lane minimum profile and can be increased over time; successful login
will eventually rehash credentials whose parameters are outdated.
Local credentials use Argon2id PHC strings with a unique random salt per
password. Current work factors meet OWASP's 19 MiB / 2 iteration / single-lane
minimum profile. Successful login rehashes credentials with outdated parameters.
## Database runtime
The application uses Turso Database in-process through its native Node package.
It opens a local SQLite-compatible database file and does not use Turso Cloud,
libSQL HTTP, remote replicas, auth tokens, or synchronization.
It opens a local SQLite-compatible database file. The application does not use
Turso Cloud, libSQL HTTP, remote replicas, auth tokens, or synchronization.
The database defaults to `./data/shd-build.db`. Production deployments must
provide a persistent writable filesystem and can override the location with
@@ -92,13 +92,12 @@ not locked by another process.
## Development database maintenance
The development database remains valuable persistent data, but it is no longer
served to outside contributors and does not require a low-downtime migration
window. Stop only this project's development server before backup, export,
restore, migration, or other work requiring exclusive access. Create and verify
a JSON backup, perform the prepared operation, verify schema state and critical
record counts, then restart and health-check the application. Prefer additive,
repeatable migrations and preserve the verified backup if maintenance fails.
Treat the development database as persistent data. Stop this project's
development server before backup, export, restore, migration, or other work
requiring exclusive access. Create and verify a JSON backup, perform the
prepared operation, verify schema state and critical record counts, then
restart and health-check the application. Prefer additive, repeatable
migrations and retain the verified backup if maintenance fails.
Production and staging remain shared persistent environments and retain the
deployment, backup, synchronization, and maintenance safeguards documented in
@@ -120,13 +119,14 @@ mutation rather than deletion or modification of the original event:
before-snapshot.
3. The UI requires a second, explicit confirmation.
4. The server reloads the target and compares it with the original
after-snapshot, ignoring bookkeeping timestamps. A mismatch means newer work
exists and blocks automatic compensation.
after-snapshot, ignoring bookkeeping timestamps. A mismatch indicates newer
work and blocks automatic compensation.
5. Referenced roles, permissions, and relationships are revalidated before
restoration. Restoring administrator authority additionally requires full
administrative access.
6. The server restores the previous state and appends an `audit.reverted` event
whose target is the original audit-event ID.
7. A reverted event cannot be reverted again.
# Moderated game-data contributions
@@ -156,8 +156,6 @@ assignment, so it applies consistently to existing users and every account
creation method. Registered agents can inspect live source data and submit
moderated proposals, but cannot directly mutate canonical records, review the
queue, or access unrelated ISAC operations without additional permissions.
7. A successfully reverted event cannot be reverted again.
Unsupported destructive identity operations, account deletion, incomplete
snapshots, stale targets, and events whose dependencies no longer exist are
marked non-reversible or rejected. Recovery for those cases requires a
@@ -256,7 +254,7 @@ from Firepower, Toughness, Engineering, Health, or Armor.
The two secondary slots draw distinct attributes from one shared option pool.
Each available secondary attribute stores its own minimum and maximum for all
four qualities. Gear tier determines capacity without per-item configuration:
T0 gear has one Gear Mod slot, while T1 and T2 gear have two.
T0 gear has one Gear Mod slot; T1 and T2 gear have two.
Canonical data is stored in `gear_mods`, `gear_mod_quality_values`,
`gear_mod_secondary_options`, and `gear_mod_secondary_quality_values`.
@@ -274,37 +272,45 @@ and secondary options reference active attributes that define Skill Chip
minimum and maximum roll values; a configured chip rolls one primary and two
distinct secondary stats from those separate pools.
Every active Skill Chip definition belongs exclusively to one active
specialization ability through `skill_chip_sets.specialization_ability_id`.
An ability has capacity for exactly three active definitions. The save and
restore actions enforce that capacity, while legacy rows may remain nullable
until they are edited. A specialization focus is builder-ready only when each
of its three assigned abilities has all three active Skill Chip definitions.
Skill Chip mutations refresh readiness for focuses that reference both the
previous and resulting ability.
Both set-bonus progression systems use the number of equipped High-end chips as
the interpolation step. For `h` High-end chips, where `h` is zero through
three, a configured minimum and maximum resolve as
`minimum + (maximum − minimum) × h / 3`. Zero High-end chips therefore produce
the minimum, while three produce the maximum. The editor stores only endpoints
and previews all four states, preventing duplicated tier data from diverging.
the minimum, while three produce the maximum. The editor stores endpoints and
previews all four states, preventing duplicated tier data from diverging.
Skill Chip mutations, owned relationships, and safe icon metadata are captured
in the `game_data` audit stream.
# Game-data icon storage
Talents, brand sets, OS Protocols, and Skill Chips reference uploaded artwork through the
polymorphic `game_data_icons` metadata table. Original uploads are never
retained. The shared server pipeline accepts transparent SVG, PNG, WebP, and
AVIF sources, rejects active or externally referenced SVG content, and uses
Sharp’s asynchronous native worker pool to rasterize and normalize every
accepted icon to a transparent 256 × 256 WebP.
Talents, brand sets, OS Protocols, and Skill Chips reference uploaded artwork
through the polymorphic `game_data_icons` metadata table. The shared server
pipeline accepts transparent SVG, PNG, WebP, and AVIF sources, rejects active
or externally referenced SVG content, and uses Sharp’s native worker pool to
rasterize and normalize each accepted icon to a transparent 256 × 256 WebP.
Normalized files live beneath the ignored `data/game-icons/` directory and are
served by the validated `/icons/game-data/[type]/[id]` endpoint with immutable
cache headers and MIME sniffing disabled. ISAC save actions process files
before mutation, apply upload or removal after record validation, and include
only safe icon metadata—not image bytes—in their audit snapshots.
safe icon metadata, rather than image bytes, in audit snapshots.
Icon metadata is optional. Canonical records and contributor proposals may be
created without an upload, allowing values and artwork to be contributed in
separate dependency-safe changes. Missing artwork is represented at render
time by the static `/missing-item-icon.svg` asset through the shared
`MISSING_ITEM_ICON_URL` constant and `ItemIcon` component; the placeholder is never inserted into
`game_data_icons`. A later edit or proposal can attach the real icon without
recreating or replacing the canonical item.
`MISSING_ITEM_ICON_URL` constant and `ItemIcon` component. The placeholder does
not enter `game_data_icons`. A later edit or proposal can attach the real icon
without recreating the canonical item.
# Oversight notification dispatch
New Data Change and Avatar Oversight review items are routed only to agents who
@@ -321,11 +327,11 @@ scheduler instance:
```
The dispatcher groups due rows by reviewer, queue, cadence, and delivery
window. Daily rows are assigned to the next 10:00 AM wall-clock boundary in
`America/New_York`, including daylight-saving transitions. Successfully
dispatched rows retain `delivered_at` evidence and are never selected again.
The existing ISAC notification delivery preferences still determine optional
browser and email fan-out after the consolidated inbox message is created.
window. Daily rows use the next 10:00 AM wall-clock boundary in
`America/New_York`, including daylight-saving transitions. Dispatched rows
retain `delivered_at` evidence and are excluded from later selection. Existing
ISAC notification delivery preferences determine optional browser and email
fan-out after the consolidated inbox message is created.
## Automated ISAC system messages