998 lines
54 KiB
Markdown
998 lines
54 KiB
Markdown
# SHD.build architecture
|
||
|
||
The team builds the project in vertical slices.
|
||
|
||
## Application stack
|
||
|
||
- SvelteKit 2 / Svelte 5 with TypeScript
|
||
- Embedded Turso Database via `@tursodatabase/database` for durable application
|
||
data
|
||
- Auth.js-compatible server authentication with Discord OAuth and credentials
|
||
providers
|
||
- Argon2id password hashing, server-side sessions, CSRF protection, and role
|
||
checks
|
||
- Browser `localStorage` only for anonymous build drafts
|
||
|
||
## Product boundaries
|
||
|
||
- Public: browse, sort, and view published builds and guides.
|
||
- Anonymous: create and edit local drafts.
|
||
- Authenticated: publish, rate, comment, favorite, copy, and manage owned
|
||
builds.
|
||
- Editor: manage builder source data.
|
||
- Admin: manage users, roles, source data, and audit history.
|
||
|
||
Server code checks authorization for each mutation. UI visibility has no effect
|
||
on server authorization.
|
||
|
||
Builder and shared Build pages currently expose only deterministic totals from
|
||
the equipped items, selected rolls, fixed bonuses, inherited profiles, augments,
|
||
and active set bonuses. Conditional Talent outcomes, weapon and Skill DPS,
|
||
encounter assumptions, activation profiles, rotations, and simulation remain
|
||
planned. Explicit `COMING SOON` panels identify those player-facing
|
||
capabilities.
|
||
|
||
Weapon and gear range calculations must preserve decimal endpoints and follow
|
||
the tested progression rules in
|
||
[`STAT_LEVEL_SCALING.md`](STAT_LEVEL_SCALING.md). Leveling is linear rather than
|
||
compounded. The presentation layer truncates game-matching integer values.
|
||
|
||
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. 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
|
||
`TDR_DATABASE_PATH`. This rules out ephemeral serverless filesystems unless a
|
||
deployment attaches a persistent volume.
|
||
|
||
## Site launch controls
|
||
|
||
The singleton `site_launch_settings` row stores the public launch gate, account
|
||
creation state, and availability for News, Database, Builds, Guides, and
|
||
Builder. Schema migration creates the row with Coming Soon enabled, account
|
||
creation enabled, and all five sections enabled. The application caches the row
|
||
for five seconds and clears the process cache after an operator saves changes.
|
||
|
||
Agents with `site.settings.manage` edit the row from ISAC → System Configuration
|
||
→ Site Controls. The save action requires an explicit value for all seven
|
||
controls and a confirmation token. A successful update records
|
||
`system_configuration / site_access.updated` with the operator, target row, and
|
||
complete before/after snapshots.
|
||
|
||
The request hook applies the launch gate to anonymous traffic and section
|
||
availability to every request:
|
||
|
||
- With Coming Soon enabled, anonymous `GET` and `HEAD` requests redirect to
|
||
`/coming-soon`. Anonymous mutations outside exempt routes receive a JSON 404.
|
||
- Authenticated agents bypass the Coming Soon gate and enter the application.
|
||
- A disabled public section blocks its route family for every user, including
|
||
authenticated agents: `/news`, `/database`, `/builds`, `/guides`, or
|
||
`/builder`. `GET` and `HEAD` requests redirect to `/coming-soon` with the
|
||
unavailable section identified. Mutations receive a JSON 404.
|
||
- Authentication callbacks, sign-in, contact, legal pages, health endpoints,
|
||
scheduler endpoints, application assets, and the prelaunch page remain
|
||
reachable without a session.
|
||
|
||
The global navigation omits disabled sections for every user. Server checks
|
||
remain authoritative for direct URLs and mutations. An authenticated operator
|
||
may open `/coming-soon` to preview the promotional page, but the preview does
|
||
not override section availability.
|
||
|
||
Account creation uses a separate control. Closing registration blocks password
|
||
registration, a magic-link request for an unknown email, and Discord OAuth for
|
||
an unknown Discord identity. Existing password, email, and Discord identities
|
||
can still sign in. The sign-in page hides the password registration form and
|
||
explains registration closure. This guard runs on the server, so a crafted form
|
||
submission or OAuth callback cannot create an account while registration is
|
||
closed.
|
||
|
||
## Community content moderation
|
||
|
||
Comments, profile biographies, custom avatars, and Guides share the moderation
|
||
lifecycle documented in [`CONTENT_MODERATION.md`](CONTENT_MODERATION.md). Local
|
||
rules and the configured provider classify each submission before publication.
|
||
Clean submissions can publish without human work. Flagged submissions and
|
||
fail-closed provider errors enter the appropriate Oversight queue.
|
||
|
||
Agents may report visible content they do not own. Reports enter Oversight
|
||
without hiding the content. Human decisions require a reason and append an audit
|
||
event. Long-form edits that need review use immutable pending revisions so the
|
||
last approved biography or Guide remains public.
|
||
|
||
ISAC Site Controls owns provider behavior, content scopes, thresholds, link and
|
||
PII checks, and allowed or banned terms. API credentials remain in server
|
||
environment configuration.
|
||
|
||
## Guides and News articles
|
||
|
||
`articles` stores the stable identity, kind, owner, immutable slug, visibility,
|
||
and published or pending revision pointers for Guides and News. Authors edit one
|
||
mutable `article_drafts` row with optimistic version checks. Publication creates
|
||
an immutable `article_revisions` snapshot. Public readers and search load only
|
||
the revision named by `articles.published_revision_id`.
|
||
|
||
Guides require OpenAI moderation before an unlisted or public revision can
|
||
publish. The moderation request includes the title, deck, document text, image
|
||
alternative text and captions, cover image, and inline images. A failed provider
|
||
request stays in Guide Oversight. Clean revisions may publish automatically;
|
||
flagged revisions preserve the previous public revision while Oversight decides.
|
||
News bypasses this lifecycle because `news.manage` is the editorial gate. News
|
||
comments still use community moderation.
|
||
|
||
### Ubisoft News syndication
|
||
|
||
The Ubisoft News importer reads the English-language Division Resurgence feed
|
||
from Ubisoft Nimbus. It stores each Nimbus item ID as the stable external ID and
|
||
publishes the source title, excerpt, canonical Ubisoft link, and validated
|
||
Ubisoft-hosted thumbnail. SHD.build does not copy the article body or image
|
||
file. A bounded same-origin endpoint proxies each thumbnail so opening the News
|
||
listing does not send visitor metadata to Ubisoft. News cards link to the
|
||
canonical Ubisoft article. Imported articles use Ubisoft's original publication
|
||
timestamp rather than the import time.
|
||
|
||
`external_news_sources` maps each Nimbus ID to one SHD.build article. A SHA-256
|
||
content hash covers the normalized title, excerpt, canonical URL, and source
|
||
publication timestamp. An unchanged hash updates `last_seen_at` without creating
|
||
a revision. Thumbnail changes update the audited source metadata without
|
||
creating an article revision. A changed content hash creates and publishes an
|
||
immutable revision. The importer does not delete or unpublish an article when
|
||
Nimbus omits it from a later feed, and it does not restore an article that an
|
||
editor deleted. The Newsroom treats source-linked articles as read-only so local
|
||
edits cannot retain Ubisoft attribution. The importer defers future-dated items
|
||
until their source publication time.
|
||
|
||
The seeded, non-public `Ubisoft Official News` service identity owns imported
|
||
articles and acts on their audit events. A first import records
|
||
`editorial_content / news.imported`; a later content sync records
|
||
`editorial_content / news.import_synced`. Audit snapshots identify the provider,
|
||
Nimbus ID, canonical URL, thumbnail metadata, source timestamp, and resulting
|
||
revision.
|
||
|
||
The existing bearer-authenticated scheduler endpoint invokes the importer. A
|
||
successful run sets the next check one hour later. The importer stores the last
|
||
attempt, last success, next check, consecutive failure count, and bounded last
|
||
error in provider state. Failures retry after 15 minutes with exponential
|
||
backoff capped at six hours. A successful run clears the error and failure
|
||
count.
|
||
|
||
The article document is a versioned, strictly validated Tiptap JSON envelope.
|
||
The application stores no author-supplied HTML and renders accepted nodes with
|
||
Svelte components. `article_revision_build_embeds` supports several Builds per
|
||
Guide and several Guides per Build. Each relation records a bounded publication
|
||
snapshot. Authors may embed any Build they own; another Agent's Build must be
|
||
public and becomes unavailable in the Guide if its source stops being public.
|
||
|
||
Guide taxonomy combines fixed activity and difficulty terms with active
|
||
Specializations, Focuses, and weapon categories. `article_search` stores the
|
||
published title, deck, body text, and tags. Weighted token scoring prioritizes
|
||
title, deck, tags, then body because the embedded Turso engine does not ship the
|
||
FTS5 module. Search rows exist only for current public revisions.
|
||
|
||
Registered and anonymous visitors may rate a Guide from 1 through 5. Anonymous
|
||
ratings and Guide reports use a random HTTP-only browser token; the database
|
||
stores only its domain-separated hash. Repeated anonymous activity requires a
|
||
Turnstile result. Guide reports reference the exact published revision so a
|
||
report against an older revision cannot hide newer work.
|
||
|
||
The Builder stores loadout data and displays deterministic equipped-item totals.
|
||
It has no Guide body. Shared Build pages use the same player-facing calculation
|
||
boundary. An owned Build page links to `/guides/new?build=<id>`, where the
|
||
server verifies ownership and initializes a removable Build block in a new
|
||
private Guide.
|
||
|
||
### Discord-compatible Build sharing experiment
|
||
|
||
Persisted public and unlisted Builds expose absolute Open Graph metadata for
|
||
their canonical `/builds/[slug]` URL. Their `og:image` and Twitter card metadata
|
||
reference the same-origin 1200 × 630 PNG at `/builds/[slug]/preview.png` so
|
||
Discord and other link unfurlers can render a Build summary. Private Builds set
|
||
`noindex,nofollow`, expose no crawler image, and provide no usable sharing
|
||
control.
|
||
|
||
The preview endpoint derives its image from canonical Build facts and the
|
||
current known-stat summary in the shared Build projection. It includes the Build
|
||
title, author, visibility, direct URL, equipped weapon names, core totals, and
|
||
up to three supporting Attributes. The renderer performs no external fetches. It
|
||
normalizes and bounds every text field by grapheme count, escapes text before
|
||
inserting it into XML, builds a text-only SVG, and rasterizes that SVG to PNG
|
||
with Sharp. Production installs DejaVu Sans from the Debian package and the
|
||
renderer names that fixed family explicitly.
|
||
|
||
The endpoint hashes a versioned serialization of the image input for its bounded
|
||
in-process render cache and derives the HTTP ETag from the resulting PNG bytes.
|
||
A bounded request queue limits concurrent database projections and Sharp work.
|
||
Responses require cache revalidation so a visibility change to Private blocks
|
||
the next request instead of leaving a stale response window. Matching
|
||
conditional requests receive `304 Not Modified`. Private, missing, and
|
||
inaccessible Builds receive no preview. Unlisted images send the
|
||
`X-Robots-Tag: noindex` response header.
|
||
|
||
For eligible Builds, the Build Uplink invokes the native Web Share API as the
|
||
primary action when the browser supports it. An explicit Copy Link action
|
||
remains available for every public or unlisted persisted Build. This experiment
|
||
does not install a Discord bot, post to Discord, extend Discord OAuth scopes, or
|
||
store Discord tokens or webhooks. Discord receives only a link that an Agent
|
||
chooses to share and may fetch the public metadata and same-origin preview.
|
||
|
||
## Public data corrections and discussion
|
||
|
||
Every public database card links to its canonical ISAC record with an exact
|
||
`item` ID and offers a moderated Community Discussion. Authenticated Agents can
|
||
post notes and replies; Community Oversight may pin one visible comment as the
|
||
record's Community Note. A public correction opens the same ISAC editor used by
|
||
data operators. Contributor-mode submissions create a proposal rather than
|
||
changing canonical data. Contributors can attach an optional bounded rationale;
|
||
the rationale is retained with the proposal and visible to Data Oversight, but
|
||
does not become part of the canonical action replay.
|
||
|
||
## Increment plan
|
||
|
||
1. Product shell and anonymous local-draft builder.
|
||
2. Embedded Turso migrations, repositories, and seed data.
|
||
3. Credentials and Discord authentication.
|
||
4. Full equipment/stat builder driven by database definitions.
|
||
5. Publishing, discovery, ratings, comments, favorites, and copy flow.
|
||
6. Rich build guides and moderation.
|
||
7. Admin editors, validation, audit log, and data import/export.
|
||
|
||
# Administrator bootstrap
|
||
|
||
The authentication service promotes the first authenticated user to `admin` if
|
||
the database does not contain an administrator. Promotion uses one conditional
|
||
database update so concurrent first sign-ins cannot both become administrators.
|
||
Once an administrator exists, later sign-ins never alter roles.
|
||
|
||
# Database backup and restore
|
||
|
||
Embedded Turso databases contain contributor-entered source data and must be
|
||
preserved across development changes.
|
||
|
||
Create a timestamped development backup:
|
||
|
||
```sh
|
||
npm run db:backup
|
||
```
|
||
|
||
Create a production backup:
|
||
|
||
```sh
|
||
npm run db:backup:production
|
||
```
|
||
|
||
The scripts write versioned JSON documents beneath `data/backups/` by default.
|
||
Git ignores that directory because backups contain private user data, including
|
||
password hashes.
|
||
|
||
To restore, first create an empty database and apply the schema. Then run:
|
||
|
||
```sh
|
||
npm run db:restore -- data/backups/<backup-file>.json
|
||
```
|
||
|
||
The restore operation refuses to import into any database containing records.
|
||
This guard prevents accidental merging or overwriting of contributor data. Stop
|
||
the application before backup or restore so the embedded database file is not
|
||
locked by another process.
|
||
|
||
## Development database maintenance
|
||
|
||
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
|
||
`docs/DEPLOYMENT.md`.
|
||
|
||
# ISAC audit and compensating reversals
|
||
|
||
Every successful ISAC mutation appends an event to `audit_log` through the
|
||
shared server audit utility. Events identify the operator, operational category,
|
||
specific action, target entity, timestamp, and useful before/after evidence. The
|
||
audit registry is read-only and requires `audit.view`; the seeded Auditor role
|
||
grants that permission without granting mutation capabilities.
|
||
|
||
Audit history is immutable. The server implements “Revert” as a new compensating
|
||
mutation rather than deletion or modification of the original event:
|
||
|
||
1. The operator must hold `audit.revert` in addition to any other access.
|
||
2. The reversion registry must support the original action and the event must
|
||
contain a usable 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 indicates newer
|
||
work and blocks automatic compensation.
|
||
5. The server revalidates referenced roles, permissions, and relationships
|
||
before restoration. Restoring administrator authority also 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. The server rejects a second reversion of the same event.
|
||
|
||
# Moderated game-data contributions
|
||
|
||
ISAC data access uses `data.view`, `data.propose`, `data.manage`, and
|
||
`data.approve`. Data Viewers can inspect registries. Data Contributors can
|
||
submit the same editor forms, but the server serializes each request into
|
||
`data_change_proposals` instead of mutating canonical tables. Data Oversight
|
||
reviews the queue and must confirm approval or rejection.
|
||
|
||
Approval replays the stored form through its original SvelteKit server action.
|
||
This preserves canonical validation, relational updates, icon processing, and
|
||
the normal game-data audit event. Proposal creation and review decisions also
|
||
produce separate `data_moderation` audit events. Failed canonical validation
|
||
marks a proposal as failed without changing live data. Pending counts appear
|
||
alongside each data registry in ISAC so collaborators can see concurrent
|
||
suggestions.
|
||
|
||
The protected seeded roles are Data Viewer, Data Contributor, Data Editor, and
|
||
Data Oversight. Data Editor retains direct-write access for trusted operators;
|
||
Data Oversight combines direct-write access with queue review. New ISAC data
|
||
modules must use the shared moderation interception path and audit both the
|
||
final mutation and proposal lifecycle.
|
||
|
||
Mechanic programs remain part of their owning source record. Talent, OS
|
||
Protocol, Skill Chip, and Specialization proposals carry the complete typed
|
||
program with the rest of the source payload. Oversight reviews and approves that
|
||
program through the source module's canonical validation path. There is no
|
||
standalone Trigger proposal module or pending Trigger dependency.
|
||
|
||
Each authenticated, active registered agent receives `isac.access`, `data.view`,
|
||
and `data.propose` as baseline permissions in the authorization layer. The
|
||
authorization layer grants this baseline without a removable role assignment, so
|
||
existing users and each account-creation method receive the same access.
|
||
Registered agents can inspect live source data and submit moderated proposals,
|
||
but cannot mutate canonical records, review the queue, or access unrelated ISAC
|
||
operations without additional permissions. 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 specialized workflow rather than inferred
|
||
database writes.
|
||
|
||
# System metrics
|
||
|
||
ISAC uses `/isac` as its canonical route namespace. The server redirects legacy
|
||
`/admin` URLs to their matching `/isac` location so old notifications and
|
||
external bookmarks remain usable.
|
||
|
||
The read-only `/isac/metrics` route requires `metrics.view` and collects a
|
||
point-in-time process-local health snapshot. Node and operating-system APIs
|
||
provide process CPU and memory, event-loop utilization and delay, host memory,
|
||
load average, core count, platform, and uptime. Embedded Turso pragmas provide
|
||
page allocation, page size, and free-page information; aggregate SQL queries
|
||
provide application record volumes.
|
||
|
||
The interface refreshes on demand and may poll every ten seconds while the
|
||
operator keeps auto-refresh enabled. CPU and event-loop values are deltas from
|
||
the previous server-side sample, so the first sample and samples separated by
|
||
long gaps provide direction rather than a monitoring-grade time series.
|
||
Production alerting and historical analysis should use a dedicated external
|
||
telemetry system. This surface omits secrets, environment-variable values, and
|
||
full database paths.
|
||
|
||
The same surface reports remove.bg paid credits and free API calls as separate
|
||
capacity pools. The scheduler reconciles the account balance no more often than
|
||
the configured interval, while successful image processing applies the
|
||
provider's `X-Credits-Charged` value to the persisted estimate. Warning and
|
||
critical transitions create one alert episode with a unique delivery for each
|
||
active Administrator. A later critical transition may alert again; repeated
|
||
scheduler runs within the same episode do not. Provider errors preserve the last
|
||
known balance and never expose the API key or raw response body.
|
||
|
||
# Weapon category bonuses
|
||
|
||
`weapon_category_attribute_bonuses` stores three ordered fixed Attribute
|
||
contributions for each Shotgun, Rifle, MMR, AR, LMG, and SMG category. Each
|
||
profile grants 5% Weapon Critical Hit Chance and 50% Weapon Critical Hit Damage.
|
||
Shotgun, Rifle, AR, LMG, and SMG grant 40% Headshot Damage; MMR grants 70%.
|
||
|
||
Weapon records do not own copies of these rows. Both builder catalog loaders
|
||
attach the matching category profile when they hydrate a weapon. Exotic weapons
|
||
retain an underlying category and inherit the same profile as a standard weapon
|
||
of that type. The combat summary preserves the existing equipped-weapon
|
||
semantics: each equipped weapon contributes its inherited profile to the Agent's
|
||
accumulated Attributes.
|
||
|
||
ISAC exposes one category-profile editor above the weapon registry. Operators
|
||
may change the three positive values, while the Attribute identities and order
|
||
remain fixed. Each successful save appends a
|
||
`game_data/weapon_bonus_profile.updated` audit event with the complete previous
|
||
and resulting profile.
|
||
|
||
# Referenced exotic weapon talents
|
||
|
||
Standard weapon talent pools remain reusable across weapons and use Talent 1 /
|
||
Top or Talent 2 / Bottom. Contributors create dedicated talents in the Talent
|
||
Registry under `Exotic Weapon`. The database stores these records as Weapon
|
||
talents with Exotic scope and no standard slot.
|
||
|
||
An exotic weapon references one active exotic talent through
|
||
`weapons.exotic_talent_id`. The weapon editor lists registered exotic talents
|
||
and identifies talents assigned to another weapon. Server validation rejects
|
||
standard talents, inactive talents, and talents already assigned elsewhere.
|
||
Changing or clearing a weapon relationship leaves the previous talent active and
|
||
available for another assignment.
|
||
|
||
Tier I and its modeled mechanic are required when a contributor saves an exotic
|
||
talent. Tier II may remain undocumented. An undocumented Tier II has no
|
||
`talent_tiers` row or conditional-effect value variant. A later Talent Registry
|
||
edit adds the Tier II projection and mechanic values without changing the weapon
|
||
reference.
|
||
|
||
# OS Protocol progression
|
||
|
||
OS Protocols use dedicated normalized tables because their talent progression
|
||
has six calculated states (base plus five upgrades) rather than the shared
|
||
two-tier talent model. Each protocol owns one `os_protocol_primary_effects`
|
||
record and one `os_protocol_talents` record. Each protocol Talent owns its
|
||
mechanic program through the shared conditional-effect tables.
|
||
|
||
Dedicated profile tables normalize the three secondary attributes. One
|
||
`os_protocol_secondary_profiles` record exists for each configured quality +
|
||
attribute-type combination, with its three fixed stat/value pairs in
|
||
`os_protocol_secondary_effects`. Every matching protocol inherits that profile;
|
||
secondary values are not copied onto individual protocol records.
|
||
|
||
The unique primary value uses `base × (1 + (level − 1) / 49)` for levels 1–50,
|
||
while talent effects use `base × (1 + upgrades / 5)` for zero to five upgrades.
|
||
Server actions validate all relationships and append the full owned graph to the
|
||
existing `game_data` audit history.
|
||
|
||
### Weapon Mods and tiered weapon slots
|
||
|
||
`weapon_mod_slots` records each weapon's available slot vocabulary per tier. The
|
||
editor captures one shared set for T1/T2 and the single shared slot omitted at
|
||
T0, then expands that input into the normalized per-tier rows. Operators may
|
||
save partial rows when the in-game configuration is unavailable. ISAC derives
|
||
`CONFIG REQUIRED` until every available tier contains four unique slots at T0 or
|
||
five at T1/T2. Regular Weapon Mods declare a single slot type and one or more
|
||
compatible base weapon categories through `weapon_mod_compatible_categories`;
|
||
eligibility requires both a category match and the slot to exist on the equipped
|
||
weapon at its selected tier.
|
||
|
||
Exotic Weapon Mods instead reference one `exotic_weapon_id`. They do not carry
|
||
regular category compatibility. The builder treats them as permanent equipment
|
||
and lets the user change only their level. They do not use quality profiles,
|
||
random primary rolls, secondary pools, or regular Mod Effects. Their canonical
|
||
contributions are fixed attribute references with explicit level-1 and level-20
|
||
values in `weapon_mod_exotic_attributes`; intermediate values use linear
|
||
interpolation.
|
||
|
||
All regular Weapon Mods permit Firepower, Toughness, or Engineering as a
|
||
builder-time primary choice. `weapon_mod_quality_values` stores the shared
|
||
level-1 and level-20 primary roll endpoints for each quality; primary values use
|
||
linear interpolation from levels 1–20.
|
||
|
||
`weapon_mod_secondary_options` is the shared pool from which a build selects two
|
||
distinct secondary attributes. Every option owns a minimum and maximum at each
|
||
quality through `weapon_mod_secondary_quality_values`; secondary availability is
|
||
not tied to a numbered slot in canonical data. Mod Effects are triggerless, hold
|
||
one or two attribute references, and store fixed per-quality values in
|
||
`weapon_mod_effect_quality_values`.
|
||
|
||
### Gear Mods and tier slot capacity
|
||
|
||
Gear Mod definitions are standard-quality records only; there is no exotic Gear
|
||
Mod branch. Every definition supplies primary roll ranges for Standard,
|
||
Specialized, Superior, and High-end quality at Levels 1 and 20. Values between
|
||
those endpoints use linear interpolation. A build instance selects one primary
|
||
attribute 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; T1 and T2 gear have two.
|
||
|
||
The `gear_mods`, `gear_mod_quality_values`, `gear_mod_secondary_options`, and
|
||
`gear_mod_secondary_quality_values` tables store canonical data. Successful
|
||
mutations use `gear_mod.created`, `gear_mod.updated`, `gear_mod.archived`, and
|
||
`gear_mod.unarchived` audit actions. Contributor submissions use the `gearmods`
|
||
proposal module and remain non-canonical until approved by Oversight.
|
||
|
||
# Skill Chip quality scaling
|
||
|
||
Skill Chip sets support Superior and High-end chip instances. Each set owns one
|
||
2-piece bonus, distinct primary and secondary stat-option pools, and one 3-piece
|
||
Talent with a typed conditional rule. Primary 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
|
||
pools.
|
||
|
||
Each active Skill Chip definition belongs to one active specialization ability
|
||
through `skill_chip_sets.specialization_ability_id`. An ability has capacity for
|
||
three active definitions. The save and restore actions enforce that capacity,
|
||
and the schema requires the foreign key for each row. 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 endpoints and
|
||
previews all four states, preventing duplicated tier data from diverging. The
|
||
`game_data` audit stream captures Skill Chip mutations, owned relationships, and
|
||
safe icon metadata.
|
||
|
||
# Mechanic programs and loadout state
|
||
|
||
Mechanic programs belong to the record whose behavior they describe. One
|
||
`conditional_effect_rules` row owns the program for one standard or exotic
|
||
Talent, OS Protocol Talent, Skill Chip Talent, or Specialization tree rank. The
|
||
`source_type` and `source_id` pair identifies that owner. No standalone Trigger
|
||
record or shared Trigger relationship participates in execution.
|
||
|
||
The mechanic runtime accepts normalized timeline events for each equipped
|
||
mechanic. Current player pages do not construct this timeline or invoke the
|
||
runtime. `movement_snapshot.values.state` is `immobile`, `moving`, or
|
||
`sprinting`; the mechanic's moving and sprinting scenario controls select that
|
||
state, with sprinting taking precedence. Enabled Skill activation profiles emit
|
||
`skill_deployed` events on the same cadence as Skill calculations and identify
|
||
the deployed ability. An `on_event` Skill modifier applies to that activation
|
||
until the ability's next deployment or the encounter ends.
|
||
|
||
Each source supplies a fixed value-variant contract:
|
||
|
||
- Standard Talents require Tier I and Tier II values. Exotic Talents require
|
||
Tier I and may omit Tier II until contributors document its game values.
|
||
Runtime calculations use Tier I while an equipped exotic Talent has no Tier II
|
||
mechanic variant.
|
||
- OS Protocol Talents store a base value. An outcome may use an expression that
|
||
reads `equipment.protocolTalentUpgrades`, from zero through five, so selected
|
||
values can scale to twice the base at upgrade five.
|
||
- Skill Chip Talents store minimum and maximum values for quality composition
|
||
interpolation.
|
||
- Specialization tree-rank effects store the rank's base value.
|
||
|
||
The rule row stores the script version, source-defined scenario-input controls,
|
||
configuration state, and one of three simulation-readiness states:
|
||
|
||
- `ready`: the runtime has enough typed data to calculate the mechanic.
|
||
- `requires_assumptions`: the mechanic needs one or more scenario inputs before
|
||
the runtime can include it.
|
||
- `captured_only`: ISAC preserves the authored mechanic, but the runtime cannot
|
||
calculate it.
|
||
|
||
The code-defined `MECHANIC_EVENT_REGISTRY` is the event authority. Its 28
|
||
normalized events are `shot_fired`, `weapon_hit`, `skill_deployed`,
|
||
`signature_ability_deployed`, `skill_hit`, `skill_multishot_triggered`,
|
||
`agent_hit`, `health_damage_taken`, `damage_dealt`, `enemy_killed`, `assist`,
|
||
`ally_downed`, `agent_downed`, `ally_rescued`, `grenade_thrown`,
|
||
`medical_kit_used`, `healing_applied`, `extra_health_granted`,
|
||
`extra_health_changed`, `movement_snapshot`, `proximity_snapshot`,
|
||
`firing_started`, `firing_stopped`, `reload_started`, `reload_completed`,
|
||
`weapon_swapped`, and `tick`. Event details such as target, distance, critical
|
||
state, source, movement, and nearby counts travel as typed event values. The
|
||
event registry publishes authoritative field metadata where branches need a
|
||
bounded contract. For those events, the mechanic editor derives its event-field
|
||
controls from that metadata. For example, `weapon_hit.values.headshot` is a
|
||
boolean that distinguishes headshots from body shots.
|
||
|
||
`extra_health_changed` is Agent-scoped. Normalized timeline producers emit it
|
||
when the Agent receives an Extra Health grant, when the remaining amount
|
||
changes, and when it is depleted to zero. Every event requires the boolean
|
||
`values.hasExtraHealth`. An event may include the non-negative
|
||
`values.currentExtraHealth`; when present, the amount must agree with the
|
||
boolean status. `extra_health_granted` represents a grant action and remains a
|
||
separate event from changes to the Agent's current Extra Health state.
|
||
|
||
The optional Action Family catalog provides authoring recipes for common
|
||
mechanics. Applying a recipe copies its typed scenario inputs, state pools, and
|
||
clauses into the source-owned program. Those branches become editable
|
||
authoritative data; the recipe key remains provenance, not a runtime dependency.
|
||
Adding another recipe appends branches. Replacing a program with a recipe
|
||
requires confirmation because replacement discards the current branch graph.
|
||
|
||
A Talent may save an empty `captured_only` program while an author records its
|
||
name, category, image, and source description. The registry marks that Talent
|
||
`CONFIG REQUIRED`, and calculations exclude it. Changing readiness to `ready` or
|
||
`requires_assumptions` restores strict branch, numbered-outcome, and
|
||
description-placeholder validation.
|
||
|
||
The program graph uses three child tables:
|
||
|
||
- `conditional_effect_state_pools` defines agent- or target-scoped flags,
|
||
counts, stacks, durations, scalar values, marks, and resources. Each pool owns
|
||
its bounds, expiry policy, refresh policy, and best-case value.
|
||
- `conditional_effect_clauses` listens for one event from the registry. A clause
|
||
owns a typed predicate, reducer, optional cooldown, optional weight
|
||
expression, state mutations, and optional recipe provenance. Reducers convert
|
||
event streams into counts, distinct or consecutive matches, sliding windows,
|
||
durations, intervals, or derived values. Mutations can set, add, consume,
|
||
clear, derive, or transfer state. A mutation may bind a target-scoped pool to
|
||
the current event target, event actor, or another typed entity selector. Each
|
||
target owns an independent value and expiry timer.
|
||
- `conditional_effect_outcomes` applies Attribute modifiers, direct damage,
|
||
restoration, resource costs, or markers. Each outcome names its target scope,
|
||
value basis, optional resource or Attribute, combat timing, optional state
|
||
band, and phase: `on_event`, `after_event`, `on_enter`, `on_exit`, or
|
||
`while_active`. One typed value or expression exists for every variant
|
||
required by the owning source. The common value models are static values, live
|
||
Attribute values, percentage-normalized Attribute values, event-field values,
|
||
reducer output, source-defined scenario values, and OS Protocol upgrade
|
||
scaling. Restoration outcomes may deliver their total immediately or
|
||
continuously over an explicit duration.
|
||
|
||
The generic runtime in `src/lib/calculations/mechanic.ts` processes combat
|
||
events in timestamp order. Predicates decide whether an event enters a clause.
|
||
Reducers convert event streams into activations or derived values. The runtime
|
||
applies state mutations, enforces pool bounds and expiry, emits outcomes in
|
||
phase order, and records telemetry for each activation.
|
||
|
||
Each write to an expiring pool refreshes its timer. When a pool expires,
|
||
`on_exit` outcomes evaluate stack thresholds and per-stack expressions against
|
||
the final value before reset. The runtime records the bound target on emitted
|
||
outcomes. A one-second target debuff with a one-second damage cadence can
|
||
therefore resolve once at its refreshed shared expiry without inventing
|
||
intermediate ticks.
|
||
|
||
Live Attribute references use a stable Attribute ID and read the authoritative
|
||
loadout total that the builder captures for the evaluation pass. Percentage
|
||
Attributes retain their displayed units, so an expression that applies a
|
||
coefficient to a percentage divides the Attribute value by 100. Evaluation fails
|
||
when the loadout does not supply a referenced Attribute; the runtime does not
|
||
substitute zero.
|
||
|
||
The read-only `combat.currentHealthPercent` reference divides resolved current
|
||
Health by resolved maximum Health and returns a value from 0 through 100. The
|
||
runtime derives it when a predicate is evaluated instead of storing a second
|
||
Health value that could become stale. Current timing observes deterministic
|
||
resource changes from earlier branches at the same event. Health-band mechanics
|
||
use strict upper bounds and inclusive lower bounds, so bands do not overlap and
|
||
an exact threshold enters the next weaker band. Maximum-potential evaluation
|
||
applies the same predicates before emitting passive outcomes.
|
||
|
||
Continuous restoration resolves the outcome's total when the branch activates,
|
||
then delivers that total at a constant rate over the configured duration.
|
||
Activations create independent streams, overlapping streams add, and restoration
|
||
above the resource maximum is lost when it occurs. The runtime integrates
|
||
delivery at event and expiry boundaries before processing the event at that
|
||
timestamp, which keeps results independent of an arbitrary tick interval.
|
||
|
||
Periodic behavior requires an interval in the reducer. The runtime does not
|
||
infer a one-second cadence from missing data. A mechanic with an unknown cadence
|
||
uses `requires_assumptions`, identifies the missing input, and stays out of the
|
||
calculation until an operator or builder scenario supplies it.
|
||
|
||
The runtime derives a read-only pseudo-language listing from the typed program.
|
||
ISAC uses that listing to explain events, predicates, reducers, state changes,
|
||
phases, and formulas. Planned player calculations can reuse it when those pages
|
||
consume mechanic outcomes. No route accepts the generated text as input, and no
|
||
server process executes it. Operators edit structured fields; the formatter
|
||
regenerates the listing after each change.
|
||
|
||
## AI-assisted mechanic drafting
|
||
|
||
Manual mechanic authoring remains the complete authoring path. The AI assistant
|
||
produces an optional draft for the existing structured editor. It cannot save,
|
||
publish, approve, or submit game data, and it cannot alter application code. An
|
||
author may ignore, revise, or replace any generated field through the manual
|
||
controls.
|
||
|
||
The shared mechanic editor exposes the mechanic assistant for standard and
|
||
exotic Talents, OS Protocol Talents, Skill Chip Talents, and Specialization
|
||
tree-rank effects. Calculation revisions use a separate assistant contract
|
||
because their typed stage schema and publication authority differ from
|
||
source-owned mechanic programs.
|
||
|
||
The server owns a versioned mechanic-authoring prompt and context builder. Each
|
||
request receives the source type, progression contract, submitted game text,
|
||
current draft when supplied, code-defined mechanic registries, and the active
|
||
ISAC Attribute catalog. Attribute context includes stable IDs, names, units,
|
||
descriptions, and calculation metadata. The client cannot replace the system
|
||
instructions, choose arbitrary models, attach files, add tools, or add external
|
||
URLs.
|
||
|
||
The deterministic draft parser owns the canonical outcome-capability manifest.
|
||
The AI context serializes that manifest instead of maintaining a second outcome
|
||
grammar. The parser rejects outcome fields absent from the manifest. Contract
|
||
tests build a draft from each advertised capability example and pass it through
|
||
the parser, so CI blocks a prompt that advertises unsupported fields. A mechanic
|
||
grammar change must update the manifest and example, pass the round-trip test,
|
||
and increment the prompt contract version before release.
|
||
|
||
The OpenAI integration uses the Responses API with Structured Outputs, no tools,
|
||
and `store: false`. The application stores the bounded clarification history
|
||
needed for the current drafting session. Requests use a fixed server-configured
|
||
model, a privacy-preserving user safety identifier, input and output limits,
|
||
timeouts, duplicate-request protection, and per-agent rate limits. The API key
|
||
remains on the server.
|
||
|
||
The model returns one structured result:
|
||
|
||
- `clarification_required` contains bounded questions whose answers affect the
|
||
calculation model.
|
||
- `draft_ready` contains the typed mechanic draft, assumptions, warnings,
|
||
readiness state, and a short explanation.
|
||
- `capability_gap` names unsupported runtime behavior, the nearest supported
|
||
representation, an optional workaround, and the schema, runtime, editor,
|
||
reference, and test changes a developer would need to add support.
|
||
|
||
For a mechanic capability gap, the client builds a developer prompt from the
|
||
exact analyzed wording, clarification transcript, source profile, and structured
|
||
gap response. The author can copy that prompt without another model request. The
|
||
author can also submit the gap to active full administrators. The submission
|
||
endpoint validates bounded fields, confirms that the request ID belongs to the
|
||
author's audited `capability_gap` generation, suppresses repeat submissions,
|
||
sends mandatory ISAC notifications, and appends a `mechanic_gap.submitted` event
|
||
to the `ai_assistance` audit category. The notification links to the filtered
|
||
audit evidence. Submission does not mutate the mechanic draft or parent
|
||
game-data record.
|
||
|
||
The server validates `draft_ready` output against the response schema and the
|
||
same deterministic mechanic parser used by manual authoring. Unknown events,
|
||
Attributes, value models, scopes, operators, reducers, or outcomes fail
|
||
validation. The client receives no draft when local validation fails.
|
||
|
||
The author reviews a validated draft and chooses whether to apply it to the
|
||
unsaved editor. Applying a draft changes client form state only. The existing
|
||
module action performs canonical validation, contribution interception,
|
||
approval, persistence, and game-data audit logging after the author initiates
|
||
the normal save flow.
|
||
|
||
The `ai.mechanic.generate` permission authorizes generation and clarification
|
||
requests. The server checks it for every request. Agents without the permission
|
||
may see the disabled assistant entry point, but they retain all manual controls
|
||
allowed by their data role.
|
||
|
||
Successful generation operations append sanitized events under the
|
||
`ai_assistance` audit category. Events identify the operator, source type,
|
||
prompt-context version, model, result type, token usage, request ID, and
|
||
validation result. Audit snapshots exclude the API key, environment
|
||
configuration, hidden model reasoning, authentication data, and unbounded prompt
|
||
content. Applying a draft changes client state and creates no server event. The
|
||
owning module's game-data audit event remains the record of any saved mechanic
|
||
change.
|
||
|
||
### AI-assisted calculation-revision drafting
|
||
|
||
The calculation assistant converts an operator's plain-language combat-model
|
||
request into an optional draft revision. It receives the current active or
|
||
edited revision, the calculation-stage vocabulary, weapon and Skill profile
|
||
contracts, and the active ISAC Attribute catalog. Its versioned prompt,
|
||
structured response schema, validation, and application adapter remain separate
|
||
from mechanic drafting.
|
||
|
||
The assistant returns clarification questions, a validated revision draft, or a
|
||
calculation capability gap. A capability gap identifies the missing stage,
|
||
operator, curve, binding, runtime behavior, or validation rule and lists the
|
||
application and test changes needed to support it. The server rejects unknown
|
||
stage keys, locked-position changes, duplicate bindings, invalid Attribute
|
||
references, unsupported operators, and malformed curve or cap values through the
|
||
deterministic calculation-revision parser.
|
||
|
||
The `ai.calculation.generate` permission authorizes calculation drafting and
|
||
clarification requests. It does not grant revision mutation or publication.
|
||
Applying a result changes the open revision form in the client. The operator
|
||
must review and save that draft through the existing revision action. The
|
||
separate `calculation.publish` permission and activation confirmation remain
|
||
mandatory before the saved draft becomes the active authored model. Current
|
||
player pages do not consume that model.
|
||
|
||
Successful calculation-generation requests append sanitized events under
|
||
`ai_assistance`. These events use calculation-specific actions, identify the
|
||
base revision and generated contract version, and follow the same request,
|
||
token-usage, result, and secret-exclusion rules as mechanic-assistance events.
|
||
Applying a draft creates no server event. Existing `calculation_configuration`
|
||
events remain the evidence for revision saves and activation.
|
||
|
||
Source authors define planned player scenario controls as typed toggle, slider,
|
||
or numeric inputs. Each input records its label, guidance, bounds when
|
||
applicable, default value, and best-case value. ISAC retains these definitions
|
||
for continued mechanic authoring. Current Builder and shared Build pages do not
|
||
render or evaluate them. Their calculation panels display `COMING SOON` instead.
|
||
|
||
Loadout schema version 4 stores one focus, selected tree ranks, nine Skill
|
||
Chips, a unified conditional-effect state map, encounter assumptions,
|
||
per-ability activation controls, and `calculationMode`. Runtime readers accept
|
||
the exact version 4 structure. The schema retains its simulation fields for
|
||
compatibility, but the current player UI ignores them. A backed-up prototype
|
||
migration removes drafts, saved builds, and calibration snapshots that use
|
||
incompatible payloads.
|
||
|
||
## Planned player calculation modes
|
||
|
||
Loadout v4 defines `maximum` and `custom` modes for the planned simulation UI:
|
||
|
||
- Maximum mode supplies each scenario input's best-case value, selects peak
|
||
conditional-effect state, and runs selected abilities on cooldown against
|
||
their configured target caps. The interface hides granular assumptions in this
|
||
mode.
|
||
- Custom mode uses the agent's encounter inputs, effect states, activation
|
||
rates, target counts, and other supported assumptions. A missing required
|
||
input produces `REQUIRES_ASSUMPTIONS` rather than a fabricated value.
|
||
|
||
Saved and shared loadouts preserve the selected mode as compatibility data. The
|
||
current Builder and shared Build UI does not expose a mode selector, read
|
||
encounter assumptions, or execute either mode. Explicit `COMING SOON` panels
|
||
reserve the player-facing space for these calculations.
|
||
|
||
## Specialization ability attacks
|
||
|
||
`specialization_ability_attacks` stores intrinsic base damage, Blast, Pierce, or
|
||
Shredding damage, direct or periodic delivery, hit or tick counts, cast time,
|
||
target cap, and charge behavior. `skill_attribute_calculation_profiles` binds
|
||
Attributes to the versioned Skill damage and cadence stages. Cooldown Recovery
|
||
changes charge and activation readiness. Duration changes the number of periodic
|
||
ticks.
|
||
|
||
Periodic delivery waits one configured tick interval before its first tick and
|
||
excludes ticks at or after the encounter boundary. The Skill engine reevaluates
|
||
active modifier windows at each delivered hit or tick. A missing tick interval
|
||
marks the attack as requiring configuration; the engine does not supply a
|
||
default cadence.
|
||
|
||
# Versioned combat calculations
|
||
|
||
`calculation_revisions` supplies one active rule set. Operators can change draft
|
||
revisions, while active and retired revisions stay immutable.
|
||
`calculation_stages` stores the ordered typed pipeline. The base-damage stage
|
||
holds the first position, and the firing-cycle stage holds the last.
|
||
`attribute_calculation_profiles` maps an Attribute to a weapon stage with its
|
||
weight, optional caps, curve, and weapon-category scope.
|
||
|
||
ISAC keeps combat revision editing, publication, calibration, assisted
|
||
suggestions, and the calculation AI assistant available so operators can
|
||
continue authoring the model. Builder and shared Build pages do not load the
|
||
active revision or consume revision, calibration, assistant, mechanic-runtime,
|
||
or combat-engine output at the current player-facing boundary.
|
||
|
||
The shared combat engine consumes weapon facts, accumulated Attributes,
|
||
calculation profiles, and mechanic outcomes. It produces four hit values, burst
|
||
DPS, reload-aware sustained DPS, encounter DPS, and stack telemetry. Custom mode
|
||
walks the expected shot timeline from RPM, magazine size, reload time, hit rate,
|
||
configured burst size, pauses between bursts, and encounter duration. It
|
||
resolves the active Attribute state at each landed shot before calculating that
|
||
shot's expected damage.
|
||
|
||
The deterministic equipment path scales a weapon definition's level-1 damage
|
||
range to the equipped item level, then the agent selects one damage value inside
|
||
that valid range. A combat invocation receives `equipped_weapon.damage` as its
|
||
base damage and consumes that number once. The combat pipeline has no
|
||
`level_scaled_damage` value and must not apply item-level scaling a second time.
|
||
|
||
The generated calculation listing names each typed pipeline stage and its bound
|
||
Attributes. The listing explains the active revision with statements such as
|
||
`TAKE equipped_weapon.damage AS base_damage`, `ADD`, `APPLY`, `WEIGHT`, and
|
||
`RETURN`. The typed stages and profiles remain authoritative. The application
|
||
neither parses nor executes the listing.
|
||
|
||
The Headshot Kill Share assumption marks the matching portion of generated kill
|
||
events for headshot-kill triggers. Rate of Fire, Magazine Size, and Reload Speed
|
||
bindings change the firing-cycle calculation. Maximum mode applies maximum
|
||
state. Custom mode can set an effect to Peak, Encounter, or Off.
|
||
|
||
The Skill engine uses a dedicated intrinsic ability-damage model. It supports
|
||
direct and periodic delivery, critical and multi-shot expectation, target caps,
|
||
per-ability activation rates, and charge recovery. Skills do not headshot.
|
||
Dynamic Talent and tree-effect windows apply to each Skill hit at its delivery
|
||
time. Encounter-triggered Skill modifiers use the primary weapon timeline until
|
||
the builder supports a combined weapon-rotation model.
|
||
|
||
The engine returns `INSUFFICIENT_DATA` with missing requirement keys when a
|
||
calculation lacks required inputs or an Attribute binding. Callers must render
|
||
that state instead of a partial result. Current player routes do not call the
|
||
engine; their planned calculation surfaces display `COMING SOON`.
|
||
|
||
`calculation_calibration_cases` stores a versioned loadout snapshot, observed
|
||
hit values, timing values, optional encounter DPS, and the model prediction.
|
||
Ready cases require the four hit values plus magazine and reload times. The
|
||
assisted suggestion action compares ready observations with their predictions
|
||
and creates a draft revision for operator review.
|
||
|
||
Rule activation requires `calculation.publish` and a confirmation. ISAC records
|
||
draft creation, draft edits, publication, calibration changes, and assisted
|
||
suggestions under the `calculation_configuration` audit category. ISAC data
|
||
export and environment synchronization include calculation rules, typed mechanic
|
||
programs, ability attacks, calibration cases, and their relationships in
|
||
dependency order.
|
||
|
||
## OS Protocol upgrade expressions and health timing
|
||
|
||
OS Protocol talents expose zero through five upgrade states. Value expressions
|
||
reference `equipment.protocolTalentUpgrades` and use the linear multiplier
|
||
`1 + upgrades / 5`. Base values resolve at upgrade zero, and scalable values
|
||
reach twice their base at upgrade five. Each outcome chooses whether to use this
|
||
multiplier, so a fixed resource cost can remain unchanged while restoration and
|
||
damage increase.
|
||
|
||
Happy Survival uses three clauses:
|
||
|
||
1. Its proximity clause records the current enemy count within 10 meters.
|
||
2. Its periodic clause restores
|
||
`min(enemyCount × 0.5%, 2.5%) × (1 + upgrades / 5)` of maximum Health. The
|
||
reducer reads the configured interval from an explicit scenario input. The
|
||
runtime caps applied restoration at missing Health.
|
||
3. Its `shot_fired` clause consumes 2% of maximum Health, then deals direct
|
||
damage equal to `0.6% × (1 + upgrades / 5)` of current Health. The
|
||
`resource_cost` outcome runs first. The damage outcome uses the remaining
|
||
current Health after that cost, rather than the pre-event Health value.
|
||
|
||
At upgrade five, the restoration becomes 1% of maximum Health per nearby enemy
|
||
up to 5%, and the shot damage becomes 1.2% of current Health. The 2% Health cost
|
||
does not scale.
|
||
|
||
# Game-data icon storage
|
||
|
||
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
|
||
SVG content and external references, 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. The
|
||
validated `/icons/game-data/[type]/[id]` endpoint serves them with immutable
|
||
cache headers and disables MIME sniffing. ISAC save actions process files before
|
||
mutation, apply upload or removal after record validation, and include 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, which lets contributors submit values and artwork in
|
||
separate dependency-safe changes. The shared `MISSING_ITEM_ICON_URL` constant
|
||
and `ItemIcon` component render `/missing-item-icon.svg` for missing artwork.
|
||
The placeholder does not enter `game_data_icons`. A later edit or proposal can
|
||
attach the real icon without recreating the canonical item.
|
||
|
||
`source_entity_type` and `source_entity_id` link each `game_data_icons` row to a
|
||
`managed_images` record. Both records share one storage key and one WebP file.
|
||
The managed record adds the searchable library name, category, tags, creator,
|
||
dimensions, and assignment state. Game-data icon saves upsert this record, keep
|
||
operator-edited metadata, and merge current entity-context tags. Removing a
|
||
source icon removes the linked library record and shared file.
|
||
|
||
The additive database migration registers older game-data icons and selects an
|
||
administrator or the earliest active account as the migration actor. ISAC and
|
||
managed-image exports accept linked storage keys. Environment synchronization
|
||
keeps the library metadata and one copy of each file.
|
||
|
||
# Oversight notification dispatch
|
||
|
||
The notification router sends Data Change review items to agents who hold
|
||
`data.approve` and Avatar Oversight review items to agents who hold
|
||
`avatars.review`. Each reviewer chooses Immediate, 15-minute, one-hour, or daily
|
||
delivery for each authorized queue. Submission creates Immediate alerts;
|
||
`oversight_notification_queue` stores consolidated alerts.
|
||
|
||
The production Compose scheduler calls the internal scheduler endpoint once per
|
||
minute. That endpoint runs the idempotent dispatcher in the application process,
|
||
which owns the embedded-database connection. Do not run a second host cron job.
|
||
|
||
The dispatcher groups due rows by reviewer, queue, cadence, and delivery 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 later queries exclude them. Existing ISAC notification delivery
|
||
preferences determine optional browser and email fan-out after the dispatcher
|
||
creates the consolidated inbox message.
|
||
|
||
## Automated ISAC system messages
|
||
|
||
Automated message definitions live in `automated_system_messages` and are
|
||
managed through ISAC → System Configuration → Automated Messages by agents with
|
||
`system.notifications.manage`. The audit log records edits under the
|
||
`system_configuration` audit category. Definitions identify a lifecycle event,
|
||
enabled state, delay in minutes, message content, primary destination, and
|
||
structured action callouts.
|
||
|
||
Password, magic-link, and Discord registration emit `account.created`. The
|
||
delivery table has a unique message/user/event/event-ID constraint, so the same
|
||
account creation cannot deliver the Welcome Agent message more than once. The
|
||
default welcome introduces News, Database, Builds, Guides, Builder, ISAC
|
||
Contributor Mode, and the SHD Discord community.
|
||
|
||
The registration request delivers Immediate definitions.
|
||
`automated_system_message_deliveries` stores definitions with a delay. The same
|
||
Compose scheduler tick runs the delayed dispatcher. Only one scheduler instance
|
||
should call the embedded-database application. Delivered rows retain both their
|
||
delivery timestamp and generated notification ID.
|