feat: expand ISAC content modeling
Validate and deploy production / validate (push) Canceled after 0s
Validate and deploy production / deploy (push) Canceled after 0s
Validate and deploy staging / validate (push) Successful in 28s
Validate and deploy staging / deploy (push) Successful in 1m3s

This commit is contained in:
jb
2026-08-03 01:23:20 -04:00
parent d9924a95b9
commit 57068b61cd
90 changed files with 9635 additions and 1270 deletions
+139 -55
View File
@@ -431,11 +431,13 @@ 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.
OS Protocols use dedicated normalized tables because an assigned Talent has six
calculated states: its base value plus five upgrades. Each protocol owns one
`os_protocol_primary_effects` record and may own one `os_protocol_talents`
record. The optional Talent owns its mechanic program through the shared
conditional-effect tables. ISAC keeps the Talent workspace collapsed until an
operator assigns or opens it, and removing an existing Talent requires explicit
confirmation.
Dedicated profile tables normalize the three secondary attributes. One
`os_protocol_secondary_profiles` record exists for each configured quality +
@@ -460,6 +462,15 @@ 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.
The eight code-defined slot types have stable UUIDs and one shared icon each.
Operators manage the artwork in the Weapon Mod registry through the game-data
icon pipeline. Each upload or removal appends a
`game_data/weapon_mod_slot_icon.updated` audit event, and contributors may send
the same action to Data Oversight. Public weapon cards use icon-only slot chips
when artwork exists, with the slot name available to screen readers and on
hover. Cards show the name when artwork is missing and retain the visible `T1+`
marker for slots unlocked at Tier 1.
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,
@@ -469,16 +480,21 @@ 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.
builder-time primary choice. `weapon_mod_quality_values` stores each
definition's 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`.
`weapon_mod_secondary_options` stores one global secondary Attribute pool for
all regular Weapon Mods. Each Attribute has Level 1 and Level 20 minimum and
maximum ranges for Specialized, Superior, and High-end quality through
`weapon_mod_secondary_quality_values`; values at intermediate levels use linear
interpolation. Secondary capacity is fixed by quality: Standard has zero,
Specialized has one, Superior has two, and High-end has two. A build selects
distinct Attributes from the pool up to that capacity. Individual Weapon Mod
records do not own secondary options or ranges. Pool updates append a
`game_data/weapon_mod_secondary_pool.updated` audit event. Mod Effects remain
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
@@ -488,42 +504,68 @@ 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.
One global Gear Mod secondary Attribute pool serves every Gear Mod definition.
Each Attribute has Level 1 and Level 20 minimum and maximum ranges for
Specialized, Superior, and High-end quality through
`gear_mod_secondary_quality_values`; values at intermediate levels use linear
interpolation. Secondary capacity is fixed by quality: Standard has zero,
Specialized has one, Superior has two, and High-end has two. A build selects
distinct Attributes from the pool up to that capacity. Individual Gear Mod
records do not own secondary options or ranges. Gear tier still determines mod
slot 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.
The `gear_mods` and `gear_mod_quality_values` tables store individual definition
data. `gear_mod_secondary_options` and `gear_mod_secondary_quality_values` store
the family-wide pool. Pool updates append a
`game_data/gear_mod_secondary_pool.updated` audit event. Individual definition
mutations retain the `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 Skill Chip definition belongs to one specialization ability. Each set owns
one 2-piece bonus and may own one 3-piece Talent with a typed conditional rule.
ISAC keeps the complete Talent workspace, including its description and mechanic
program, inside one collapsible optional section.
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
All definitions use one fixed primary profile. The profile contains Skill
Duration, Skill Radius, Skill Cooldown Recovery, and Skill Intensity, with
direct minimum and maximum values for Standard, Specialized, Superior, and
High-end. Primary values apply to the assigned ability rather than the Agent.
Builder and shared Build pages label them `NOT YET DEFINED` and exclude them
from current Agent and combat totals.
All definitions also use one shared secondary Attribute pool. Each pool entry
stores direct minimum and maximum values for Specialized, Superior, and
High-end. Standard chips have no secondary slot, Specialized chips have one, and
Superior and High-end chips have two. A configured chip selects distinct
secondary Attributes up to its quality's capacity. Skill Chips have no item
level, so neither profile uses level interpolation.
Active definitions reference an 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.
Every equipped quality counts toward the two-piece threshold. After the loadout
meets that threshold, only equipped High-end chips increase 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 resolve to the minimum, while three resolve to the maximum. The
editor stores endpoints and previews all four states.
ISAC presents the fixed primary profile and secondary pool as two shared
registry/editor cards above the definition registry. Successful profile saves
append `game_data/skill_chip_primary_profile.updated` and
`game_data/skill_chip_secondary_pool.updated`, respectively. Definition
mutations continue to capture their owned bonus, optional Talent, ability
relationship, and safe icon metadata in the `game_data` audit stream.
# Mechanic programs and loadout state
@@ -577,7 +619,23 @@ 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.
boolean that distinguishes headshots from body shots. `enemy_killed` and
`assist` may carry `values.assignedAbilityActive`; a Skill Chip Talent uses that
boolean to restrict a kill or assist trigger to its assigned ability's active
period. `skill_deployed` and `signature_ability_deployed` may carry
`values.assignedAbilityDurationSeconds` to start assigned-ability state. The
runtime expires that state before it evaluates another event at the same
timestamp.
`weapon_hit.values.aiming` records whether the Agent was aiming down sights when
the hit resolved. A producer omits the field when it lacks authoritative aiming
state; the runtime does not replace missing evidence with a scenario assumption.
Predicates compare this field explicitly with `true` or `false`; negation and
inequality cannot turn missing evidence into a match. A weapon-hit event may
also include an event-target-centered area sample. The bounded sample lists up
to 256 valid ally and enemy entity IDs with their distances from the hit target
and includes that target as an enemy at zero meters. Duplicate IDs, negative
distances, or a missing center target invalidate the evaluation.
`extra_health_changed` is Agent-scoped. Normalized timeline producers emit it
when the Agent receives an Extra Health grant, when the remaining amount
@@ -612,7 +670,10 @@ The program graph uses three child tables:
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.
target owns an independent value and expiry timer. A clause may require active
assigned-ability state and carry `procChancePercent`. The runtime evaluates
that chance after predicates and cooldown checks, before reducers, mutations,
and outcomes.
- `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
@@ -621,15 +682,37 @@ The program graph uses three child tables:
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
scaling. An outcome may carry `probabilityPercent`; each eligible outcome
rolls separately. Restoration outcomes may deliver their total immediately or
continuously over an explicit duration.
Target-scoped direct damage may use `event_target_area` targeting for an
explosion around its event target. This targeting selects valid enemies and
stores a positive radius in meters. It is valid only for `on_event` or
`after_event` outcomes. The legacy `within_radius` shape remains descriptive
targeting metadata and does not select runtime entities.
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.
When `event_target_area` damage activates, the runtime requires the triggering
event's authoritative area sample. It includes valid enemies whose distance is
less than or equal to the configured radius, sorts them by entity ID, and emits
one equal damage outcome for each entity. The outcome chance resolves once
before fan-out. A missing sample invalidates the evaluation instead of producing
single-target damage or treating the area as empty.
Custom evaluation uses a Mulberry32 stream seeded by `randomSeed`; an omitted
seed resolves from the stable mechanic program ID. The returned evaluation
reports the seed. Maximum mode resolves every nonzero clause or outcome chance
as successful. `extend_duration` changes only an active assigned ability's
remaining duration. `start_cooldown_on_max` writes an expiring Agent cooldown
pool only when an earlier mutation in the same clause transitions a capped stack
pool to its maximum.
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
@@ -793,17 +876,17 @@ 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
Loadout schema version 5 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.
the exact version 5 structure. The schema retains its simulation fields, but the
current player UI ignores them. Version 4 is obsolete prototype data. A
backed-up prototype migration removes version 4 drafts, saved builds, and
calibration snapshots instead of translating them.
## Planned player calculation modes
Loadout v4 defines `maximum` and `custom` modes for the planned simulation UI:
Loadout v5 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
@@ -925,11 +1008,12 @@ 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.
Talents, brand sets, OS Protocols, Skill Chips, Specializations, and shared
Weapon Mod slots 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