Add territory and player packed event import
This commit is contained in:
parent
f73234cb99
commit
ca208f74e0
26 changed files with 1912 additions and 272 deletions
18
README.md
18
README.md
|
|
@ -15,14 +15,16 @@ frontier is broader real grouped-descriptor coverage on top of the existing save
|
||||||
overlay-import, compact-control, and symbolic company-target workflows. The runtime already carries
|
overlay-import, compact-control, and symbolic company-target workflows. The runtime already carries
|
||||||
selected-company and controller-role context through overlay imports, and real descriptors `2`
|
selected-company and controller-role context through overlay imports, and real descriptors `2`
|
||||||
`Company Cash`, `13` `Deactivate Company`, and `16` `Company Track Pieces Buildable` now parse and
|
`Company Cash`, `13` `Deactivate Company`, and `16` `Company Track Pieces Buildable` now parse and
|
||||||
execute through the ordinary runtime path. Synthetic packed records still exercise the same service
|
execute through the ordinary runtime path, and descriptor `1` `Player Cash` now joins that batch
|
||||||
engine without a parallel packed executor. The first grounded condition-side unlock now exists for
|
through the same service engine. Synthetic packed records still exercise the same runtime without a
|
||||||
negative-sentinel `raw_condition_id = -1` company scopes, and the first ordinary nonnegative
|
parallel packed executor. The first grounded condition-side unlock now exists for negative-sentinel
|
||||||
condition batch now executes too: numeric-threshold company finance, company track, aggregate
|
`raw_condition_id = -1` company scopes, and the first ordinary nonnegative condition batch now
|
||||||
territory track, and company-territory track rows can import through overlay-backed runtime
|
executes too: numeric-threshold company finance, company track, aggregate territory track, and
|
||||||
context. Named-territory bindings and player-owned condition scope still remain blocked. Mixed
|
company-territory track rows can import through overlay-backed runtime context. Exact
|
||||||
supported/unsupported real rows still stay parity-only. The PE32 hook remains useful as capture and
|
named-territory binding now executes, while descriptor `3` `Territory - Allow All` remains the
|
||||||
integration tooling, but it is no longer the main execution milestone.
|
explicit parity-only descriptor frontier. Mixed supported/unsupported real rows still stay
|
||||||
|
parity-only. The PE32 hook remains useful as capture and integration tooling, but it is no longer
|
||||||
|
the main execution milestone.
|
||||||
|
|
||||||
## Project Docs
|
## Project Docs
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -174,6 +174,8 @@ mod tests {
|
||||||
metadata: BTreeMap::new(),
|
metadata: BTreeMap::new(),
|
||||||
companies: Vec::new(),
|
companies: Vec::new(),
|
||||||
selected_company_id: None,
|
selected_company_id: None,
|
||||||
|
players: Vec::new(),
|
||||||
|
selected_player_id: None,
|
||||||
territories: Vec::new(),
|
territories: Vec::new(),
|
||||||
company_territory_track_piece_counts: Vec::new(),
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
packed_event_collection: None,
|
packed_event_collection: None,
|
||||||
|
|
@ -339,6 +341,8 @@ mod tests {
|
||||||
available_track_laying_capacity: None,
|
available_track_laying_capacity: None,
|
||||||
}],
|
}],
|
||||||
selected_company_id: Some(42),
|
selected_company_id: Some(42),
|
||||||
|
players: Vec::new(),
|
||||||
|
selected_player_id: None,
|
||||||
territories: Vec::new(),
|
territories: Vec::new(),
|
||||||
company_territory_track_piece_counts: Vec::new(),
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
packed_event_collection: None,
|
packed_event_collection: None,
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,8 @@ pub struct ExpectedRuntimeSummary {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub active_company_count: Option<usize>,
|
pub active_company_count: Option<usize>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
pub player_count: Option<usize>,
|
||||||
|
#[serde(default)]
|
||||||
pub territory_count: Option<usize>,
|
pub territory_count: Option<usize>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub company_territory_track_count: Option<usize>,
|
pub company_territory_track_count: Option<usize>,
|
||||||
|
|
@ -86,8 +88,16 @@ pub struct ExpectedRuntimeSummary {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub packed_event_blocked_missing_company_role_context_count: Option<usize>,
|
pub packed_event_blocked_missing_company_role_context_count: Option<usize>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
pub packed_event_blocked_missing_player_context_count: Option<usize>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub packed_event_blocked_missing_player_selection_context_count: Option<usize>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub packed_event_blocked_missing_player_role_context_count: Option<usize>,
|
||||||
|
#[serde(default)]
|
||||||
pub packed_event_blocked_missing_condition_context_count: Option<usize>,
|
pub packed_event_blocked_missing_condition_context_count: Option<usize>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
pub packed_event_blocked_missing_player_condition_context_count: Option<usize>,
|
||||||
|
#[serde(default)]
|
||||||
pub packed_event_blocked_company_condition_scope_disabled_count: Option<usize>,
|
pub packed_event_blocked_company_condition_scope_disabled_count: Option<usize>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub packed_event_blocked_player_condition_scope_count: Option<usize>,
|
pub packed_event_blocked_player_condition_scope_count: Option<usize>,
|
||||||
|
|
@ -104,6 +114,8 @@ pub struct ExpectedRuntimeSummary {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub packed_event_blocked_unmapped_real_descriptor_count: Option<usize>,
|
pub packed_event_blocked_unmapped_real_descriptor_count: Option<usize>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
pub packed_event_blocked_territory_policy_descriptor_count: Option<usize>,
|
||||||
|
#[serde(default)]
|
||||||
pub packed_event_blocked_structural_only_count: Option<usize>,
|
pub packed_event_blocked_structural_only_count: Option<usize>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub event_runtime_record_count: Option<usize>,
|
pub event_runtime_record_count: Option<usize>,
|
||||||
|
|
@ -353,6 +365,14 @@ impl ExpectedRuntimeSummary {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if let Some(count) = self.player_count {
|
||||||
|
if actual.player_count != count {
|
||||||
|
mismatches.push(format!(
|
||||||
|
"player_count mismatch: expected {count}, got {}",
|
||||||
|
actual.player_count
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
if let Some(count) = self.territory_count {
|
if let Some(count) = self.territory_count {
|
||||||
if actual.territory_count != count {
|
if actual.territory_count != count {
|
||||||
mismatches.push(format!(
|
mismatches.push(format!(
|
||||||
|
|
@ -441,6 +461,30 @@ impl ExpectedRuntimeSummary {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if let Some(count) = self.packed_event_blocked_missing_player_context_count {
|
||||||
|
if actual.packed_event_blocked_missing_player_context_count != count {
|
||||||
|
mismatches.push(format!(
|
||||||
|
"packed_event_blocked_missing_player_context_count mismatch: expected {count}, got {}",
|
||||||
|
actual.packed_event_blocked_missing_player_context_count
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(count) = self.packed_event_blocked_missing_player_selection_context_count {
|
||||||
|
if actual.packed_event_blocked_missing_player_selection_context_count != count {
|
||||||
|
mismatches.push(format!(
|
||||||
|
"packed_event_blocked_missing_player_selection_context_count mismatch: expected {count}, got {}",
|
||||||
|
actual.packed_event_blocked_missing_player_selection_context_count
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(count) = self.packed_event_blocked_missing_player_role_context_count {
|
||||||
|
if actual.packed_event_blocked_missing_player_role_context_count != count {
|
||||||
|
mismatches.push(format!(
|
||||||
|
"packed_event_blocked_missing_player_role_context_count mismatch: expected {count}, got {}",
|
||||||
|
actual.packed_event_blocked_missing_player_role_context_count
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
if let Some(count) = self.packed_event_blocked_missing_condition_context_count {
|
if let Some(count) = self.packed_event_blocked_missing_condition_context_count {
|
||||||
if actual.packed_event_blocked_missing_condition_context_count != count {
|
if actual.packed_event_blocked_missing_condition_context_count != count {
|
||||||
mismatches.push(format!(
|
mismatches.push(format!(
|
||||||
|
|
@ -449,6 +493,14 @@ impl ExpectedRuntimeSummary {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if let Some(count) = self.packed_event_blocked_missing_player_condition_context_count {
|
||||||
|
if actual.packed_event_blocked_missing_player_condition_context_count != count {
|
||||||
|
mismatches.push(format!(
|
||||||
|
"packed_event_blocked_missing_player_condition_context_count mismatch: expected {count}, got {}",
|
||||||
|
actual.packed_event_blocked_missing_player_condition_context_count
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
if let Some(count) = self.packed_event_blocked_company_condition_scope_disabled_count {
|
if let Some(count) = self.packed_event_blocked_company_condition_scope_disabled_count {
|
||||||
if actual.packed_event_blocked_company_condition_scope_disabled_count != count {
|
if actual.packed_event_blocked_company_condition_scope_disabled_count != count {
|
||||||
mismatches.push(format!(
|
mismatches.push(format!(
|
||||||
|
|
@ -513,6 +565,14 @@ impl ExpectedRuntimeSummary {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if let Some(count) = self.packed_event_blocked_territory_policy_descriptor_count {
|
||||||
|
if actual.packed_event_blocked_territory_policy_descriptor_count != count {
|
||||||
|
mismatches.push(format!(
|
||||||
|
"packed_event_blocked_territory_policy_descriptor_count mismatch: expected {count}, got {}",
|
||||||
|
actual.packed_event_blocked_territory_policy_descriptor_count
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
if let Some(count) = self.packed_event_blocked_structural_only_count {
|
if let Some(count) = self.packed_event_blocked_structural_only_count {
|
||||||
if actual.packed_event_blocked_structural_only_count != count {
|
if actual.packed_event_blocked_structural_only_count != count {
|
||||||
mismatches.push(format!(
|
mismatches.push(format!(
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -41,9 +41,10 @@ pub use runtime::{
|
||||||
RuntimeEventRecordTemplate, RuntimePackedEventCollectionSummary,
|
RuntimeEventRecordTemplate, RuntimePackedEventCollectionSummary,
|
||||||
RuntimePackedEventCompactControlSummary, RuntimePackedEventConditionRowSummary,
|
RuntimePackedEventCompactControlSummary, RuntimePackedEventConditionRowSummary,
|
||||||
RuntimePackedEventGroupedEffectRowSummary, RuntimePackedEventNegativeSentinelScopeSummary,
|
RuntimePackedEventGroupedEffectRowSummary, RuntimePackedEventNegativeSentinelScopeSummary,
|
||||||
RuntimePackedEventRecordSummary, RuntimePackedEventTextBandSummary,
|
RuntimePackedEventRecordSummary, RuntimePackedEventTextBandSummary, RuntimePlayer,
|
||||||
RuntimePlayerConditionTestScope, RuntimeSaveProfileState, RuntimeServiceState, RuntimeState,
|
RuntimePlayerConditionTestScope, RuntimePlayerTarget, RuntimeSaveProfileState,
|
||||||
RuntimeTerritory, RuntimeTerritoryMetric, RuntimeTrackMetric, RuntimeTrackPieceCounts,
|
RuntimeServiceState, RuntimeState, RuntimeTerritory, RuntimeTerritoryMetric,
|
||||||
|
RuntimeTerritoryTarget, RuntimeTrackMetric, RuntimeTrackPieceCounts,
|
||||||
RuntimeWorldRestoreState,
|
RuntimeWorldRestoreState,
|
||||||
};
|
};
|
||||||
pub use smp::{
|
pub use smp::{
|
||||||
|
|
|
||||||
|
|
@ -94,6 +94,8 @@ mod tests {
|
||||||
metadata: BTreeMap::new(),
|
metadata: BTreeMap::new(),
|
||||||
companies: Vec::new(),
|
companies: Vec::new(),
|
||||||
selected_company_id: None,
|
selected_company_id: None,
|
||||||
|
players: Vec::new(),
|
||||||
|
selected_player_id: None,
|
||||||
territories: Vec::new(),
|
territories: Vec::new(),
|
||||||
company_territory_track_piece_counts: Vec::new(),
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
packed_event_collection: None,
|
packed_event_collection: None,
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,8 @@ pub struct RuntimeTrackPieceCounts {
|
||||||
pub struct RuntimeTerritory {
|
pub struct RuntimeTerritory {
|
||||||
pub territory_id: u32,
|
pub territory_id: u32,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
pub name: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
pub track_piece_counts: RuntimeTrackPieceCounts,
|
pub track_piece_counts: RuntimeTrackPieceCounts,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -67,6 +69,20 @@ pub struct RuntimeCompanyTerritoryTrackPieceCount {
|
||||||
pub track_piece_counts: RuntimeTrackPieceCounts,
|
pub track_piece_counts: RuntimeTrackPieceCounts,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn runtime_player_default_active() -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub struct RuntimePlayer {
|
||||||
|
pub player_id: u32,
|
||||||
|
pub current_cash: i64,
|
||||||
|
#[serde(default = "runtime_player_default_active")]
|
||||||
|
pub active: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
pub controller_kind: RuntimeCompanyControllerKind,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
#[serde(tag = "kind", rename_all = "snake_case")]
|
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||||
pub enum RuntimeCompanyTarget {
|
pub enum RuntimeCompanyTarget {
|
||||||
|
|
@ -78,6 +94,24 @@ pub enum RuntimeCompanyTarget {
|
||||||
Ids { ids: Vec<u32> },
|
Ids { ids: Vec<u32> },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||||
|
pub enum RuntimePlayerTarget {
|
||||||
|
AllActive,
|
||||||
|
HumanPlayers,
|
||||||
|
AiPlayers,
|
||||||
|
SelectedPlayer,
|
||||||
|
ConditionTruePlayer,
|
||||||
|
Ids { ids: Vec<u32> },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||||
|
pub enum RuntimeTerritoryTarget {
|
||||||
|
AllTerritories,
|
||||||
|
Ids { ids: Vec<u32> },
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum RuntimeCompanyConditionTestScope {
|
pub enum RuntimeCompanyConditionTestScope {
|
||||||
|
|
@ -158,12 +192,14 @@ pub enum RuntimeCondition {
|
||||||
value: i64,
|
value: i64,
|
||||||
},
|
},
|
||||||
TerritoryNumericThreshold {
|
TerritoryNumericThreshold {
|
||||||
|
target: RuntimeTerritoryTarget,
|
||||||
metric: RuntimeTerritoryMetric,
|
metric: RuntimeTerritoryMetric,
|
||||||
comparator: RuntimeConditionComparator,
|
comparator: RuntimeConditionComparator,
|
||||||
value: i64,
|
value: i64,
|
||||||
},
|
},
|
||||||
CompanyTerritoryNumericThreshold {
|
CompanyTerritoryNumericThreshold {
|
||||||
target: RuntimeCompanyTarget,
|
target: RuntimeCompanyTarget,
|
||||||
|
territory: RuntimeTerritoryTarget,
|
||||||
metric: RuntimeTrackMetric,
|
metric: RuntimeTrackMetric,
|
||||||
comparator: RuntimeConditionComparator,
|
comparator: RuntimeConditionComparator,
|
||||||
value: i64,
|
value: i64,
|
||||||
|
|
@ -181,6 +217,10 @@ pub enum RuntimeEffect {
|
||||||
target: RuntimeCompanyTarget,
|
target: RuntimeCompanyTarget,
|
||||||
value: i64,
|
value: i64,
|
||||||
},
|
},
|
||||||
|
SetPlayerCash {
|
||||||
|
target: RuntimePlayerTarget,
|
||||||
|
value: i64,
|
||||||
|
},
|
||||||
DeactivateCompany {
|
DeactivateCompany {
|
||||||
target: RuntimeCompanyTarget,
|
target: RuntimeCompanyTarget,
|
||||||
},
|
},
|
||||||
|
|
@ -508,6 +548,10 @@ pub struct RuntimeState {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub selected_company_id: Option<u32>,
|
pub selected_company_id: Option<u32>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
pub players: Vec<RuntimePlayer>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub selected_player_id: Option<u32>,
|
||||||
|
#[serde(default)]
|
||||||
pub territories: Vec<RuntimeTerritory>,
|
pub territories: Vec<RuntimeTerritory>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub company_territory_track_piece_counts: Vec<RuntimeCompanyTerritoryTrackPieceCount>,
|
pub company_territory_track_piece_counts: Vec<RuntimeCompanyTerritoryTrackPieceCount>,
|
||||||
|
|
@ -552,11 +596,48 @@ impl RuntimeState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut seen_player_ids = BTreeSet::new();
|
||||||
|
let mut active_player_ids = BTreeSet::new();
|
||||||
|
for player in &self.players {
|
||||||
|
if !seen_player_ids.insert(player.player_id) {
|
||||||
|
return Err(format!("duplicate player_id {}", player.player_id));
|
||||||
|
}
|
||||||
|
if player.active {
|
||||||
|
active_player_ids.insert(player.player_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(selected_player_id) = self.selected_player_id {
|
||||||
|
if !seen_player_ids.contains(&selected_player_id) {
|
||||||
|
return Err(format!(
|
||||||
|
"selected_player_id {} does not reference a live player",
|
||||||
|
selected_player_id
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if !active_player_ids.contains(&selected_player_id) {
|
||||||
|
return Err(format!(
|
||||||
|
"selected_player_id {} must reference an active player",
|
||||||
|
selected_player_id
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut seen_territory_ids = BTreeSet::new();
|
let mut seen_territory_ids = BTreeSet::new();
|
||||||
|
let mut seen_territory_names = BTreeSet::new();
|
||||||
for territory in &self.territories {
|
for territory in &self.territories {
|
||||||
if !seen_territory_ids.insert(territory.territory_id) {
|
if !seen_territory_ids.insert(territory.territory_id) {
|
||||||
return Err(format!("duplicate territory_id {}", territory.territory_id));
|
return Err(format!("duplicate territory_id {}", territory.territory_id));
|
||||||
}
|
}
|
||||||
|
if let Some(name) = territory.name.as_deref() {
|
||||||
|
if name.trim().is_empty() {
|
||||||
|
return Err(format!(
|
||||||
|
"territory_id {} has an empty name",
|
||||||
|
territory.territory_id
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if !seen_territory_names.insert(name.to_string()) {
|
||||||
|
return Err(format!("duplicate territory name {name:?}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for entry in &self.company_territory_track_piece_counts {
|
for entry in &self.company_territory_track_piece_counts {
|
||||||
if !seen_company_ids.contains(&entry.company_id) {
|
if !seen_company_ids.contains(&entry.company_id) {
|
||||||
|
|
@ -579,7 +660,8 @@ impl RuntimeState {
|
||||||
return Err(format!("duplicate record_id {}", record.record_id));
|
return Err(format!("duplicate record_id {}", record.record_id));
|
||||||
}
|
}
|
||||||
for (condition_index, condition) in record.conditions.iter().enumerate() {
|
for (condition_index, condition) in record.conditions.iter().enumerate() {
|
||||||
validate_runtime_condition(condition, &seen_company_ids).map_err(|err| {
|
validate_runtime_condition(condition, &seen_company_ids, &seen_territory_ids)
|
||||||
|
.map_err(|err| {
|
||||||
format!(
|
format!(
|
||||||
"event_runtime_records[record_id={}].conditions[{condition_index}] {err}",
|
"event_runtime_records[record_id={}].conditions[{condition_index}] {err}",
|
||||||
record.record_id
|
record.record_id
|
||||||
|
|
@ -587,7 +669,13 @@ impl RuntimeState {
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
for (effect_index, effect) in record.effects.iter().enumerate() {
|
for (effect_index, effect) in record.effects.iter().enumerate() {
|
||||||
validate_runtime_effect(effect, &seen_company_ids).map_err(|err| {
|
validate_runtime_effect(
|
||||||
|
effect,
|
||||||
|
&seen_company_ids,
|
||||||
|
&seen_player_ids,
|
||||||
|
&seen_territory_ids,
|
||||||
|
)
|
||||||
|
.map_err(|err| {
|
||||||
format!(
|
format!(
|
||||||
"event_runtime_records[record_id={}].effects[{effect_index}] {err}",
|
"event_runtime_records[record_id={}].effects[{effect_index}] {err}",
|
||||||
record.record_id
|
record.record_id
|
||||||
|
|
@ -912,6 +1000,8 @@ impl RuntimeState {
|
||||||
fn validate_runtime_effect(
|
fn validate_runtime_effect(
|
||||||
effect: &RuntimeEffect,
|
effect: &RuntimeEffect,
|
||||||
valid_company_ids: &BTreeSet<u32>,
|
valid_company_ids: &BTreeSet<u32>,
|
||||||
|
valid_player_ids: &BTreeSet<u32>,
|
||||||
|
valid_territory_ids: &BTreeSet<u32>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
match effect {
|
match effect {
|
||||||
RuntimeEffect::SetWorldFlag { key, .. } => {
|
RuntimeEffect::SetWorldFlag { key, .. } => {
|
||||||
|
|
@ -926,6 +1016,9 @@ fn validate_runtime_effect(
|
||||||
| RuntimeEffect::AdjustCompanyDebt { target, .. } => {
|
| RuntimeEffect::AdjustCompanyDebt { target, .. } => {
|
||||||
validate_company_target(target, valid_company_ids)?;
|
validate_company_target(target, valid_company_ids)?;
|
||||||
}
|
}
|
||||||
|
RuntimeEffect::SetPlayerCash { target, .. } => {
|
||||||
|
validate_player_target(target, valid_player_ids)?;
|
||||||
|
}
|
||||||
RuntimeEffect::SetCandidateAvailability { name, .. } => {
|
RuntimeEffect::SetCandidateAvailability { name, .. } => {
|
||||||
if name.trim().is_empty() {
|
if name.trim().is_empty() {
|
||||||
return Err("name must not be empty".to_string());
|
return Err("name must not be empty".to_string());
|
||||||
|
|
@ -937,7 +1030,12 @@ fn validate_runtime_effect(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RuntimeEffect::AppendEventRecord { record } => {
|
RuntimeEffect::AppendEventRecord { record } => {
|
||||||
validate_event_record_template(record, valid_company_ids)?;
|
validate_event_record_template(
|
||||||
|
record,
|
||||||
|
valid_company_ids,
|
||||||
|
valid_player_ids,
|
||||||
|
valid_territory_ids,
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
RuntimeEffect::ActivateEventRecord { .. }
|
RuntimeEffect::ActivateEventRecord { .. }
|
||||||
| RuntimeEffect::DeactivateEventRecord { .. }
|
| RuntimeEffect::DeactivateEventRecord { .. }
|
||||||
|
|
@ -950,17 +1048,27 @@ fn validate_runtime_effect(
|
||||||
fn validate_event_record_template(
|
fn validate_event_record_template(
|
||||||
record: &RuntimeEventRecordTemplate,
|
record: &RuntimeEventRecordTemplate,
|
||||||
valid_company_ids: &BTreeSet<u32>,
|
valid_company_ids: &BTreeSet<u32>,
|
||||||
|
valid_player_ids: &BTreeSet<u32>,
|
||||||
|
valid_territory_ids: &BTreeSet<u32>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
for (condition_index, condition) in record.conditions.iter().enumerate() {
|
for (condition_index, condition) in record.conditions.iter().enumerate() {
|
||||||
validate_runtime_condition(condition, valid_company_ids).map_err(|err| {
|
validate_runtime_condition(condition, valid_company_ids, valid_territory_ids).map_err(
|
||||||
|
|err| {
|
||||||
format!(
|
format!(
|
||||||
"template record_id={}.conditions[{condition_index}] {err}",
|
"template record_id={}.conditions[{condition_index}] {err}",
|
||||||
record.record_id
|
record.record_id
|
||||||
)
|
)
|
||||||
})?;
|
},
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
for (effect_index, effect) in record.effects.iter().enumerate() {
|
for (effect_index, effect) in record.effects.iter().enumerate() {
|
||||||
validate_runtime_effect(effect, valid_company_ids).map_err(|err| {
|
validate_runtime_effect(
|
||||||
|
effect,
|
||||||
|
valid_company_ids,
|
||||||
|
valid_player_ids,
|
||||||
|
valid_territory_ids,
|
||||||
|
)
|
||||||
|
.map_err(|err| {
|
||||||
format!(
|
format!(
|
||||||
"template record_id={}.effects[{effect_index}] {err}",
|
"template record_id={}.effects[{effect_index}] {err}",
|
||||||
record.record_id
|
record.record_id
|
||||||
|
|
@ -974,13 +1082,23 @@ fn validate_event_record_template(
|
||||||
fn validate_runtime_condition(
|
fn validate_runtime_condition(
|
||||||
condition: &RuntimeCondition,
|
condition: &RuntimeCondition,
|
||||||
valid_company_ids: &BTreeSet<u32>,
|
valid_company_ids: &BTreeSet<u32>,
|
||||||
|
valid_territory_ids: &BTreeSet<u32>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
match condition {
|
match condition {
|
||||||
RuntimeCondition::CompanyNumericThreshold { target, .. }
|
RuntimeCondition::CompanyNumericThreshold { target, .. } => {
|
||||||
| RuntimeCondition::CompanyTerritoryNumericThreshold { target, .. } => {
|
|
||||||
validate_company_target(target, valid_company_ids)
|
validate_company_target(target, valid_company_ids)
|
||||||
}
|
}
|
||||||
RuntimeCondition::TerritoryNumericThreshold { .. } => Ok(()),
|
RuntimeCondition::TerritoryNumericThreshold { target, .. } => {
|
||||||
|
validate_territory_target(target, valid_territory_ids)
|
||||||
|
}
|
||||||
|
RuntimeCondition::CompanyTerritoryNumericThreshold {
|
||||||
|
target,
|
||||||
|
territory,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
validate_company_target(target, valid_company_ids)?;
|
||||||
|
validate_territory_target(territory, valid_territory_ids)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1008,6 +1126,52 @@ fn validate_company_target(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn validate_player_target(
|
||||||
|
target: &RuntimePlayerTarget,
|
||||||
|
valid_player_ids: &BTreeSet<u32>,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
match target {
|
||||||
|
RuntimePlayerTarget::AllActive
|
||||||
|
| RuntimePlayerTarget::HumanPlayers
|
||||||
|
| RuntimePlayerTarget::AiPlayers
|
||||||
|
| RuntimePlayerTarget::SelectedPlayer
|
||||||
|
| RuntimePlayerTarget::ConditionTruePlayer => Ok(()),
|
||||||
|
RuntimePlayerTarget::Ids { ids } => {
|
||||||
|
if ids.is_empty() {
|
||||||
|
return Err("target ids must not be empty".to_string());
|
||||||
|
}
|
||||||
|
for player_id in ids {
|
||||||
|
if !valid_player_ids.contains(player_id) {
|
||||||
|
return Err(format!("target references unknown player_id {player_id}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_territory_target(
|
||||||
|
target: &RuntimeTerritoryTarget,
|
||||||
|
valid_territory_ids: &BTreeSet<u32>,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
match target {
|
||||||
|
RuntimeTerritoryTarget::AllTerritories => Ok(()),
|
||||||
|
RuntimeTerritoryTarget::Ids { ids } => {
|
||||||
|
if ids.is_empty() {
|
||||||
|
return Err("territory target ids must not be empty".to_string());
|
||||||
|
}
|
||||||
|
for territory_id in ids {
|
||||||
|
if !valid_territory_ids.contains(territory_id) {
|
||||||
|
return Err(format!(
|
||||||
|
"territory target references unknown territory_id {territory_id}"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
@ -1050,6 +1214,8 @@ mod tests {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
selected_company_id: None,
|
selected_company_id: None,
|
||||||
|
players: Vec::new(),
|
||||||
|
selected_player_id: None,
|
||||||
territories: Vec::new(),
|
territories: Vec::new(),
|
||||||
company_territory_track_piece_counts: Vec::new(),
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
packed_event_collection: None,
|
packed_event_collection: None,
|
||||||
|
|
@ -1099,6 +1265,8 @@ mod tests {
|
||||||
metadata: BTreeMap::new(),
|
metadata: BTreeMap::new(),
|
||||||
companies: Vec::new(),
|
companies: Vec::new(),
|
||||||
selected_company_id: None,
|
selected_company_id: None,
|
||||||
|
players: Vec::new(),
|
||||||
|
selected_player_id: None,
|
||||||
territories: Vec::new(),
|
territories: Vec::new(),
|
||||||
company_territory_track_piece_counts: Vec::new(),
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
packed_event_collection: None,
|
packed_event_collection: None,
|
||||||
|
|
@ -1136,6 +1304,8 @@ mod tests {
|
||||||
controller_kind: RuntimeCompanyControllerKind::Unknown,
|
controller_kind: RuntimeCompanyControllerKind::Unknown,
|
||||||
}],
|
}],
|
||||||
selected_company_id: None,
|
selected_company_id: None,
|
||||||
|
players: Vec::new(),
|
||||||
|
selected_player_id: None,
|
||||||
territories: Vec::new(),
|
territories: Vec::new(),
|
||||||
company_territory_track_piece_counts: Vec::new(),
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
packed_event_collection: None,
|
packed_event_collection: None,
|
||||||
|
|
@ -1186,6 +1356,8 @@ mod tests {
|
||||||
controller_kind: RuntimeCompanyControllerKind::Unknown,
|
controller_kind: RuntimeCompanyControllerKind::Unknown,
|
||||||
}],
|
}],
|
||||||
selected_company_id: None,
|
selected_company_id: None,
|
||||||
|
players: Vec::new(),
|
||||||
|
selected_player_id: None,
|
||||||
territories: Vec::new(),
|
territories: Vec::new(),
|
||||||
company_territory_track_piece_counts: Vec::new(),
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
packed_event_collection: None,
|
packed_event_collection: None,
|
||||||
|
|
@ -1236,6 +1408,8 @@ mod tests {
|
||||||
metadata: BTreeMap::new(),
|
metadata: BTreeMap::new(),
|
||||||
companies: Vec::new(),
|
companies: Vec::new(),
|
||||||
selected_company_id: None,
|
selected_company_id: None,
|
||||||
|
players: Vec::new(),
|
||||||
|
selected_player_id: None,
|
||||||
territories: Vec::new(),
|
territories: Vec::new(),
|
||||||
company_territory_track_piece_counts: Vec::new(),
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
packed_event_collection: Some(RuntimePackedEventCollectionSummary {
|
packed_event_collection: Some(RuntimePackedEventCollectionSummary {
|
||||||
|
|
@ -1337,6 +1511,8 @@ mod tests {
|
||||||
controller_kind: RuntimeCompanyControllerKind::Human,
|
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||||
}],
|
}],
|
||||||
selected_company_id: Some(2),
|
selected_company_id: Some(2),
|
||||||
|
players: Vec::new(),
|
||||||
|
selected_player_id: None,
|
||||||
territories: Vec::new(),
|
territories: Vec::new(),
|
||||||
company_territory_track_piece_counts: Vec::new(),
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
packed_event_collection: None,
|
packed_event_collection: None,
|
||||||
|
|
@ -1374,6 +1550,8 @@ mod tests {
|
||||||
controller_kind: RuntimeCompanyControllerKind::Human,
|
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||||
}],
|
}],
|
||||||
selected_company_id: Some(1),
|
selected_company_id: Some(1),
|
||||||
|
players: Vec::new(),
|
||||||
|
selected_player_id: None,
|
||||||
territories: Vec::new(),
|
territories: Vec::new(),
|
||||||
company_territory_track_piece_counts: Vec::new(),
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
packed_event_collection: None,
|
packed_event_collection: None,
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,8 @@ use sha2::{Digest, Sha256};
|
||||||
use crate::{
|
use crate::{
|
||||||
RuntimeCompanyConditionTestScope, RuntimeCompanyMetric, RuntimeCompanyTarget,
|
RuntimeCompanyConditionTestScope, RuntimeCompanyMetric, RuntimeCompanyTarget,
|
||||||
RuntimeCondition, RuntimeConditionComparator, RuntimeEffect, RuntimeEventRecordTemplate,
|
RuntimeCondition, RuntimeConditionComparator, RuntimeEffect, RuntimeEventRecordTemplate,
|
||||||
RuntimePlayerConditionTestScope, RuntimeTerritoryMetric, RuntimeTrackMetric,
|
RuntimePlayerConditionTestScope, RuntimePlayerTarget, RuntimeTerritoryMetric,
|
||||||
|
RuntimeTerritoryTarget, RuntimeTrackMetric,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const SMP_FOUR_SIDECAR_BYTE_PLANES_MIN_BUNDLE_VERSION: u32 = 0x03ec;
|
pub const SMP_FOUR_SIDECAR_BYTE_PLANES_MIN_BUNDLE_VERSION: u32 = 0x03ec;
|
||||||
|
|
@ -132,7 +133,7 @@ const REAL_GROUPED_EFFECT_DESCRIPTOR_METADATA: [RealGroupedEffectDescriptorMetad
|
||||||
label: "Player Cash",
|
label: "Player Cash",
|
||||||
target_mask_bits: 0x02,
|
target_mask_bits: 0x02,
|
||||||
parameter_family: "player_finance_scalar",
|
parameter_family: "player_finance_scalar",
|
||||||
executable_in_runtime: false,
|
executable_in_runtime: true,
|
||||||
},
|
},
|
||||||
RealGroupedEffectDescriptorMetadata {
|
RealGroupedEffectDescriptorMetadata {
|
||||||
descriptor_id: 2,
|
descriptor_id: 2,
|
||||||
|
|
@ -2471,6 +2472,7 @@ fn decode_real_condition_row(
|
||||||
negative_sentinel_scope
|
negative_sentinel_scope
|
||||||
.filter(|scope| scope.territory_scope_selector_is_0x63)
|
.filter(|scope| scope.territory_scope_selector_is_0x63)
|
||||||
.map(|_| RuntimeCondition::TerritoryNumericThreshold {
|
.map(|_| RuntimeCondition::TerritoryNumericThreshold {
|
||||||
|
target: RuntimeTerritoryTarget::AllTerritories,
|
||||||
metric,
|
metric,
|
||||||
comparator,
|
comparator,
|
||||||
value,
|
value,
|
||||||
|
|
@ -2481,6 +2483,7 @@ fn decode_real_condition_row(
|
||||||
.filter(|scope| scope.territory_scope_selector_is_0x63)
|
.filter(|scope| scope.territory_scope_selector_is_0x63)
|
||||||
.map(|_| RuntimeCondition::CompanyTerritoryNumericThreshold {
|
.map(|_| RuntimeCondition::CompanyTerritoryNumericThreshold {
|
||||||
target: RuntimeCompanyTarget::ConditionTrueCompany,
|
target: RuntimeCompanyTarget::ConditionTrueCompany,
|
||||||
|
territory: RuntimeTerritoryTarget::AllTerritories,
|
||||||
metric,
|
metric,
|
||||||
comparator,
|
comparator,
|
||||||
value,
|
value,
|
||||||
|
|
@ -2588,19 +2591,25 @@ fn decode_real_grouped_effect_action(
|
||||||
.grouped_target_scope_ordinals_0x7fb
|
.grouped_target_scope_ordinals_0x7fb
|
||||||
.get(row.group_index)
|
.get(row.group_index)
|
||||||
.copied()?;
|
.copied()?;
|
||||||
let target = match target_scope_ordinal {
|
|
||||||
0 => RuntimeCompanyTarget::ConditionTrueCompany,
|
if descriptor_metadata.executable_in_runtime
|
||||||
1 => RuntimeCompanyTarget::SelectedCompany,
|
&& descriptor_metadata.descriptor_id == 1
|
||||||
2 => RuntimeCompanyTarget::HumanCompanies,
|
&& row.opcode == 8
|
||||||
3 => RuntimeCompanyTarget::AiCompanies,
|
&& row.row_shape == "multivalue_scalar"
|
||||||
_ => return None,
|
{
|
||||||
};
|
let target = real_grouped_player_target(target_scope_ordinal)?;
|
||||||
|
return Some(RuntimeEffect::SetPlayerCash {
|
||||||
|
target,
|
||||||
|
value: i64::from(row.raw_scalar_value),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if descriptor_metadata.executable_in_runtime
|
if descriptor_metadata.executable_in_runtime
|
||||||
&& descriptor_metadata.descriptor_id == 2
|
&& descriptor_metadata.descriptor_id == 2
|
||||||
&& row.opcode == 8
|
&& row.opcode == 8
|
||||||
&& row.row_shape == "multivalue_scalar"
|
&& row.row_shape == "multivalue_scalar"
|
||||||
{
|
{
|
||||||
|
let target = real_grouped_company_target(target_scope_ordinal)?;
|
||||||
return Some(RuntimeEffect::SetCompanyCash {
|
return Some(RuntimeEffect::SetCompanyCash {
|
||||||
target,
|
target,
|
||||||
value: i64::from(row.raw_scalar_value),
|
value: i64::from(row.raw_scalar_value),
|
||||||
|
|
@ -2612,6 +2621,7 @@ fn decode_real_grouped_effect_action(
|
||||||
&& row.row_shape == "bool_toggle"
|
&& row.row_shape == "bool_toggle"
|
||||||
&& row.raw_scalar_value != 0
|
&& row.raw_scalar_value != 0
|
||||||
{
|
{
|
||||||
|
let target = real_grouped_company_target(target_scope_ordinal)?;
|
||||||
return Some(RuntimeEffect::DeactivateCompany { target });
|
return Some(RuntimeEffect::DeactivateCompany { target });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2620,6 +2630,7 @@ fn decode_real_grouped_effect_action(
|
||||||
&& row.row_shape == "scalar_assignment"
|
&& row.row_shape == "scalar_assignment"
|
||||||
&& row.raw_scalar_value >= 0
|
&& row.raw_scalar_value >= 0
|
||||||
{
|
{
|
||||||
|
let target = real_grouped_company_target(target_scope_ordinal)?;
|
||||||
return Some(RuntimeEffect::SetCompanyTrackLayingCapacity {
|
return Some(RuntimeEffect::SetCompanyTrackLayingCapacity {
|
||||||
target,
|
target,
|
||||||
value: Some(row.raw_scalar_value as u32),
|
value: Some(row.raw_scalar_value as u32),
|
||||||
|
|
@ -2629,6 +2640,26 @@ fn decode_real_grouped_effect_action(
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn real_grouped_company_target(ordinal: u8) -> Option<RuntimeCompanyTarget> {
|
||||||
|
match ordinal {
|
||||||
|
0 => Some(RuntimeCompanyTarget::ConditionTrueCompany),
|
||||||
|
1 => Some(RuntimeCompanyTarget::SelectedCompany),
|
||||||
|
2 => Some(RuntimeCompanyTarget::HumanCompanies),
|
||||||
|
3 => Some(RuntimeCompanyTarget::AiCompanies),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn real_grouped_player_target(ordinal: u8) -> Option<RuntimePlayerTarget> {
|
||||||
|
match ordinal {
|
||||||
|
0 => Some(RuntimePlayerTarget::ConditionTruePlayer),
|
||||||
|
1 => Some(RuntimePlayerTarget::SelectedPlayer),
|
||||||
|
2 => Some(RuntimePlayerTarget::HumanPlayers),
|
||||||
|
3 => Some(RuntimePlayerTarget::AiPlayers),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_synthetic_packed_event_action(bytes: &[u8], cursor: &mut usize) -> Option<RuntimeEffect> {
|
fn parse_synthetic_packed_event_action(bytes: &[u8], cursor: &mut usize) -> Option<RuntimeEffect> {
|
||||||
let opcode = read_u8_at(bytes, *cursor)?;
|
let opcode = read_u8_at(bytes, *cursor)?;
|
||||||
*cursor += 1;
|
*cursor += 1;
|
||||||
|
|
@ -2784,6 +2815,15 @@ fn runtime_effect_supported_for_save_import(effect: &RuntimeEffect) -> bool {
|
||||||
| RuntimeEffect::ActivateEventRecord { .. }
|
| RuntimeEffect::ActivateEventRecord { .. }
|
||||||
| RuntimeEffect::DeactivateEventRecord { .. }
|
| RuntimeEffect::DeactivateEventRecord { .. }
|
||||||
| RuntimeEffect::RemoveEventRecord { .. } => true,
|
| RuntimeEffect::RemoveEventRecord { .. } => true,
|
||||||
|
RuntimeEffect::SetPlayerCash { target, .. } => matches!(
|
||||||
|
target,
|
||||||
|
RuntimePlayerTarget::AllActive
|
||||||
|
| RuntimePlayerTarget::Ids { .. }
|
||||||
|
| RuntimePlayerTarget::HumanPlayers
|
||||||
|
| RuntimePlayerTarget::AiPlayers
|
||||||
|
| RuntimePlayerTarget::SelectedPlayer
|
||||||
|
| RuntimePlayerTarget::ConditionTruePlayer
|
||||||
|
),
|
||||||
RuntimeEffect::SetCompanyCash { target, .. }
|
RuntimeEffect::SetCompanyCash { target, .. }
|
||||||
| RuntimeEffect::AdjustCompanyCash { target, .. }
|
| RuntimeEffect::AdjustCompanyCash { target, .. }
|
||||||
| RuntimeEffect::AdjustCompanyDebt { target, .. } => matches!(
|
| RuntimeEffect::AdjustCompanyDebt { target, .. } => matches!(
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,9 @@ use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
RuntimeCompanyControllerKind, RuntimeCompanyMetric, RuntimeCompanyTarget, RuntimeCondition,
|
RuntimeCompanyControllerKind, RuntimeCompanyMetric, RuntimeCompanyTarget, RuntimeCondition,
|
||||||
RuntimeConditionComparator, RuntimeEffect, RuntimeEventRecordTemplate, RuntimeState,
|
RuntimeConditionComparator, RuntimeEffect, RuntimeEventRecordTemplate, RuntimePlayerTarget,
|
||||||
RuntimeSummary, RuntimeTerritoryMetric, RuntimeTrackMetric, RuntimeTrackPieceCounts,
|
RuntimeState, RuntimeSummary, RuntimeTerritoryMetric, RuntimeTerritoryTarget,
|
||||||
|
RuntimeTrackMetric, RuntimeTrackPieceCounts,
|
||||||
calendar::BoundaryEventKind,
|
calendar::BoundaryEventKind,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -48,6 +49,7 @@ pub struct ServiceEvent {
|
||||||
pub serviced_record_ids: Vec<u32>,
|
pub serviced_record_ids: Vec<u32>,
|
||||||
pub applied_effect_count: u32,
|
pub applied_effect_count: u32,
|
||||||
pub mutated_company_ids: Vec<u32>,
|
pub mutated_company_ids: Vec<u32>,
|
||||||
|
pub mutated_player_ids: Vec<u32>,
|
||||||
pub appended_record_ids: Vec<u32>,
|
pub appended_record_ids: Vec<u32>,
|
||||||
pub activated_record_ids: Vec<u32>,
|
pub activated_record_ids: Vec<u32>,
|
||||||
pub deactivated_record_ids: Vec<u32>,
|
pub deactivated_record_ids: Vec<u32>,
|
||||||
|
|
@ -84,6 +86,7 @@ struct AppliedEffectsSummary {
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
struct ResolvedConditionContext {
|
struct ResolvedConditionContext {
|
||||||
matching_company_ids: BTreeSet<u32>,
|
matching_company_ids: BTreeSet<u32>,
|
||||||
|
matching_player_ids: BTreeSet<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn execute_step_command(
|
pub fn execute_step_command(
|
||||||
|
|
@ -205,6 +208,7 @@ fn service_trigger_kind(
|
||||||
let mut serviced_record_ids = Vec::new();
|
let mut serviced_record_ids = Vec::new();
|
||||||
let mut applied_effect_count = 0_u32;
|
let mut applied_effect_count = 0_u32;
|
||||||
let mut mutated_company_ids = BTreeSet::new();
|
let mut mutated_company_ids = BTreeSet::new();
|
||||||
|
let mut mutated_player_ids = BTreeSet::new();
|
||||||
let mut appended_record_ids = Vec::new();
|
let mut appended_record_ids = Vec::new();
|
||||||
let mut activated_record_ids = Vec::new();
|
let mut activated_record_ids = Vec::new();
|
||||||
let mut deactivated_record_ids = Vec::new();
|
let mut deactivated_record_ids = Vec::new();
|
||||||
|
|
@ -245,6 +249,7 @@ fn service_trigger_kind(
|
||||||
&record_effects,
|
&record_effects,
|
||||||
&condition_context,
|
&condition_context,
|
||||||
&mut mutated_company_ids,
|
&mut mutated_company_ids,
|
||||||
|
&mut mutated_player_ids,
|
||||||
&mut staged_event_graph_mutations,
|
&mut staged_event_graph_mutations,
|
||||||
)?;
|
)?;
|
||||||
applied_effect_count += effect_summary.applied_effect_count;
|
applied_effect_count += effect_summary.applied_effect_count;
|
||||||
|
|
@ -276,6 +281,7 @@ fn service_trigger_kind(
|
||||||
serviced_record_ids,
|
serviced_record_ids,
|
||||||
applied_effect_count,
|
applied_effect_count,
|
||||||
mutated_company_ids: mutated_company_ids.into_iter().collect(),
|
mutated_company_ids: mutated_company_ids.into_iter().collect(),
|
||||||
|
mutated_player_ids: mutated_player_ids.into_iter().collect(),
|
||||||
appended_record_ids,
|
appended_record_ids,
|
||||||
activated_record_ids,
|
activated_record_ids,
|
||||||
deactivated_record_ids,
|
deactivated_record_ids,
|
||||||
|
|
@ -296,6 +302,7 @@ fn apply_runtime_effects(
|
||||||
effects: &[RuntimeEffect],
|
effects: &[RuntimeEffect],
|
||||||
condition_context: &ResolvedConditionContext,
|
condition_context: &ResolvedConditionContext,
|
||||||
mutated_company_ids: &mut BTreeSet<u32>,
|
mutated_company_ids: &mut BTreeSet<u32>,
|
||||||
|
mutated_player_ids: &mut BTreeSet<u32>,
|
||||||
staged_event_graph_mutations: &mut Vec<EventGraphMutation>,
|
staged_event_graph_mutations: &mut Vec<EventGraphMutation>,
|
||||||
) -> Result<AppliedEffectsSummary, String> {
|
) -> Result<AppliedEffectsSummary, String> {
|
||||||
let mut summary = AppliedEffectsSummary::default();
|
let mut summary = AppliedEffectsSummary::default();
|
||||||
|
|
@ -319,6 +326,20 @@ fn apply_runtime_effects(
|
||||||
mutated_company_ids.insert(company_id);
|
mutated_company_ids.insert(company_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
RuntimeEffect::SetPlayerCash { target, value } => {
|
||||||
|
let player_ids = resolve_player_target_ids(state, target, condition_context)?;
|
||||||
|
for player_id in player_ids {
|
||||||
|
let player = state
|
||||||
|
.players
|
||||||
|
.iter_mut()
|
||||||
|
.find(|player| player.player_id == player_id)
|
||||||
|
.ok_or_else(|| {
|
||||||
|
format!("missing player_id {player_id} while applying cash effect")
|
||||||
|
})?;
|
||||||
|
player.current_cash = *value;
|
||||||
|
mutated_player_ids.insert(player_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
RuntimeEffect::DeactivateCompany { target } => {
|
RuntimeEffect::DeactivateCompany { target } => {
|
||||||
let company_ids = resolve_company_target_ids(state, target, condition_context)?;
|
let company_ids = resolve_company_target_ids(state, target, condition_context)?;
|
||||||
for company_id in company_ids {
|
for company_id in company_ids {
|
||||||
|
|
@ -520,21 +541,25 @@ fn evaluate_record_conditions(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RuntimeCondition::TerritoryNumericThreshold {
|
RuntimeCondition::TerritoryNumericThreshold {
|
||||||
|
target,
|
||||||
metric,
|
metric,
|
||||||
comparator,
|
comparator,
|
||||||
value,
|
value,
|
||||||
} => {
|
} => {
|
||||||
let actual = territory_metric_value(state, *metric);
|
let territory_ids = resolve_territory_target_ids(state, target)?;
|
||||||
|
let actual = territory_metric_value(state, &territory_ids, *metric);
|
||||||
if !compare_condition_value(actual, *comparator, *value) {
|
if !compare_condition_value(actual, *comparator, *value) {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RuntimeCondition::CompanyTerritoryNumericThreshold {
|
RuntimeCondition::CompanyTerritoryNumericThreshold {
|
||||||
target,
|
target,
|
||||||
|
territory,
|
||||||
metric,
|
metric,
|
||||||
comparator,
|
comparator,
|
||||||
value,
|
value,
|
||||||
} => {
|
} => {
|
||||||
|
let territory_ids = resolve_territory_target_ids(state, territory)?;
|
||||||
let resolved = resolve_company_target_ids(
|
let resolved = resolve_company_target_ids(
|
||||||
state,
|
state,
|
||||||
target,
|
target,
|
||||||
|
|
@ -544,7 +569,12 @@ fn evaluate_record_conditions(
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|company_id| {
|
.filter(|company_id| {
|
||||||
compare_condition_value(
|
compare_condition_value(
|
||||||
company_territory_metric_value(state, *company_id, *metric),
|
company_territory_metric_value(
|
||||||
|
state,
|
||||||
|
*company_id,
|
||||||
|
&territory_ids,
|
||||||
|
*metric,
|
||||||
|
),
|
||||||
*comparator,
|
*comparator,
|
||||||
*value,
|
*value,
|
||||||
)
|
)
|
||||||
|
|
@ -563,6 +593,7 @@ fn evaluate_record_conditions(
|
||||||
|
|
||||||
Ok(Some(ResolvedConditionContext {
|
Ok(Some(ResolvedConditionContext {
|
||||||
matching_company_ids: company_matches.unwrap_or_default(),
|
matching_company_ids: company_matches.unwrap_or_default(),
|
||||||
|
matching_player_ids: BTreeSet::new(),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -676,6 +707,119 @@ fn resolve_company_target_ids(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn resolve_player_target_ids(
|
||||||
|
state: &RuntimeState,
|
||||||
|
target: &RuntimePlayerTarget,
|
||||||
|
condition_context: &ResolvedConditionContext,
|
||||||
|
) -> Result<Vec<u32>, String> {
|
||||||
|
match target {
|
||||||
|
RuntimePlayerTarget::AllActive => Ok(state
|
||||||
|
.players
|
||||||
|
.iter()
|
||||||
|
.filter(|player| player.active)
|
||||||
|
.map(|player| player.player_id)
|
||||||
|
.collect()),
|
||||||
|
RuntimePlayerTarget::Ids { ids } => {
|
||||||
|
let known_ids = state
|
||||||
|
.players
|
||||||
|
.iter()
|
||||||
|
.map(|player| player.player_id)
|
||||||
|
.collect::<BTreeSet<_>>();
|
||||||
|
for player_id in ids {
|
||||||
|
if !known_ids.contains(player_id) {
|
||||||
|
return Err(format!("target references unknown player_id {player_id}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(ids.clone())
|
||||||
|
}
|
||||||
|
RuntimePlayerTarget::HumanPlayers => {
|
||||||
|
if state
|
||||||
|
.players
|
||||||
|
.iter()
|
||||||
|
.any(|player| player.controller_kind == RuntimeCompanyControllerKind::Unknown)
|
||||||
|
{
|
||||||
|
return Err(
|
||||||
|
"target requires player role context but at least one player has unknown controller_kind"
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(state
|
||||||
|
.players
|
||||||
|
.iter()
|
||||||
|
.filter(|player| {
|
||||||
|
player.active && player.controller_kind == RuntimeCompanyControllerKind::Human
|
||||||
|
})
|
||||||
|
.map(|player| player.player_id)
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
RuntimePlayerTarget::AiPlayers => {
|
||||||
|
if state
|
||||||
|
.players
|
||||||
|
.iter()
|
||||||
|
.any(|player| player.controller_kind == RuntimeCompanyControllerKind::Unknown)
|
||||||
|
{
|
||||||
|
return Err(
|
||||||
|
"target requires player role context but at least one player has unknown controller_kind"
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(state
|
||||||
|
.players
|
||||||
|
.iter()
|
||||||
|
.filter(|player| {
|
||||||
|
player.active && player.controller_kind == RuntimeCompanyControllerKind::Ai
|
||||||
|
})
|
||||||
|
.map(|player| player.player_id)
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
RuntimePlayerTarget::SelectedPlayer => {
|
||||||
|
let selected_player_id = state
|
||||||
|
.selected_player_id
|
||||||
|
.ok_or_else(|| "target requires selected_player_id context".to_string())?;
|
||||||
|
if state
|
||||||
|
.players
|
||||||
|
.iter()
|
||||||
|
.any(|player| player.player_id == selected_player_id && player.active)
|
||||||
|
{
|
||||||
|
Ok(vec![selected_player_id])
|
||||||
|
} else {
|
||||||
|
Err("target requires selected_player_id to reference an active player".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RuntimePlayerTarget::ConditionTruePlayer => {
|
||||||
|
if condition_context.matching_player_ids.is_empty() {
|
||||||
|
Err("target requires player condition-evaluation context".to_string())
|
||||||
|
} else {
|
||||||
|
Ok(condition_context.matching_player_ids.iter().copied().collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_territory_target_ids(
|
||||||
|
state: &RuntimeState,
|
||||||
|
target: &RuntimeTerritoryTarget,
|
||||||
|
) -> Result<Vec<u32>, String> {
|
||||||
|
match target {
|
||||||
|
RuntimeTerritoryTarget::AllTerritories => {
|
||||||
|
Ok(state.territories.iter().map(|territory| territory.territory_id).collect())
|
||||||
|
}
|
||||||
|
RuntimeTerritoryTarget::Ids { ids } => {
|
||||||
|
let known_ids = state
|
||||||
|
.territories
|
||||||
|
.iter()
|
||||||
|
.map(|territory| territory.territory_id)
|
||||||
|
.collect::<BTreeSet<_>>();
|
||||||
|
for territory_id in ids {
|
||||||
|
if !known_ids.contains(territory_id) {
|
||||||
|
return Err(format!("territory target references unknown territory_id {territory_id}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(ids.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn company_metric_value(company: &crate::RuntimeCompany, metric: RuntimeCompanyMetric) -> i64 {
|
fn company_metric_value(company: &crate::RuntimeCompany, metric: RuntimeCompanyMetric) -> i64 {
|
||||||
match metric {
|
match metric {
|
||||||
RuntimeCompanyMetric::CurrentCash => company.current_cash,
|
RuntimeCompanyMetric::CurrentCash => company.current_cash,
|
||||||
|
|
@ -697,9 +841,14 @@ fn company_metric_value(company: &crate::RuntimeCompany, metric: RuntimeCompanyM
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn territory_metric_value(state: &RuntimeState, metric: RuntimeTerritoryMetric) -> i64 {
|
fn territory_metric_value(
|
||||||
|
state: &RuntimeState,
|
||||||
|
territory_ids: &[u32],
|
||||||
|
metric: RuntimeTerritoryMetric,
|
||||||
|
) -> i64 {
|
||||||
state.territories
|
state.territories
|
||||||
.iter()
|
.iter()
|
||||||
|
.filter(|territory| territory_ids.contains(&territory.territory_id))
|
||||||
.map(|territory| {
|
.map(|territory| {
|
||||||
track_piece_metric_value(
|
track_piece_metric_value(
|
||||||
territory.track_piece_counts,
|
territory.track_piece_counts,
|
||||||
|
|
@ -712,11 +861,12 @@ fn territory_metric_value(state: &RuntimeState, metric: RuntimeTerritoryMetric)
|
||||||
fn company_territory_metric_value(
|
fn company_territory_metric_value(
|
||||||
state: &RuntimeState,
|
state: &RuntimeState,
|
||||||
company_id: u32,
|
company_id: u32,
|
||||||
|
territory_ids: &[u32],
|
||||||
metric: RuntimeTrackMetric,
|
metric: RuntimeTrackMetric,
|
||||||
) -> i64 {
|
) -> i64 {
|
||||||
state.company_territory_track_piece_counts
|
state.company_territory_track_piece_counts
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|entry| entry.company_id == company_id)
|
.filter(|entry| entry.company_id == company_id && territory_ids.contains(&entry.territory_id))
|
||||||
.map(|entry| track_piece_metric_value(entry.track_piece_counts, metric))
|
.map(|entry| track_piece_metric_value(entry.track_piece_counts, metric))
|
||||||
.sum()
|
.sum()
|
||||||
}
|
}
|
||||||
|
|
@ -805,6 +955,8 @@ mod tests {
|
||||||
available_track_laying_capacity: None,
|
available_track_laying_capacity: None,
|
||||||
}],
|
}],
|
||||||
selected_company_id: None,
|
selected_company_id: None,
|
||||||
|
players: Vec::new(),
|
||||||
|
selected_player_id: None,
|
||||||
territories: Vec::new(),
|
territories: Vec::new(),
|
||||||
company_territory_track_piece_counts: Vec::new(),
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
packed_event_collection: None,
|
packed_event_collection: None,
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ pub struct RuntimeSummary {
|
||||||
pub metadata_count: usize,
|
pub metadata_count: usize,
|
||||||
pub company_count: usize,
|
pub company_count: usize,
|
||||||
pub active_company_count: usize,
|
pub active_company_count: usize,
|
||||||
|
pub player_count: usize,
|
||||||
pub territory_count: usize,
|
pub territory_count: usize,
|
||||||
pub company_territory_track_count: usize,
|
pub company_territory_track_count: usize,
|
||||||
pub packed_event_collection_present: bool,
|
pub packed_event_collection_present: bool,
|
||||||
|
|
@ -40,7 +41,11 @@ pub struct RuntimeSummary {
|
||||||
pub packed_event_blocked_missing_company_context_count: usize,
|
pub packed_event_blocked_missing_company_context_count: usize,
|
||||||
pub packed_event_blocked_missing_selection_context_count: usize,
|
pub packed_event_blocked_missing_selection_context_count: usize,
|
||||||
pub packed_event_blocked_missing_company_role_context_count: usize,
|
pub packed_event_blocked_missing_company_role_context_count: usize,
|
||||||
|
pub packed_event_blocked_missing_player_context_count: usize,
|
||||||
|
pub packed_event_blocked_missing_player_selection_context_count: usize,
|
||||||
|
pub packed_event_blocked_missing_player_role_context_count: usize,
|
||||||
pub packed_event_blocked_missing_condition_context_count: usize,
|
pub packed_event_blocked_missing_condition_context_count: usize,
|
||||||
|
pub packed_event_blocked_missing_player_condition_context_count: usize,
|
||||||
pub packed_event_blocked_company_condition_scope_disabled_count: usize,
|
pub packed_event_blocked_company_condition_scope_disabled_count: usize,
|
||||||
pub packed_event_blocked_player_condition_scope_count: usize,
|
pub packed_event_blocked_player_condition_scope_count: usize,
|
||||||
pub packed_event_blocked_territory_condition_scope_count: usize,
|
pub packed_event_blocked_territory_condition_scope_count: usize,
|
||||||
|
|
@ -49,6 +54,7 @@ pub struct RuntimeSummary {
|
||||||
pub packed_event_blocked_unmapped_ordinary_condition_count: usize,
|
pub packed_event_blocked_unmapped_ordinary_condition_count: usize,
|
||||||
pub packed_event_blocked_missing_compact_control_count: usize,
|
pub packed_event_blocked_missing_compact_control_count: usize,
|
||||||
pub packed_event_blocked_unmapped_real_descriptor_count: usize,
|
pub packed_event_blocked_unmapped_real_descriptor_count: usize,
|
||||||
|
pub packed_event_blocked_territory_policy_descriptor_count: usize,
|
||||||
pub packed_event_blocked_structural_only_count: usize,
|
pub packed_event_blocked_structural_only_count: usize,
|
||||||
pub event_runtime_record_count: usize,
|
pub event_runtime_record_count: usize,
|
||||||
pub candidate_availability_count: usize,
|
pub candidate_availability_count: usize,
|
||||||
|
|
@ -136,6 +142,7 @@ impl RuntimeSummary {
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|company| company.active)
|
.filter(|company| company.active)
|
||||||
.count(),
|
.count(),
|
||||||
|
player_count: state.players.len(),
|
||||||
territory_count: state.territories.len(),
|
territory_count: state.territories.len(),
|
||||||
company_territory_track_count: state.company_territory_track_piece_counts.len(),
|
company_territory_track_count: state.company_territory_track_piece_counts.len(),
|
||||||
packed_event_collection_present: state.packed_event_collection.is_some(),
|
packed_event_collection_present: state.packed_event_collection.is_some(),
|
||||||
|
|
@ -218,6 +225,48 @@ impl RuntimeSummary {
|
||||||
.count()
|
.count()
|
||||||
})
|
})
|
||||||
.unwrap_or(0),
|
.unwrap_or(0),
|
||||||
|
packed_event_blocked_missing_player_context_count: state
|
||||||
|
.packed_event_collection
|
||||||
|
.as_ref()
|
||||||
|
.map(|summary| {
|
||||||
|
summary
|
||||||
|
.records
|
||||||
|
.iter()
|
||||||
|
.filter(|record| {
|
||||||
|
record.import_outcome.as_deref()
|
||||||
|
== Some("blocked_missing_player_context")
|
||||||
|
})
|
||||||
|
.count()
|
||||||
|
})
|
||||||
|
.unwrap_or(0),
|
||||||
|
packed_event_blocked_missing_player_selection_context_count: state
|
||||||
|
.packed_event_collection
|
||||||
|
.as_ref()
|
||||||
|
.map(|summary| {
|
||||||
|
summary
|
||||||
|
.records
|
||||||
|
.iter()
|
||||||
|
.filter(|record| {
|
||||||
|
record.import_outcome.as_deref()
|
||||||
|
== Some("blocked_missing_player_selection_context")
|
||||||
|
})
|
||||||
|
.count()
|
||||||
|
})
|
||||||
|
.unwrap_or(0),
|
||||||
|
packed_event_blocked_missing_player_role_context_count: state
|
||||||
|
.packed_event_collection
|
||||||
|
.as_ref()
|
||||||
|
.map(|summary| {
|
||||||
|
summary
|
||||||
|
.records
|
||||||
|
.iter()
|
||||||
|
.filter(|record| {
|
||||||
|
record.import_outcome.as_deref()
|
||||||
|
== Some("blocked_missing_player_role_context")
|
||||||
|
})
|
||||||
|
.count()
|
||||||
|
})
|
||||||
|
.unwrap_or(0),
|
||||||
packed_event_blocked_missing_condition_context_count: state
|
packed_event_blocked_missing_condition_context_count: state
|
||||||
.packed_event_collection
|
.packed_event_collection
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
|
@ -232,6 +281,20 @@ impl RuntimeSummary {
|
||||||
.count()
|
.count()
|
||||||
})
|
})
|
||||||
.unwrap_or(0),
|
.unwrap_or(0),
|
||||||
|
packed_event_blocked_missing_player_condition_context_count: state
|
||||||
|
.packed_event_collection
|
||||||
|
.as_ref()
|
||||||
|
.map(|summary| {
|
||||||
|
summary
|
||||||
|
.records
|
||||||
|
.iter()
|
||||||
|
.filter(|record| {
|
||||||
|
record.import_outcome.as_deref()
|
||||||
|
== Some("blocked_missing_player_condition_context")
|
||||||
|
})
|
||||||
|
.count()
|
||||||
|
})
|
||||||
|
.unwrap_or(0),
|
||||||
packed_event_blocked_company_condition_scope_disabled_count: state
|
packed_event_blocked_company_condition_scope_disabled_count: state
|
||||||
.packed_event_collection
|
.packed_event_collection
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
|
@ -344,6 +407,20 @@ impl RuntimeSummary {
|
||||||
.count()
|
.count()
|
||||||
})
|
})
|
||||||
.unwrap_or(0),
|
.unwrap_or(0),
|
||||||
|
packed_event_blocked_territory_policy_descriptor_count: state
|
||||||
|
.packed_event_collection
|
||||||
|
.as_ref()
|
||||||
|
.map(|summary| {
|
||||||
|
summary
|
||||||
|
.records
|
||||||
|
.iter()
|
||||||
|
.filter(|record| {
|
||||||
|
record.import_outcome.as_deref()
|
||||||
|
== Some("blocked_territory_policy_descriptor")
|
||||||
|
})
|
||||||
|
.count()
|
||||||
|
})
|
||||||
|
.unwrap_or(0),
|
||||||
packed_event_blocked_structural_only_count: state
|
packed_event_blocked_structural_only_count: state
|
||||||
.packed_event_collection
|
.packed_event_collection
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
|
@ -425,6 +502,8 @@ mod tests {
|
||||||
metadata: BTreeMap::new(),
|
metadata: BTreeMap::new(),
|
||||||
companies: Vec::new(),
|
companies: Vec::new(),
|
||||||
selected_company_id: None,
|
selected_company_id: None,
|
||||||
|
players: Vec::new(),
|
||||||
|
selected_player_id: None,
|
||||||
territories: Vec::new(),
|
territories: Vec::new(),
|
||||||
company_territory_track_piece_counts: Vec::new(),
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
packed_event_collection: Some(RuntimePackedEventCollectionSummary {
|
packed_event_collection: Some(RuntimePackedEventCollectionSummary {
|
||||||
|
|
@ -650,6 +729,8 @@ mod tests {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
selected_company_id: None,
|
selected_company_id: None,
|
||||||
|
players: Vec::new(),
|
||||||
|
selected_player_id: None,
|
||||||
territories: Vec::new(),
|
territories: Vec::new(),
|
||||||
company_territory_track_piece_counts: Vec::new(),
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
packed_event_collection: None,
|
packed_event_collection: None,
|
||||||
|
|
|
||||||
|
|
@ -81,14 +81,18 @@ The highest-value next passes are now:
|
||||||
first company-scoped batch already parses, summarizes, and executes through the ordinary runtime
|
first company-scoped batch already parses, summarizes, and executes through the ordinary runtime
|
||||||
path when overlay context resolves its symbolic company scope: descriptor `2` `Company Cash`,
|
path when overlay context resolves its symbolic company scope: descriptor `2` `Company Cash`,
|
||||||
descriptor `13` `Deactivate Company`, and descriptor `16` `Company Track Pieces Buildable`
|
descriptor `13` `Deactivate Company`, and descriptor `16` `Company Track Pieces Buildable`
|
||||||
|
- descriptor `1` `Player Cash` now joins that executable real batch through the same ordinary
|
||||||
|
runtime path, backed by the minimal player runtime and overlay-import context
|
||||||
- widen real packed-event executable coverage descriptor by descriptor after identity, target mask,
|
- widen real packed-event executable coverage descriptor by descriptor after identity, target mask,
|
||||||
and normalized effect semantics are all grounded, not just after row framing is parsed
|
and normalized effect semantics are all grounded, not just after row framing is parsed
|
||||||
- the first grounded condition-side unlock now exists for negative-sentinel `raw_condition_id = -1`
|
- the first grounded condition-side unlock now exists for negative-sentinel `raw_condition_id = -1`
|
||||||
company scopes, and the first ordinary nonnegative condition batch now executes too: numeric
|
company scopes, and the first ordinary nonnegative condition batch now executes too: numeric
|
||||||
thresholds for company finance, company track, aggregate territory track, and company-territory
|
thresholds for company finance, company track, aggregate territory track, and company-territory
|
||||||
track
|
track
|
||||||
- named-territory ordinary rows and player-owned condition scope are still the remaining condition
|
- exact named-territory binding now executes too, while named-territory no-match cases remain the
|
||||||
frontier, and mixed supported/unsupported real rows stay parity-only
|
explicit binding blocker frontier
|
||||||
|
- descriptor `3` `Territory - Allow All` remains the explicit parity-only descriptor frontier, and
|
||||||
|
mixed supported/unsupported real rows still stay parity-only
|
||||||
- keep in mind that the current local `.gms` corpus still exports with no packed event collection,
|
- keep in mind that the current local `.gms` corpus still exports with no packed event collection,
|
||||||
so real descriptor mapping needs to stay plumbing-first until better captures exist
|
so real descriptor mapping needs to stay plumbing-first until better captures exist
|
||||||
- use `rrt-hook` primarily as optional capture or integration tooling, not as the first execution
|
- use `rrt-hook` primarily as optional capture or integration tooling, not as the first execution
|
||||||
|
|
|
||||||
|
|
@ -33,17 +33,20 @@ Implemented today:
|
||||||
descriptor `13` = `Deactivate Company`, and descriptor `16` = `Company Track Pieces Buildable`
|
descriptor `13` = `Deactivate Company`, and descriptor `16` = `Company Track Pieces Buildable`
|
||||||
- the first grounded condition-side unlock now exists for real packed rows: negative-sentinel
|
- the first grounded condition-side unlock now exists for real packed rows: negative-sentinel
|
||||||
`raw_condition_id = -1` company scope lowers `condition_true_company` into normalized company
|
`raw_condition_id = -1` company scope lowers `condition_true_company` into normalized company
|
||||||
targets during import, while player and territory scope variants remain parity-visible and
|
targets during import
|
||||||
explicitly blocked
|
|
||||||
- the first ordinary nonnegative condition-id batch now executes too: numeric-threshold company
|
- the first ordinary nonnegative condition-id batch now executes too: numeric-threshold company
|
||||||
finance, company track, aggregate territory track, and company-territory track rows can import
|
finance, company track, aggregate territory track, and company-territory track rows can import
|
||||||
through overlay-backed runtime context, while named-territory bindings stay parity-only and
|
through overlay-backed runtime context
|
||||||
player-owned condition scope still has no runtime owner
|
- exact named-territory binding now lowers candidate-name ordinary rows onto tracked territory
|
||||||
|
names, a minimal player runtime now carries selected-player and role context, and real descriptor
|
||||||
|
`1` = `Player Cash` now imports and executes through the ordinary runtime path
|
||||||
|
- descriptor `3` = `Territory - Allow All` now has an explicit parity-only frontier label instead
|
||||||
|
of hiding behind the generic unmapped bucket
|
||||||
|
|
||||||
That means the next implementation work is breadth, not bootstrap. The recommended next slice is
|
That means the next implementation work is breadth, not bootstrap. The recommended next slice is
|
||||||
broader ordinary condition-id coverage beyond numeric thresholds, plus runtime ownership for the
|
broader ordinary condition-id coverage beyond numeric thresholds, wider real grouped-descriptor
|
||||||
still-blocked player-scoped and named-territory condition families, alongside wider real
|
coverage beyond the current company/player cash batch, and later executable territory-policy
|
||||||
grouped-descriptor coverage beyond the current company-scoped batch.
|
mutation once those semantics are grounded strongly enough to avoid guessing.
|
||||||
|
|
||||||
## Why This Boundary
|
## Why This Boundary
|
||||||
|
|
||||||
|
|
@ -241,9 +244,10 @@ Current status:
|
||||||
- overlay-backed captured-runtime inputs now provide enough runtime company context for symbolic
|
- overlay-backed captured-runtime inputs now provide enough runtime company context for symbolic
|
||||||
selected-company and controller-role scopes without inventing company state from save bytes alone
|
selected-company and controller-role scopes without inventing company state from save bytes alone
|
||||||
- aggregate territory context and company-territory track counters now flow through tracked overlay
|
- aggregate territory context and company-territory track counters now flow through tracked overlay
|
||||||
snapshots, so the remaining gap is broader ordinary condition-id coverage beyond numeric
|
snapshots, named-territory binding now executes on exact matches, and a minimal player runtime is
|
||||||
thresholds, named-territory binding, player runtime ownership, and wider real grouped-descriptor
|
now present, so the remaining gap is broader ordinary condition-id coverage beyond numeric
|
||||||
semantic coverage, not first-pass captured-runtime plumbing
|
thresholds plus wider real grouped-descriptor and territory-policy semantic coverage, not
|
||||||
|
first-pass captured-runtime plumbing
|
||||||
|
|
||||||
### Milestone 4: Domain Expansion
|
### Milestone 4: Domain Expansion
|
||||||
|
|
||||||
|
|
@ -398,10 +402,10 @@ Target behavior:
|
||||||
- extend ordinary condition coverage beyond numeric thresholds only when comparator semantics,
|
- extend ordinary condition coverage beyond numeric thresholds only when comparator semantics,
|
||||||
runtime ownership, and binding rules are grounded enough to lower honestly into the normalized
|
runtime ownership, and binding rules are grounded enough to lower honestly into the normalized
|
||||||
runtime path
|
runtime path
|
||||||
- keep named-territory ordinary rows explicit and parity-visible until candidate-name territory
|
- keep named-territory ordinary rows on exact case-sensitive binding until captured evidence
|
||||||
binding is grounded
|
justifies alias tables or fuzzier matching
|
||||||
- keep player-owned condition scope explicit and parity-visible until there is a first-class player
|
- keep player-owned condition scope within the minimal event runtime model until later slices need
|
||||||
runtime model
|
richer player metrics or profile/chairman ownership
|
||||||
- continue widening real grouped-descriptor execution only when both descriptor identity and
|
- continue widening real grouped-descriptor execution only when both descriptor identity and
|
||||||
runtime effect semantics are grounded enough to map into the normalized runtime path honestly
|
runtime effect semantics are grounded enough to map into the normalized runtime path honestly
|
||||||
|
|
||||||
|
|
@ -409,8 +413,8 @@ Public-model expectations for that slice:
|
||||||
|
|
||||||
- additional checked-in ordinary-condition metadata entries beyond the current numeric-threshold
|
- additional checked-in ordinary-condition metadata entries beyond the current numeric-threshold
|
||||||
allowlist
|
allowlist
|
||||||
- richer runtime ownership for still-blocked condition domains such as named territory and player
|
- richer ordinary-condition metadata and later runtime ownership only where new condition domains
|
||||||
scope
|
still remain blocked after the current named-territory and player-runtime unlocks
|
||||||
- more selective real-row `decoded_conditions` and `decoded_actions` only where the
|
- more selective real-row `decoded_conditions` and `decoded_actions` only where the
|
||||||
condition/effect-to-runtime mapping is supported end to end
|
condition/effect-to-runtime mapping is supported end to end
|
||||||
|
|
||||||
|
|
@ -418,7 +422,9 @@ Fixture work for that slice:
|
||||||
|
|
||||||
- preserve the new ordinary-condition tracked overlays for executable company finance, company
|
- preserve the new ordinary-condition tracked overlays for executable company finance, company
|
||||||
track, aggregate territory track, and company-territory track thresholds
|
track, aggregate territory track, and company-territory track thresholds
|
||||||
- preserve the named-territory tracked overlay as the explicit binding blocker frontier
|
- preserve the named-territory no-match tracked overlay as the explicit binding blocker frontier
|
||||||
|
- preserve the territory-policy tracked sample as the explicit descriptor frontier until mutation
|
||||||
|
semantics are grounded strongly enough to move beyond parity-only
|
||||||
- keep the older negative-sentinel, mixed real-row, and company-scoped descriptor fixtures green so
|
- keep the older negative-sentinel, mixed real-row, and company-scoped descriptor fixtures green so
|
||||||
ordinary-condition breadth does not regress descriptor-side execution
|
ordinary-condition breadth does not regress descriptor-side execution
|
||||||
- keep synthetic harness, save-slice, and overlay paths green as the real descriptor surface widens
|
- keep synthetic harness, save-slice, and overlay paths green as the real descriptor surface widens
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"fixture_id": "packed-event-ordinary-named-company-territory-overlay-fixture",
|
||||||
|
"source": {
|
||||||
|
"kind": "captured-runtime",
|
||||||
|
"description": "Fixture proving named-territory company-territory ordinary rows bind exactly and execute through the normal runtime path."
|
||||||
|
},
|
||||||
|
"state_import_path": "packed-event-ordinary-named-company-territory-overlay.json",
|
||||||
|
"commands": [
|
||||||
|
{
|
||||||
|
"kind": "service_trigger_kind",
|
||||||
|
"trigger_kind": 7
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"expected_summary": {
|
||||||
|
"calendar_projection_source": "base-snapshot-preserved",
|
||||||
|
"calendar_projection_is_placeholder": false,
|
||||||
|
"company_count": 3,
|
||||||
|
"active_company_count": 3,
|
||||||
|
"player_count": 2,
|
||||||
|
"territory_count": 2,
|
||||||
|
"company_territory_track_count": 3,
|
||||||
|
"packed_event_collection_present": true,
|
||||||
|
"packed_event_record_count": 1,
|
||||||
|
"packed_event_decoded_record_count": 1,
|
||||||
|
"packed_event_imported_runtime_record_count": 1,
|
||||||
|
"packed_event_parity_only_record_count": 1,
|
||||||
|
"event_runtime_record_count": 1,
|
||||||
|
"total_event_record_service_count": 1,
|
||||||
|
"total_trigger_dispatch_count": 1,
|
||||||
|
"total_company_cash": 734
|
||||||
|
},
|
||||||
|
"expected_state_fragment": {
|
||||||
|
"companies": [
|
||||||
|
{
|
||||||
|
"company_id": 1,
|
||||||
|
"current_cash": 444
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"company_id": 2,
|
||||||
|
"current_cash": 90
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"company_id": 3,
|
||||||
|
"current_cash": 200
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"packed_event_collection": {
|
||||||
|
"records": [
|
||||||
|
{
|
||||||
|
"import_outcome": "imported",
|
||||||
|
"decoded_conditions": [
|
||||||
|
{
|
||||||
|
"kind": "company_territory_numeric_threshold",
|
||||||
|
"target": {
|
||||||
|
"kind": "selected_company"
|
||||||
|
},
|
||||||
|
"territory": {
|
||||||
|
"kind": "ids",
|
||||||
|
"ids": [7]
|
||||||
|
},
|
||||||
|
"metric": "total",
|
||||||
|
"comparator": "ge",
|
||||||
|
"value": 10
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"import_id": "packed-event-ordinary-named-company-territory-overlay",
|
||||||
|
"source": {
|
||||||
|
"description": "Overlay import combining named-territory runtime context with the real company-territory threshold sample."
|
||||||
|
},
|
||||||
|
"base_snapshot_path": "packed-event-territory-player-overlay-base-snapshot.json",
|
||||||
|
"save_slice_path": "packed-event-ordinary-named-company-territory-save-slice.json"
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,144 @@
|
||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"save_slice_id": "packed-event-ordinary-named-company-territory-save-slice",
|
||||||
|
"source": {
|
||||||
|
"description": "Tracked save-slice document with a real named-territory company-territory threshold row gating Company Cash.",
|
||||||
|
"original_save_filename": "captured-ordinary-named-company-territory.gms",
|
||||||
|
"original_save_sha256": "ordinary-named-company-territory-sample-sha256",
|
||||||
|
"notes": [
|
||||||
|
"tracked as JSON save-slice document rather than raw .smp",
|
||||||
|
"proves exact named-territory binding for company-territory thresholds"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"save_slice": {
|
||||||
|
"file_extension_hint": "gms",
|
||||||
|
"container_profile_family": "rt3-classic-save-container-v1",
|
||||||
|
"mechanism_family": "classic-save-rehydrate-v1",
|
||||||
|
"mechanism_confidence": "grounded",
|
||||||
|
"trailer_family": null,
|
||||||
|
"bridge_family": null,
|
||||||
|
"profile": null,
|
||||||
|
"candidate_availability_table": null,
|
||||||
|
"special_conditions_table": null,
|
||||||
|
"event_runtime_collection": {
|
||||||
|
"source_kind": "packed-event-runtime-collection",
|
||||||
|
"mechanism_family": "classic-save-rehydrate-v1",
|
||||||
|
"mechanism_confidence": "grounded",
|
||||||
|
"container_profile_family": "rt3-classic-save-container-v1",
|
||||||
|
"metadata_tag_offset": 28928,
|
||||||
|
"records_tag_offset": 29184,
|
||||||
|
"close_tag_offset": 29696,
|
||||||
|
"packed_state_version": 1001,
|
||||||
|
"packed_state_version_hex": "0x000003e9",
|
||||||
|
"live_id_bound": 46,
|
||||||
|
"live_record_count": 1,
|
||||||
|
"live_entry_ids": [46],
|
||||||
|
"decoded_record_count": 1,
|
||||||
|
"imported_runtime_record_count": 1,
|
||||||
|
"records": [
|
||||||
|
{
|
||||||
|
"record_index": 0,
|
||||||
|
"live_entry_id": 46,
|
||||||
|
"payload_offset": 29240,
|
||||||
|
"payload_len": 176,
|
||||||
|
"decode_status": "parity_only",
|
||||||
|
"payload_family": "real_packed_v1",
|
||||||
|
"trigger_kind": 7,
|
||||||
|
"one_shot": false,
|
||||||
|
"compact_control": {
|
||||||
|
"mode_byte_0x7ef": 7,
|
||||||
|
"primary_selector_0x7f0": 99,
|
||||||
|
"grouped_mode_0x7f4": 2,
|
||||||
|
"one_shot_header_0x7f5": 0,
|
||||||
|
"modifier_flag_0x7f9": 2,
|
||||||
|
"modifier_flag_0x7fa": 0,
|
||||||
|
"grouped_target_scope_ordinals_0x7fb": [0, 1, 1, 1],
|
||||||
|
"grouped_scope_checkboxes_0x7ff": [1, 0, 0, 0],
|
||||||
|
"summary_toggle_0x800": 1,
|
||||||
|
"grouped_territory_selectors_0x80f": [-1, -1, -1, -1]
|
||||||
|
},
|
||||||
|
"text_bands": [],
|
||||||
|
"standalone_condition_row_count": 1,
|
||||||
|
"standalone_condition_rows": [
|
||||||
|
{
|
||||||
|
"row_index": 0,
|
||||||
|
"raw_condition_id": 2323,
|
||||||
|
"subtype": 0,
|
||||||
|
"flag_bytes": [10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
|
"candidate_name": "Appalachia",
|
||||||
|
"comparator": "ge",
|
||||||
|
"metric": "Company-Territory Track Pieces",
|
||||||
|
"semantic_family": "numeric_threshold",
|
||||||
|
"semantic_preview": "Test Company-Territory Track Pieces >= 10",
|
||||||
|
"requires_candidate_name_binding": true,
|
||||||
|
"notes": [
|
||||||
|
"condition row carries candidate-name side string"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"negative_sentinel_scope": {
|
||||||
|
"company_test_scope": "selected_company_only",
|
||||||
|
"player_test_scope": "disabled",
|
||||||
|
"territory_scope_selector_is_0x63": true,
|
||||||
|
"source_row_indexes": [0]
|
||||||
|
},
|
||||||
|
"grouped_effect_row_counts": [1, 0, 0, 0],
|
||||||
|
"grouped_effect_rows": [
|
||||||
|
{
|
||||||
|
"group_index": 0,
|
||||||
|
"row_index": 0,
|
||||||
|
"descriptor_id": 2,
|
||||||
|
"descriptor_label": "Company Cash",
|
||||||
|
"target_mask_bits": 1,
|
||||||
|
"parameter_family": "company_finance_scalar",
|
||||||
|
"opcode": 8,
|
||||||
|
"raw_scalar_value": 444,
|
||||||
|
"value_byte_0x09": 1,
|
||||||
|
"value_dword_0x0d": 12,
|
||||||
|
"value_byte_0x11": 2,
|
||||||
|
"value_byte_0x12": 3,
|
||||||
|
"value_word_0x14": 24,
|
||||||
|
"value_word_0x16": 36,
|
||||||
|
"row_shape": "multivalue_scalar",
|
||||||
|
"semantic_family": "multivalue_scalar",
|
||||||
|
"semantic_preview": "Set Company Cash to 444 with aux [2, 3, 24, 36]",
|
||||||
|
"locomotive_name": null,
|
||||||
|
"notes": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"decoded_conditions": [
|
||||||
|
{
|
||||||
|
"kind": "company_territory_numeric_threshold",
|
||||||
|
"target": {
|
||||||
|
"kind": "condition_true_company"
|
||||||
|
},
|
||||||
|
"territory": {
|
||||||
|
"kind": "all_territories"
|
||||||
|
},
|
||||||
|
"metric": "total",
|
||||||
|
"comparator": "ge",
|
||||||
|
"value": 10
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"decoded_actions": [
|
||||||
|
{
|
||||||
|
"kind": "set_company_cash",
|
||||||
|
"target": {
|
||||||
|
"kind": "condition_true_company"
|
||||||
|
},
|
||||||
|
"value": 444
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"executable_import_ready": true,
|
||||||
|
"notes": [
|
||||||
|
"decoded from grounded real 0x4e9a row framing",
|
||||||
|
"named company-territory threshold lowers both company and territory scope at import time"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notes": [
|
||||||
|
"real named company-territory threshold sample"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"fixture_id": "packed-event-ordinary-named-territory-executable-overlay-fixture",
|
||||||
|
"source": {
|
||||||
|
"kind": "captured-runtime",
|
||||||
|
"description": "Fixture proving named-territory ordinary rows bind exactly and execute through the normal runtime path."
|
||||||
|
},
|
||||||
|
"state_import_path": "packed-event-ordinary-named-territory-executable-overlay.json",
|
||||||
|
"commands": [
|
||||||
|
{
|
||||||
|
"kind": "service_trigger_kind",
|
||||||
|
"trigger_kind": 7
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"expected_summary": {
|
||||||
|
"calendar_projection_source": "base-snapshot-preserved",
|
||||||
|
"calendar_projection_is_placeholder": false,
|
||||||
|
"company_count": 3,
|
||||||
|
"active_company_count": 3,
|
||||||
|
"player_count": 2,
|
||||||
|
"territory_count": 2,
|
||||||
|
"company_territory_track_count": 3,
|
||||||
|
"packed_event_collection_present": true,
|
||||||
|
"packed_event_record_count": 1,
|
||||||
|
"packed_event_decoded_record_count": 1,
|
||||||
|
"packed_event_imported_runtime_record_count": 1,
|
||||||
|
"packed_event_parity_only_record_count": 1,
|
||||||
|
"packed_event_blocked_named_territory_binding_count": 0,
|
||||||
|
"event_runtime_record_count": 1,
|
||||||
|
"total_event_record_service_count": 1,
|
||||||
|
"total_trigger_dispatch_count": 1,
|
||||||
|
"total_company_cash": 1067
|
||||||
|
},
|
||||||
|
"expected_state_fragment": {
|
||||||
|
"companies": [
|
||||||
|
{
|
||||||
|
"company_id": 1,
|
||||||
|
"current_cash": 777
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"company_id": 2,
|
||||||
|
"current_cash": 90
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"company_id": 3,
|
||||||
|
"current_cash": 200
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"packed_event_collection": {
|
||||||
|
"records": [
|
||||||
|
{
|
||||||
|
"import_outcome": "imported",
|
||||||
|
"decoded_conditions": [
|
||||||
|
{
|
||||||
|
"kind": "territory_numeric_threshold",
|
||||||
|
"target": {
|
||||||
|
"kind": "ids",
|
||||||
|
"ids": [7]
|
||||||
|
},
|
||||||
|
"metric": "track_pieces_total",
|
||||||
|
"comparator": "ge",
|
||||||
|
"value": 10
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"event_runtime_records": [
|
||||||
|
{
|
||||||
|
"record_id": 45,
|
||||||
|
"service_count": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"import_id": "packed-event-ordinary-named-territory-executable-overlay",
|
||||||
|
"source": {
|
||||||
|
"description": "Overlay import combining named-territory runtime context with the real named-territory threshold sample."
|
||||||
|
},
|
||||||
|
"base_snapshot_path": "packed-event-territory-player-overlay-base-snapshot.json",
|
||||||
|
"save_slice_path": "packed-event-ordinary-named-territory-save-slice.json"
|
||||||
|
}
|
||||||
|
|
@ -103,6 +103,9 @@
|
||||||
"decoded_conditions": [
|
"decoded_conditions": [
|
||||||
{
|
{
|
||||||
"kind": "territory_numeric_threshold",
|
"kind": "territory_numeric_threshold",
|
||||||
|
"target": {
|
||||||
|
"kind": "all_territories"
|
||||||
|
},
|
||||||
"metric": "track_pieces_total",
|
"metric": "track_pieces_total",
|
||||||
"comparator": "ge",
|
"comparator": "ge",
|
||||||
"value": 10
|
"value": 10
|
||||||
|
|
|
||||||
|
|
@ -23,15 +23,15 @@
|
||||||
"packed_event_collection_present": true,
|
"packed_event_collection_present": true,
|
||||||
"packed_event_record_count": 2,
|
"packed_event_record_count": 2,
|
||||||
"packed_event_decoded_record_count": 1,
|
"packed_event_decoded_record_count": 1,
|
||||||
"packed_event_imported_runtime_record_count": 0,
|
"packed_event_imported_runtime_record_count": 1,
|
||||||
"packed_event_parity_only_record_count": 1,
|
"packed_event_parity_only_record_count": 1,
|
||||||
"packed_event_unsupported_record_count": 1,
|
"packed_event_unsupported_record_count": 1,
|
||||||
"packed_event_blocked_missing_condition_context_count": 0,
|
"packed_event_blocked_missing_condition_context_count": 0,
|
||||||
"packed_event_blocked_territory_condition_scope_count": 1,
|
"packed_event_blocked_territory_condition_scope_count": 0,
|
||||||
"packed_event_blocked_missing_compact_control_count": 0,
|
"packed_event_blocked_missing_compact_control_count": 0,
|
||||||
"packed_event_blocked_unmapped_real_descriptor_count": 0,
|
"packed_event_blocked_unmapped_real_descriptor_count": 0,
|
||||||
"packed_event_blocked_structural_only_count": 0,
|
"packed_event_blocked_structural_only_count": 0,
|
||||||
"event_runtime_record_count": 0,
|
"event_runtime_record_count": 1,
|
||||||
"total_company_cash": 0
|
"total_company_cash": 0
|
||||||
},
|
},
|
||||||
"expected_state_fragment": {
|
"expected_state_fragment": {
|
||||||
|
|
@ -53,7 +53,7 @@
|
||||||
"payload_family": "real_packed_v1",
|
"payload_family": "real_packed_v1",
|
||||||
"trigger_kind": 6,
|
"trigger_kind": 6,
|
||||||
"one_shot": true,
|
"one_shot": true,
|
||||||
"import_outcome": "blocked_territory_condition_scope",
|
"import_outcome": "imported",
|
||||||
"compact_control": {
|
"compact_control": {
|
||||||
"primary_selector_0x7f0": 99,
|
"primary_selector_0x7f0": 99,
|
||||||
"grouped_target_scope_ordinals_0x7fb": [0, 1, 2, 3]
|
"grouped_target_scope_ordinals_0x7fb": [0, 1, 2, 3]
|
||||||
|
|
@ -81,7 +81,7 @@
|
||||||
{
|
{
|
||||||
"kind": "set_company_cash",
|
"kind": "set_company_cash",
|
||||||
"target": {
|
"target": {
|
||||||
"kind": "condition_true_company"
|
"kind": "all_active"
|
||||||
},
|
},
|
||||||
"value": 7
|
"value": 7
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@
|
||||||
"live_record_count": 2,
|
"live_record_count": 2,
|
||||||
"live_entry_ids": [3, 5],
|
"live_entry_ids": [3, 5],
|
||||||
"decoded_record_count": 1,
|
"decoded_record_count": 1,
|
||||||
"imported_runtime_record_count": 0,
|
"imported_runtime_record_count": 1,
|
||||||
"records": [
|
"records": [
|
||||||
{
|
{
|
||||||
"record_index": 0,
|
"record_index": 0,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"fixture_id": "packed-event-player-cash-overlay-fixture",
|
||||||
|
"source": {
|
||||||
|
"kind": "captured-runtime",
|
||||||
|
"description": "Fixture proving descriptor 1 Player Cash imports and executes through the ordinary runtime path."
|
||||||
|
},
|
||||||
|
"state_import_path": "packed-event-player-cash-overlay.json",
|
||||||
|
"commands": [
|
||||||
|
{
|
||||||
|
"kind": "service_trigger_kind",
|
||||||
|
"trigger_kind": 6
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"expected_summary": {
|
||||||
|
"calendar_projection_source": "base-snapshot-preserved",
|
||||||
|
"calendar_projection_is_placeholder": false,
|
||||||
|
"company_count": 3,
|
||||||
|
"player_count": 2,
|
||||||
|
"territory_count": 2,
|
||||||
|
"packed_event_collection_present": true,
|
||||||
|
"packed_event_record_count": 1,
|
||||||
|
"packed_event_decoded_record_count": 1,
|
||||||
|
"packed_event_imported_runtime_record_count": 1,
|
||||||
|
"event_runtime_record_count": 1,
|
||||||
|
"total_event_record_service_count": 1,
|
||||||
|
"total_trigger_dispatch_count": 1
|
||||||
|
},
|
||||||
|
"expected_state_fragment": {
|
||||||
|
"players": [
|
||||||
|
{
|
||||||
|
"player_id": 1,
|
||||||
|
"current_cash": 888
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"player_id": 2,
|
||||||
|
"current_cash": 250
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"packed_event_collection": {
|
||||||
|
"records": [
|
||||||
|
{
|
||||||
|
"import_outcome": "imported",
|
||||||
|
"decoded_actions": [
|
||||||
|
{
|
||||||
|
"kind": "set_player_cash",
|
||||||
|
"target": {
|
||||||
|
"kind": "selected_player"
|
||||||
|
},
|
||||||
|
"value": 888
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
9
fixtures/runtime/packed-event-player-cash-overlay.json
Normal file
9
fixtures/runtime/packed-event-player-cash-overlay.json
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"import_id": "packed-event-player-cash-overlay",
|
||||||
|
"source": {
|
||||||
|
"description": "Overlay import combining player runtime context with the real Player Cash descriptor sample."
|
||||||
|
},
|
||||||
|
"base_snapshot_path": "packed-event-territory-player-overlay-base-snapshot.json",
|
||||||
|
"save_slice_path": "packed-event-player-cash-save-slice.json"
|
||||||
|
}
|
||||||
126
fixtures/runtime/packed-event-player-cash-save-slice.json
Normal file
126
fixtures/runtime/packed-event-player-cash-save-slice.json
Normal file
|
|
@ -0,0 +1,126 @@
|
||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"save_slice_id": "packed-event-player-cash-save-slice",
|
||||||
|
"source": {
|
||||||
|
"description": "Tracked save-slice document with a real player-scoped Player Cash row.",
|
||||||
|
"original_save_filename": "captured-player-cash.gms",
|
||||||
|
"original_save_sha256": "player-cash-sample-sha256",
|
||||||
|
"notes": [
|
||||||
|
"tracked as JSON save-slice document rather than raw .smp",
|
||||||
|
"proves descriptor 1 import through the normal runtime path"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"save_slice": {
|
||||||
|
"file_extension_hint": "gms",
|
||||||
|
"container_profile_family": "rt3-classic-save-container-v1",
|
||||||
|
"mechanism_family": "classic-save-rehydrate-v1",
|
||||||
|
"mechanism_confidence": "grounded",
|
||||||
|
"trailer_family": null,
|
||||||
|
"bridge_family": null,
|
||||||
|
"profile": null,
|
||||||
|
"candidate_availability_table": null,
|
||||||
|
"special_conditions_table": null,
|
||||||
|
"event_runtime_collection": {
|
||||||
|
"source_kind": "packed-event-runtime-collection",
|
||||||
|
"mechanism_family": "classic-save-rehydrate-v1",
|
||||||
|
"mechanism_confidence": "grounded",
|
||||||
|
"container_profile_family": "rt3-classic-save-container-v1",
|
||||||
|
"metadata_tag_offset": 28928,
|
||||||
|
"records_tag_offset": 29184,
|
||||||
|
"close_tag_offset": 29696,
|
||||||
|
"packed_state_version": 1001,
|
||||||
|
"packed_state_version_hex": "0x000003e9",
|
||||||
|
"live_id_bound": 47,
|
||||||
|
"live_record_count": 1,
|
||||||
|
"live_entry_ids": [47],
|
||||||
|
"decoded_record_count": 1,
|
||||||
|
"imported_runtime_record_count": 1,
|
||||||
|
"records": [
|
||||||
|
{
|
||||||
|
"record_index": 0,
|
||||||
|
"live_entry_id": 47,
|
||||||
|
"payload_offset": 29280,
|
||||||
|
"payload_len": 140,
|
||||||
|
"decode_status": "parity_only",
|
||||||
|
"payload_family": "real_packed_v1",
|
||||||
|
"trigger_kind": 6,
|
||||||
|
"one_shot": false,
|
||||||
|
"compact_control": {
|
||||||
|
"mode_byte_0x7ef": 6,
|
||||||
|
"primary_selector_0x7f0": 12,
|
||||||
|
"grouped_mode_0x7f4": 2,
|
||||||
|
"one_shot_header_0x7f5": 0,
|
||||||
|
"modifier_flag_0x7f9": 0,
|
||||||
|
"modifier_flag_0x7fa": 2,
|
||||||
|
"grouped_target_scope_ordinals_0x7fb": [0, 1, 1, 1],
|
||||||
|
"grouped_scope_checkboxes_0x7ff": [1, 0, 0, 0],
|
||||||
|
"summary_toggle_0x800": 1,
|
||||||
|
"grouped_territory_selectors_0x80f": [-1, -1, -1, -1]
|
||||||
|
},
|
||||||
|
"text_bands": [],
|
||||||
|
"standalone_condition_row_count": 1,
|
||||||
|
"standalone_condition_rows": [
|
||||||
|
{
|
||||||
|
"row_index": 0,
|
||||||
|
"raw_condition_id": -1,
|
||||||
|
"subtype": 4,
|
||||||
|
"flag_bytes": [48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72],
|
||||||
|
"candidate_name": null,
|
||||||
|
"notes": [
|
||||||
|
"negative sentinel-style condition row id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"negative_sentinel_scope": {
|
||||||
|
"company_test_scope": "disabled",
|
||||||
|
"player_test_scope": "selected_player_only",
|
||||||
|
"territory_scope_selector_is_0x63": false,
|
||||||
|
"source_row_indexes": [0]
|
||||||
|
},
|
||||||
|
"grouped_effect_row_counts": [1, 0, 0, 0],
|
||||||
|
"grouped_effect_rows": [
|
||||||
|
{
|
||||||
|
"group_index": 0,
|
||||||
|
"row_index": 0,
|
||||||
|
"descriptor_id": 1,
|
||||||
|
"descriptor_label": "Player Cash",
|
||||||
|
"target_mask_bits": 2,
|
||||||
|
"parameter_family": "player_finance_scalar",
|
||||||
|
"opcode": 8,
|
||||||
|
"raw_scalar_value": 888,
|
||||||
|
"value_byte_0x09": 1,
|
||||||
|
"value_dword_0x0d": 12,
|
||||||
|
"value_byte_0x11": 2,
|
||||||
|
"value_byte_0x12": 3,
|
||||||
|
"value_word_0x14": 24,
|
||||||
|
"value_word_0x16": 36,
|
||||||
|
"row_shape": "multivalue_scalar",
|
||||||
|
"semantic_family": "multivalue_scalar",
|
||||||
|
"semantic_preview": "Set Player Cash to 888 with aux [2, 3, 24, 36]",
|
||||||
|
"locomotive_name": null,
|
||||||
|
"notes": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"decoded_conditions": [],
|
||||||
|
"decoded_actions": [
|
||||||
|
{
|
||||||
|
"kind": "set_player_cash",
|
||||||
|
"target": {
|
||||||
|
"kind": "condition_true_player"
|
||||||
|
},
|
||||||
|
"value": 888
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"executable_import_ready": true,
|
||||||
|
"notes": [
|
||||||
|
"decoded from grounded real 0x4e9a row framing",
|
||||||
|
"player-side negative-sentinel scope lowers player cash at import time"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notes": [
|
||||||
|
"real player cash descriptor sample"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,158 @@
|
||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"snapshot_id": "packed-event-territory-player-overlay-base-snapshot",
|
||||||
|
"source": {
|
||||||
|
"description": "Base runtime snapshot supplying named-territory and player-selection context for packed-event overlays."
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"calendar": {
|
||||||
|
"year": 1840,
|
||||||
|
"month_slot": 1,
|
||||||
|
"phase_slot": 2,
|
||||||
|
"tick_slot": 3
|
||||||
|
},
|
||||||
|
"world_flags": {
|
||||||
|
"base.only": true
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"base.note": "territory-and-player overlay context"
|
||||||
|
},
|
||||||
|
"companies": [
|
||||||
|
{
|
||||||
|
"company_id": 1,
|
||||||
|
"current_cash": 150,
|
||||||
|
"debt": 80,
|
||||||
|
"credit_rating_score": 650,
|
||||||
|
"prime_rate": 5,
|
||||||
|
"controller_kind": "human",
|
||||||
|
"track_piece_counts": {
|
||||||
|
"total": 20,
|
||||||
|
"single": 5,
|
||||||
|
"double": 8,
|
||||||
|
"transition": 1,
|
||||||
|
"electric": 3,
|
||||||
|
"non_electric": 17
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"company_id": 2,
|
||||||
|
"current_cash": 90,
|
||||||
|
"debt": 40,
|
||||||
|
"credit_rating_score": 480,
|
||||||
|
"prime_rate": 6,
|
||||||
|
"controller_kind": "ai",
|
||||||
|
"track_piece_counts": {
|
||||||
|
"total": 8,
|
||||||
|
"single": 2,
|
||||||
|
"double": 2,
|
||||||
|
"transition": 0,
|
||||||
|
"electric": 1,
|
||||||
|
"non_electric": 7
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"company_id": 3,
|
||||||
|
"current_cash": 200,
|
||||||
|
"debt": 10,
|
||||||
|
"credit_rating_score": 720,
|
||||||
|
"prime_rate": 4,
|
||||||
|
"controller_kind": "human",
|
||||||
|
"track_piece_counts": {
|
||||||
|
"total": 30,
|
||||||
|
"single": 10,
|
||||||
|
"double": 12,
|
||||||
|
"transition": 2,
|
||||||
|
"electric": 8,
|
||||||
|
"non_electric": 22
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"selected_company_id": 1,
|
||||||
|
"players": [
|
||||||
|
{
|
||||||
|
"player_id": 1,
|
||||||
|
"current_cash": 500,
|
||||||
|
"controller_kind": "human"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"player_id": 2,
|
||||||
|
"current_cash": 250,
|
||||||
|
"controller_kind": "ai"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"selected_player_id": 1,
|
||||||
|
"territories": [
|
||||||
|
{
|
||||||
|
"territory_id": 7,
|
||||||
|
"name": "Appalachia",
|
||||||
|
"track_piece_counts": {
|
||||||
|
"total": 50,
|
||||||
|
"single": 10,
|
||||||
|
"double": 20,
|
||||||
|
"transition": 5,
|
||||||
|
"electric": 15,
|
||||||
|
"non_electric": 35
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"territory_id": 8,
|
||||||
|
"name": "Great Plains",
|
||||||
|
"track_piece_counts": {
|
||||||
|
"total": 12,
|
||||||
|
"single": 4,
|
||||||
|
"double": 3,
|
||||||
|
"transition": 1,
|
||||||
|
"electric": 2,
|
||||||
|
"non_electric": 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"company_territory_track_piece_counts": [
|
||||||
|
{
|
||||||
|
"company_id": 1,
|
||||||
|
"territory_id": 7,
|
||||||
|
"track_piece_counts": {
|
||||||
|
"total": 12,
|
||||||
|
"single": 3,
|
||||||
|
"double": 5,
|
||||||
|
"transition": 1,
|
||||||
|
"electric": 4,
|
||||||
|
"non_electric": 8
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"company_id": 2,
|
||||||
|
"territory_id": 7,
|
||||||
|
"track_piece_counts": {
|
||||||
|
"total": 7,
|
||||||
|
"single": 2,
|
||||||
|
"double": 2,
|
||||||
|
"transition": 0,
|
||||||
|
"electric": 1,
|
||||||
|
"non_electric": 6
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"company_id": 3,
|
||||||
|
"territory_id": 7,
|
||||||
|
"track_piece_counts": {
|
||||||
|
"total": 15,
|
||||||
|
"single": 5,
|
||||||
|
"double": 6,
|
||||||
|
"transition": 2,
|
||||||
|
"electric": 5,
|
||||||
|
"non_electric": 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"event_runtime_records": [],
|
||||||
|
"candidate_availability": {},
|
||||||
|
"special_conditions": {},
|
||||||
|
"service_state": {
|
||||||
|
"periodic_boundary_calls": 0,
|
||||||
|
"trigger_dispatch_counts": {},
|
||||||
|
"total_event_record_services": 0,
|
||||||
|
"dirty_rerun_count": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"fixture_id": "packed-event-territory-policy-save-slice-fixture",
|
||||||
|
"source": {
|
||||||
|
"kind": "captured-runtime",
|
||||||
|
"description": "Fixture proving descriptor 3 Territory - Allow All stays parity-only with an explicit blocker."
|
||||||
|
},
|
||||||
|
"state_save_slice_path": "packed-event-territory-policy-save-slice.json",
|
||||||
|
"commands": [
|
||||||
|
{
|
||||||
|
"kind": "step_count",
|
||||||
|
"steps": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"expected_summary": {
|
||||||
|
"calendar_projection_is_placeholder": true,
|
||||||
|
"packed_event_collection_present": true,
|
||||||
|
"packed_event_record_count": 1,
|
||||||
|
"packed_event_decoded_record_count": 1,
|
||||||
|
"packed_event_imported_runtime_record_count": 0,
|
||||||
|
"packed_event_parity_only_record_count": 1,
|
||||||
|
"packed_event_blocked_territory_policy_descriptor_count": 1,
|
||||||
|
"event_runtime_record_count": 0
|
||||||
|
},
|
||||||
|
"expected_state_fragment": {
|
||||||
|
"packed_event_collection": {
|
||||||
|
"records": [
|
||||||
|
{
|
||||||
|
"import_outcome": "blocked_territory_policy_descriptor",
|
||||||
|
"grouped_effect_rows": [
|
||||||
|
{
|
||||||
|
"descriptor_label": "Territory - Allow All"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
102
fixtures/runtime/packed-event-territory-policy-save-slice.json
Normal file
102
fixtures/runtime/packed-event-territory-policy-save-slice.json
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"save_slice_id": "packed-event-territory-policy-save-slice",
|
||||||
|
"source": {
|
||||||
|
"description": "Tracked save-slice document with a real Territory - Allow All row that stays parity-only.",
|
||||||
|
"original_save_filename": "captured-territory-policy.gms",
|
||||||
|
"original_save_sha256": "territory-policy-sample-sha256",
|
||||||
|
"notes": [
|
||||||
|
"tracked as JSON save-slice document rather than raw .smp",
|
||||||
|
"keeps descriptor 3 explicit without guessing territory policy mutation semantics"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"save_slice": {
|
||||||
|
"file_extension_hint": "gms",
|
||||||
|
"container_profile_family": "rt3-classic-save-container-v1",
|
||||||
|
"mechanism_family": "classic-save-rehydrate-v1",
|
||||||
|
"mechanism_confidence": "grounded",
|
||||||
|
"trailer_family": null,
|
||||||
|
"bridge_family": null,
|
||||||
|
"profile": null,
|
||||||
|
"candidate_availability_table": null,
|
||||||
|
"special_conditions_table": null,
|
||||||
|
"event_runtime_collection": {
|
||||||
|
"source_kind": "packed-event-runtime-collection",
|
||||||
|
"mechanism_family": "classic-save-rehydrate-v1",
|
||||||
|
"mechanism_confidence": "grounded",
|
||||||
|
"container_profile_family": "rt3-classic-save-container-v1",
|
||||||
|
"metadata_tag_offset": 28928,
|
||||||
|
"records_tag_offset": 29184,
|
||||||
|
"close_tag_offset": 29696,
|
||||||
|
"packed_state_version": 1001,
|
||||||
|
"packed_state_version_hex": "0x000003e9",
|
||||||
|
"live_id_bound": 48,
|
||||||
|
"live_record_count": 1,
|
||||||
|
"live_entry_ids": [48],
|
||||||
|
"decoded_record_count": 1,
|
||||||
|
"imported_runtime_record_count": 0,
|
||||||
|
"records": [
|
||||||
|
{
|
||||||
|
"record_index": 0,
|
||||||
|
"live_entry_id": 48,
|
||||||
|
"payload_offset": 29320,
|
||||||
|
"payload_len": 132,
|
||||||
|
"decode_status": "parity_only",
|
||||||
|
"payload_family": "real_packed_v1",
|
||||||
|
"trigger_kind": 6,
|
||||||
|
"one_shot": false,
|
||||||
|
"compact_control": {
|
||||||
|
"mode_byte_0x7ef": 6,
|
||||||
|
"primary_selector_0x7f0": 12,
|
||||||
|
"grouped_mode_0x7f4": 2,
|
||||||
|
"one_shot_header_0x7f5": 0,
|
||||||
|
"modifier_flag_0x7f9": 0,
|
||||||
|
"modifier_flag_0x7fa": 0,
|
||||||
|
"grouped_target_scope_ordinals_0x7fb": [1, 1, 1, 1],
|
||||||
|
"grouped_scope_checkboxes_0x7ff": [1, 0, 0, 0],
|
||||||
|
"summary_toggle_0x800": 1,
|
||||||
|
"grouped_territory_selectors_0x80f": [7, -1, -1, -1]
|
||||||
|
},
|
||||||
|
"text_bands": [],
|
||||||
|
"standalone_condition_row_count": 0,
|
||||||
|
"standalone_condition_rows": [],
|
||||||
|
"negative_sentinel_scope": null,
|
||||||
|
"grouped_effect_row_counts": [1, 0, 0, 0],
|
||||||
|
"grouped_effect_rows": [
|
||||||
|
{
|
||||||
|
"group_index": 0,
|
||||||
|
"row_index": 0,
|
||||||
|
"descriptor_id": 3,
|
||||||
|
"descriptor_label": "Territory - Allow All",
|
||||||
|
"target_mask_bits": 5,
|
||||||
|
"parameter_family": "territory_access_toggle",
|
||||||
|
"opcode": 1,
|
||||||
|
"raw_scalar_value": 1,
|
||||||
|
"value_byte_0x09": 0,
|
||||||
|
"value_dword_0x0d": 0,
|
||||||
|
"value_byte_0x11": 0,
|
||||||
|
"value_byte_0x12": 0,
|
||||||
|
"value_word_0x14": 0,
|
||||||
|
"value_word_0x16": 0,
|
||||||
|
"row_shape": "bool_toggle",
|
||||||
|
"semantic_family": "bool_toggle",
|
||||||
|
"semantic_preview": "Set Territory - Allow All to TRUE",
|
||||||
|
"locomotive_name": null,
|
||||||
|
"notes": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"decoded_conditions": [],
|
||||||
|
"decoded_actions": [],
|
||||||
|
"executable_import_ready": false,
|
||||||
|
"notes": [
|
||||||
|
"decoded from grounded real 0x4e9a row framing",
|
||||||
|
"territory policy mutation remains parity-only in this slice"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notes": [
|
||||||
|
"real territory policy descriptor sample"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue