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
+54 -1
View File
@@ -54,6 +54,7 @@ export type ConditionalEffectClauseRow = {
event_kind?: DatabaseValue;
recipe_key?: DatabaseValue;
parameters_json: DatabaseValue;
recipe_parameters_json?: DatabaseValue;
conditions_json: DatabaseValue;
reducer_json: DatabaseValue;
weight_expression_json?: DatabaseValue;
@@ -85,6 +86,7 @@ export type ConditionalEffectOutcomeRow = {
delivery_mode?: DatabaseValue;
delivery_duration_seconds?: DatabaseValue;
targeting_json?: DatabaseValue;
probability_percent?: DatabaseValue;
value_scaling: DatabaseValue;
unit: DatabaseValue;
damage_basis: DatabaseValue;
@@ -205,6 +207,9 @@ function isValueReference(value: unknown) {
if (value.source === 'event' || value.source === 'equipment') {
return typeof value.path === 'string' && Boolean(value.path.trim());
}
if (value.source === 'assigned_ability') {
return value.path === 'active' || value.path === 'remainingDurationSeconds';
}
if (value.source === 'combat') {
return (
typeof value.path === 'string' &&
@@ -313,6 +318,19 @@ function isMutation(value: unknown): value is ClauseMutation {
isNumericExpression(value.value))
);
}
if (value.kind === 'extend_duration') {
return (
value.target === 'assigned_ability' && isNumericExpression(value.seconds)
);
}
if (value.kind === 'start_cooldown_on_max') {
return (
typeof value.stackPool === 'string' &&
Boolean(value.stackPool.trim()) &&
typeof value.cooldownPool === 'string' &&
Boolean(value.cooldownPool.trim())
);
}
return (
['add', 'set', 'consume', 'derive'].includes(value.kind) &&
typeof value.pool === 'string' &&
@@ -731,6 +749,17 @@ function parseOutcome(
if (delivery === 'instant' && durationSeconds !== null) {
errors.push(`${path} instant delivery cannot set a duration.`);
}
const probabilityPercent = number(row.probability_percent);
if (
row.probability_percent !== undefined &&
row.probability_percent !== null &&
row.probability_percent !== '' &&
(probabilityPercent === null ||
probabilityPercent < 0 ||
probabilityPercent > 100)
) {
errors.push(`${path} probability must be from 0 through 100 percent.`);
}
let targeting: MechanicOutcome['targeting'] | undefined;
if (
row.targeting_json !== null &&
@@ -766,6 +795,7 @@ function parseOutcome(
? { delivery, durationSeconds }
: {}),
...(targeting ? { targeting } : {}),
...(probabilityPercent === null ? {} : { probabilityPercent }),
...(kind === 'attribute_modifier' && nullableText(row.stat_attribute_id)
? { attributeId: text(row.stat_attribute_id) }
: {}),
@@ -903,7 +933,7 @@ export function adaptConditionalEffectProgram(
continue;
}
const parametersJson = parseJson(
row.parameters_json,
row.recipe_parameters_json ?? row.parameters_json,
`${path} parameters`,
errors,
);
@@ -976,6 +1006,25 @@ export function adaptConditionalEffectProgram(
}
}
const recipeKey = nullableText(row.recipe_key);
const procChancePercent = parametersJson.value.procChancePercent;
if (
procChancePercent !== undefined &&
(!finite(procChancePercent) ||
procChancePercent < 0 ||
procChancePercent > 100)
) {
errors.push(`${path} proc chance must be from 0 through 100 percent.`);
}
const requiresAssignedAbilityActive =
parametersJson.value.requiresAssignedAbilityActive;
if (
requiresAssignedAbilityActive !== undefined &&
typeof requiresAssignedAbilityActive !== 'boolean'
) {
errors.push(
`${path} assigned-ability activity requirement must be a boolean.`,
);
}
clauses.push({
id,
label: text(row.label) || text(row.clause_key) || undefined,
@@ -989,6 +1038,10 @@ export function adaptConditionalEffectProgram(
? {}
: { cooldownSeconds: number(row.cooldown_seconds) as number }),
...(weight === undefined ? {} : { weight }),
...(typeof procChancePercent === 'number' ? { procChancePercent } : {}),
...(typeof requiresAssignedAbilityActive === 'boolean'
? { requiresAssignedAbilityActive }
: {}),
mutations: mutationsJson.value,
...(clauseOutcomes.length ? { outcomes: clauseOutcomes } : {}),
});