From c3a09d6577fe49b665acb1dcd48587e8fb480f8c Mon Sep 17 00:00:00 2001 From: Wickedness Date: Thu, 18 Jun 2026 12:20:04 +0900 Subject: [PATCH] Add event id validation --- docs/DATA_MODEL.md | 2 +- tools/validate_data.ps1 | 36 ++++++++++++++++++++++++++++++------ 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/docs/DATA_MODEL.md b/docs/DATA_MODEL.md index c94acda..7425906 100644 --- a/docs/DATA_MODEL.md +++ b/docs/DATA_MODEL.md @@ -462,7 +462,7 @@ Conditions may include `after_event` to stay inactive until a one-shot event has `post_battle_choices` are shown on the victory result panel after first-time scenario rewards are applied. Each choice needs a unique stable lowercase `id`, a non-empty `label`, and a `set_flags` object whose keys are stable lowercase flag ids. Choices may also grant positive `gold`, known `items`, `join_officers`, and `leave_officers`. When rewards are saved but the player has not selected a choice yet, `CampaignState.pending_post_battle_choice_scenario_id` records that scenario id so reloading the game returns to the victory choice panel instead of skipping the branch. The selected choice is saved to `CampaignState.flags`, `CampaignState.applied_post_battle_choices`, and campaign membership only when the player presses its result-panel button; completed-scenario replays do not show choices again. Save-load migration for older saves uses flags as a conservative choice signal, so avoid designing multiple choices in one scenario that can all match the same saved flag state. -Scenario `events` support `battle_start`, `battle_begin`, `turn_start`, and movement-triggered `unit_reaches_tile` triggers. `battle_begin` runs after the briefing is closed. `unit_reaches_tile` events run when a moved unit enters the zero-based `pos` cell; use `team`, `unit_ids`, or `officer_ids` to limit who can trigger the event. Actions currently include `log`, `dialogue`, `set_objective`, `grant_item`, `grant_items`, `spawn_deployment`, and `spawn_deployments`. Event `dialogue` actions use the same dialogue line format, portrait default rules, and optional `side` placement as `post_battle_dialogue`. Event item grants add to the battle inventory immediately; they persist to the campaign save only when the battle is won. +Scenario `events` support `battle_start`, `battle_begin`, `turn_start`, and movement-triggered `unit_reaches_tile` triggers. Each event needs a stable lowercase `id` that is unique within the scenario; `after_event` conditions can only reference those explicit ids. `battle_begin` runs after the briefing is closed. `unit_reaches_tile` events run when a moved unit enters the zero-based `pos` cell; use `team`, `unit_ids`, or `officer_ids` to limit who can trigger the event. Actions currently include `log`, `dialogue`, `set_objective`, `grant_item`, `grant_items`, `spawn_deployment`, and `spawn_deployments`. Event `dialogue` actions use the same dialogue line format, portrait default rules, and optional `side` placement as `post_battle_dialogue`. Event item grants add to the battle inventory immediately; they persist to the campaign save only when the battle is won. ```json { diff --git a/tools/validate_data.ps1 b/tools/validate_data.ps1 index 491775e..9b48b28 100644 --- a/tools/validate_data.ps1 +++ b/tools/validate_data.ps1 @@ -1408,6 +1408,35 @@ function Check-Post-Battle-Choices($Choices, [string]$ScenarioId) { } } +function Check-Event-Ids($Events, [string]$ScenarioId) { + $eventIds = New-Object System.Collections.Generic.HashSet[string] + if ($null -eq $Events) { + return ,$eventIds + } + if (-not ($Events -is [System.Array])) { + Fail "Scenario $ScenarioId events must be an array." + } + foreach ($event in @($Events)) { + if ($event -is [string] -or $event -is [System.Array]) { + Fail "Scenario $ScenarioId has malformed event." + } + if (-not (Has-Prop $event "id")) { + Fail "Scenario $ScenarioId event has empty id." + } + $eventId = [string](Get-Prop $event "id" "") + if ([string]::IsNullOrWhiteSpace($eventId)) { + Fail "Scenario $ScenarioId event has empty id." + } + if (-not ($eventId -match "^[a-z0-9_]+$")) { + Fail "Scenario $ScenarioId event has unstable id: $eventId" + } + if (-not $eventIds.Add($eventId)) { + Fail "Scenario $ScenarioId has duplicate event id: $eventId" + } + } + return ,$eventIds +} + function Check-Required-Flags($RequiredFlags, [string]$ScenarioId, [string]$Context) { if ($null -eq $RequiredFlags) { return @@ -1602,12 +1631,7 @@ foreach ($scenario in $campaign.scenarios) { } } - $eventIds = New-Object System.Collections.Generic.HashSet[string] - foreach ($event in (Get-Prop $scenarioData "events" @())) { - if (Has-Prop $event "id") { - $eventIds.Add([string]$event.id) | Out-Null - } - } + $eventIds = Check-Event-Ids (Get-Prop $scenarioData "events" $null) $scenarioId $knownUnitIds = Collect-Scenario-Unit-Ids $scenarioData if (Has-Prop $scenarioData "conditions") {