feat(runtime): project initial Behavior control groups
Docs Deploy / Build and Deploy MkDocs (push) Successful in 37s
Docs Deploy / Build and Deploy MkDocs (push) Successful in 37s
This commit is contained in:
4 files changed
+328
No files matched your search
@@ -495,6 +495,38 @@ pub struct MissionControlDeviceTableState {
|
|||||||
pub current_output_rate_5e0_bits: u32,
|
pub current_output_rate_5e0_bits: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// One initial outer row prepared from the current Control descriptor table.
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct MissionBehaviorControlGroup {
|
||||||
|
/// Table ordinal of the descriptor whose current tag starts this group.
|
||||||
|
pub header_device_ordinal: usize,
|
||||||
|
/// Table ordinals of current-tag-2 descriptors following this header.
|
||||||
|
pub candidate_device_ordinals: Vec<usize>,
|
||||||
|
/// Table ordinals of current-tag-30 descriptors following this header.
|
||||||
|
pub tag30_device_ordinals: Vec<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initial grouping of a prepared Control table, before live Behavior refreshes.
|
||||||
|
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||||
|
pub struct MissionBehaviorControlGrouping {
|
||||||
|
/// Groups in the order their current-tag-1 headers appeared.
|
||||||
|
pub groups: Vec<MissionBehaviorControlGroup>,
|
||||||
|
/// Current-tag-2 entries before the first header.
|
||||||
|
pub ungrouped_candidate_device_ordinals: Vec<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initial Behavior grouping projection for one full-load mission object.
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct MissionBehaviorControlGroupingState {
|
||||||
|
/// Stable identity from the source TMA object order.
|
||||||
|
pub original_id: Option<OriginalObjectId>,
|
||||||
|
/// Corresponding live Rust world object.
|
||||||
|
pub handle: ObjectHandle,
|
||||||
|
/// Initial grouping, or the reason its prepared Control table is unavailable.
|
||||||
|
pub grouping:
|
||||||
|
Result<MissionBehaviorControlGrouping, MissionControlDeviceTableUnavailableReason>,
|
||||||
|
}
|
||||||
|
|
||||||
/// Provenance for the class passed to Control's constructor.
|
/// Provenance for the class passed to Control's constructor.
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
pub enum MissionControlClassSource {
|
pub enum MissionControlClassSource {
|
||||||
@@ -974,6 +1006,7 @@ struct LoadedMissionState {
|
|||||||
behavior_ore_states: Vec<BehaviorOreState>,
|
behavior_ore_states: Vec<BehaviorOreState>,
|
||||||
behavior_profiles: Vec<BehaviorProfileState>,
|
behavior_profiles: Vec<BehaviorProfileState>,
|
||||||
control_device_tables: Vec<MissionControlDeviceTableState>,
|
control_device_tables: Vec<MissionControlDeviceTableState>,
|
||||||
|
behavior_control_groupings: Vec<MissionBehaviorControlGroupingState>,
|
||||||
research_trf_tables: Vec<MissionResearchTrfTable>,
|
research_trf_tables: Vec<MissionResearchTrfTable>,
|
||||||
script_bundles: Vec<MissionScriptBundle>,
|
script_bundles: Vec<MissionScriptBundle>,
|
||||||
script_varset: Option<MissionScriptVarSet>,
|
script_varset: Option<MissionScriptVarSet>,
|
||||||
@@ -1720,6 +1753,14 @@ fn load_mission_with_options(
|
|||||||
} else {
|
} else {
|
||||||
Vec::new()
|
Vec::new()
|
||||||
};
|
};
|
||||||
|
let behavior_control_groupings = if options.asset_scope == MissionAssetScope::Full {
|
||||||
|
control_device_tables
|
||||||
|
.iter()
|
||||||
|
.map(project_mission_behavior_control_grouping)
|
||||||
|
.collect()
|
||||||
|
} else {
|
||||||
|
Vec::new()
|
||||||
|
};
|
||||||
|
|
||||||
let mut next_behavior_rng_state = engine.behavior_rng_state;
|
let mut next_behavior_rng_state = engine.behavior_rng_state;
|
||||||
if options.asset_scope == MissionAssetScope::Full {
|
if options.asset_scope == MissionAssetScope::Full {
|
||||||
@@ -1801,6 +1842,7 @@ fn load_mission_with_options(
|
|||||||
behavior_ore_states,
|
behavior_ore_states,
|
||||||
behavior_profiles,
|
behavior_profiles,
|
||||||
control_device_tables,
|
control_device_tables,
|
||||||
|
behavior_control_groupings,
|
||||||
research_trf_tables,
|
research_trf_tables,
|
||||||
script_bundles: loaded_scripts.bundles,
|
script_bundles: loaded_scripts.bundles,
|
||||||
script_varset: loaded_scripts.varset,
|
script_varset: loaded_scripts.varset,
|
||||||
@@ -2132,6 +2174,61 @@ pub fn loaded_mission_control_device_tables(
|
|||||||
.map(|state| state.control_device_tables.as_slice())
|
.map(|state| state.control_device_tables.as_slice())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the initial prepared Control grouping for full-load mission objects.
|
||||||
|
#[must_use]
|
||||||
|
pub fn loaded_mission_behavior_control_groupings(
|
||||||
|
engine: &Engine,
|
||||||
|
) -> Option<&[MissionBehaviorControlGroupingState]> {
|
||||||
|
engine
|
||||||
|
.loaded
|
||||||
|
.as_ref()
|
||||||
|
.map(|state| state.behavior_control_groupings.as_slice())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn project_mission_behavior_control_grouping(
|
||||||
|
table: &MissionControlDeviceTableState,
|
||||||
|
) -> MissionBehaviorControlGroupingState {
|
||||||
|
let grouping = match &table.status {
|
||||||
|
MissionControlDeviceTableStatus::Available(devices) => {
|
||||||
|
Ok(build_mission_behavior_control_grouping(devices))
|
||||||
|
}
|
||||||
|
MissionControlDeviceTableStatus::Unavailable(reason) => Err(reason.clone()),
|
||||||
|
};
|
||||||
|
MissionBehaviorControlGroupingState {
|
||||||
|
original_id: table.original_id,
|
||||||
|
handle: table.handle,
|
||||||
|
grouping,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_mission_behavior_control_grouping(
|
||||||
|
devices: &[EffectiveControlDevice],
|
||||||
|
) -> MissionBehaviorControlGrouping {
|
||||||
|
let mut grouping = MissionBehaviorControlGrouping::default();
|
||||||
|
for (ordinal, device) in devices.iter().enumerate() {
|
||||||
|
match device.descriptor.tag {
|
||||||
|
1 => {
|
||||||
|
grouping.groups.push(MissionBehaviorControlGroup {
|
||||||
|
header_device_ordinal: ordinal,
|
||||||
|
candidate_device_ordinals: Vec::new(),
|
||||||
|
tag30_device_ordinals: Vec::new(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
2 => match grouping.groups.last_mut() {
|
||||||
|
Some(group) => group.candidate_device_ordinals.push(ordinal),
|
||||||
|
None => grouping.ungrouped_candidate_device_ordinals.push(ordinal),
|
||||||
|
},
|
||||||
|
30 => {
|
||||||
|
if let Some(group) = grouping.groups.last_mut() {
|
||||||
|
group.tag30_device_ordinals.push(ordinal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
grouping
|
||||||
|
}
|
||||||
|
|
||||||
/// Queries native Control global-table selector 1 (weighted ratio), 2 (sum), or 15.
|
/// Queries native Control global-table selector 1 (weighted ratio), 2 (sum), or 15.
|
||||||
/// `Ok(None)` preserves the original false/no-result behavior for an empty or
|
/// `Ok(None)` preserves the original false/no-result behavior for an empty or
|
||||||
/// zero-denominator table or selector 15 without both cached factory rows. Other
|
/// zero-denominator table or selector 15 without both cached factory rows. Other
|
||||||
@@ -7276,6 +7373,95 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn behavior_control_grouping_uses_current_tags_and_table_ordinals() {
|
||||||
|
let tags = [2, 30, 1, 30, 2, 99, 1, 2, 30, 2, 1];
|
||||||
|
let devices = tags
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(ordinal, current_tag)| {
|
||||||
|
let mut device = synthetic_effective_control_device(
|
||||||
|
5,
|
||||||
|
current_tag,
|
||||||
|
1.0_f32.to_bits(),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
device.key = 100 + ordinal as u32;
|
||||||
|
device.descriptor.fixed_words[1] = 200 + ordinal as u32;
|
||||||
|
device
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let table =
|
||||||
|
synthetic_control_device_table(MissionControlDeviceTableStatus::Available(devices));
|
||||||
|
let state = project_mission_behavior_control_grouping(&table);
|
||||||
|
assert_eq!(state.original_id, Some(OriginalObjectId(7)));
|
||||||
|
assert_eq!(
|
||||||
|
state.handle,
|
||||||
|
ObjectHandle {
|
||||||
|
generation: 5,
|
||||||
|
slot: 7
|
||||||
|
}
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
state.grouping,
|
||||||
|
Ok(MissionBehaviorControlGrouping {
|
||||||
|
ungrouped_candidate_device_ordinals: vec![0],
|
||||||
|
groups: vec![
|
||||||
|
MissionBehaviorControlGroup {
|
||||||
|
header_device_ordinal: 2,
|
||||||
|
candidate_device_ordinals: vec![4],
|
||||||
|
tag30_device_ordinals: vec![3],
|
||||||
|
},
|
||||||
|
MissionBehaviorControlGroup {
|
||||||
|
header_device_ordinal: 6,
|
||||||
|
candidate_device_ordinals: vec![7, 9],
|
||||||
|
tag30_device_ordinals: vec![8],
|
||||||
|
},
|
||||||
|
MissionBehaviorControlGroup {
|
||||||
|
header_device_ordinal: 10,
|
||||||
|
candidate_device_ordinals: Vec::new(),
|
||||||
|
tag30_device_ordinals: Vec::new(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
"group membership uses current descriptor tags and table ordinals, not factory tags or keys"
|
||||||
|
);
|
||||||
|
|
||||||
|
let empty =
|
||||||
|
synthetic_control_device_table(MissionControlDeviceTableStatus::Available(Vec::new()));
|
||||||
|
assert_eq!(
|
||||||
|
project_mission_behavior_control_grouping(&empty).grouping,
|
||||||
|
Ok(MissionBehaviorControlGrouping::default())
|
||||||
|
);
|
||||||
|
|
||||||
|
let headerless_devices = [2, 30, 2]
|
||||||
|
.into_iter()
|
||||||
|
.map(|current_tag| {
|
||||||
|
synthetic_effective_control_device(5, current_tag, 1.0_f32.to_bits(), None, None)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let headerless = synthetic_control_device_table(
|
||||||
|
MissionControlDeviceTableStatus::Available(headerless_devices),
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
project_mission_behavior_control_grouping(&headerless).grouping,
|
||||||
|
Ok(MissionBehaviorControlGrouping {
|
||||||
|
groups: Vec::new(),
|
||||||
|
ungrouped_candidate_device_ordinals: vec![0, 2],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
let reason = MissionControlDeviceTableUnavailableReason::NoControlResource;
|
||||||
|
let unavailable = synthetic_control_device_table(
|
||||||
|
MissionControlDeviceTableStatus::Unavailable(reason.clone()),
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
project_mission_behavior_control_grouping(&unavailable).grouping,
|
||||||
|
Err(reason)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
fn control_budget_test_table(generator_coefficient: f32) -> MissionControlDeviceTableState {
|
fn control_budget_test_table(generator_coefficient: f32) -> MissionControlDeviceTableState {
|
||||||
let mut generator = synthetic_effective_control_device(
|
let mut generator = synthetic_effective_control_device(
|
||||||
19,
|
19,
|
||||||
@@ -13548,6 +13734,62 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore = "requires licensed corpus"]
|
||||||
|
fn full_load_builds_initial_behavior_control_groupings_for_autodemo() {
|
||||||
|
let mut engine = create(EngineServices::new(Arc::new(DirectoryVfs::new(
|
||||||
|
licensed_root("IS"),
|
||||||
|
))))
|
||||||
|
.expect("engine");
|
||||||
|
load_mission(
|
||||||
|
&mut engine,
|
||||||
|
MissionRequest {
|
||||||
|
key: "MISSIONS/Autodemo.00/data.tma".into(),
|
||||||
|
mode: MissionMode::AutoDemo,
|
||||||
|
},
|
||||||
|
MissionStartupInput {
|
||||||
|
queue_time_ms: 100_000,
|
||||||
|
research_creation_gate: false,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.expect("load licensed AutoDemo with explicit startup inputs");
|
||||||
|
|
||||||
|
let groupings =
|
||||||
|
loaded_mission_behavior_control_groupings(&engine).expect("initial Behavior groupings");
|
||||||
|
let group = |header_device_ordinal, candidate_device_ordinals: &[usize]| {
|
||||||
|
MissionBehaviorControlGroup {
|
||||||
|
header_device_ordinal,
|
||||||
|
candidate_device_ordinals: candidate_device_ordinals.to_vec(),
|
||||||
|
tag30_device_ordinals: Vec::new(),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let expected = [
|
||||||
|
(0, vec![group(6, &[10, 11, 12, 13])]),
|
||||||
|
(1, vec![group(6, &[10, 11])]),
|
||||||
|
(2, vec![group(8, &[12, 13, 14])]),
|
||||||
|
(3, vec![group(6, &[10, 11, 12])]),
|
||||||
|
(4, Vec::new()),
|
||||||
|
(5, Vec::new()),
|
||||||
|
(6, Vec::new()),
|
||||||
|
(7, Vec::new()),
|
||||||
|
];
|
||||||
|
assert_eq!(groupings.len(), expected.len());
|
||||||
|
for (original_id, expected_groups) in expected {
|
||||||
|
let state = groupings
|
||||||
|
.iter()
|
||||||
|
.find(|state| state.original_id == Some(OriginalObjectId(original_id)))
|
||||||
|
.expect("grouping for AutoDemo Control object");
|
||||||
|
assert_eq!(
|
||||||
|
state.grouping,
|
||||||
|
Ok(MissionBehaviorControlGrouping {
|
||||||
|
groups: expected_groups,
|
||||||
|
ungrouped_candidate_device_ordinals: Vec::new(),
|
||||||
|
}),
|
||||||
|
"original object {original_id}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[ignore = "requires licensed corpus"]
|
#[ignore = "requires licensed corpus"]
|
||||||
fn full_load_queries_control_selector_15_against_frozen_native_inputs() {
|
fn full_load_queries_control_selector_15_against_frozen_native_inputs() {
|
||||||
@@ -14654,6 +14896,10 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.expect("load licensed Single.01 preview");
|
.expect("load licensed Single.01 preview");
|
||||||
|
|
||||||
|
assert!(loaded_mission_behavior_control_groupings(&engine)
|
||||||
|
.expect("preview grouping projection")
|
||||||
|
.is_empty());
|
||||||
|
|
||||||
let bundles = loaded_mission_script_bundles(&engine).expect("script bundles");
|
let bundles = loaded_mission_script_bundles(&engine).expect("script bundles");
|
||||||
assert_eq!(bundles.len(), 2);
|
assert_eq!(bundles.len(), 2);
|
||||||
let first = bundles
|
let first = bundles
|
||||||
|
|||||||
@@ -262,6 +262,26 @@ Behavior (`Behavior.dll`, RVA `0x3600`) вызывает `World3D!stdGetGameMode
|
|||||||
с реальными интерфейсами ArealMap, нужными `FUN_10019e80`; полный Behavior tick
|
с реальными интерфейсами ArealMap, нужными `FUN_10019e80`; полный Behavior tick
|
||||||
flow и общий dispatcher событий также остаются открытыми.
|
flow и общий dispatcher событий также остаются открытыми.
|
||||||
|
|
||||||
|
`loaded_mission_behavior_control_groupings` публикует только начальную
|
||||||
|
группировку подготовленной Control-таблицы. Ветка с меткой `2` исходного
|
||||||
|
`Behavior.dll!FUN_1001be10` (RVA `0x1be10`) дополнительно вызывает
|
||||||
|
`FUN_1001b4b0` и обращается к свойствам через QI `0x204`, включая запросы с
|
||||||
|
номерами `0x700`, `6` и `0x400`. Результат `0x700` попадает в строку кандидата
|
||||||
|
по смещению `+0x3c`, результат `6` — в `+0x40`, а `0x400` может обнулить второе
|
||||||
|
поле. Их доменный смысл, множитель `0x700`, агрегаты Behavior QI `0x204` и
|
||||||
|
прочие числовые поля кандидата не восстановлены. Rust не подставляет множитель
|
||||||
|
`0x700`, нулевые агрегаты, выбранный индекс или эффект. Список для метки `30` не
|
||||||
|
встречается в ресурсах AutoDemo и проверяется только на искусственном наборе.
|
||||||
|
|
||||||
|
Игнорируемая проверка AutoDemo сравнивает порядковые номера записей начальной
|
||||||
|
группировки, отдельно выведенные по снимку текущих меток дескрипторов, с
|
||||||
|
состоянием Rust после полной загрузки. Это подтверждает согласованность разбора
|
||||||
|
подготовленной таблицы и правила группировки на этих ресурсах, но не является
|
||||||
|
захватом состояния Behavior оригинала и не устанавливает, когда список
|
||||||
|
прикрепляется или обновляется в живом объекте. Полный штатный путь загрузки и
|
||||||
|
запуска, агрегаты Behavior QI `0x204`, обновление после изменений Control,
|
||||||
|
`Handler(43)` и полный игровой цикл остаются отдельными границами.
|
||||||
|
|
||||||
Начальные свойства `CurrentOre`/`MaximumOre` восстановлены отдельно от
|
Начальные свойства `CurrentOre`/`MaximumOre` восстановлены отдельно от
|
||||||
регулярного обмена. В проверенных `Behavior.dll` (SHA-256
|
регулярного обмена. В проверенных `Behavior.dll` (SHA-256
|
||||||
`ddfbdbde07832358fe6e35e01c8dba9b58e7313789498b6049ed51d4ed9baf99`) RVA
|
`ddfbdbde07832358fe6e35e01c8dba9b58e7313789498b6049ed51d4ed9baf99`) RVA
|
||||||
|
|||||||
@@ -763,6 +763,49 @@ tie-векторов подтверждает реакцию `FISTP`, но не
|
|||||||
одного numeric literal; остальные формы инструкции и содержимое `.scr`
|
одного numeric literal; остальные формы инструкции и содержимое `.scr`
|
||||||
сохраняются reader-ом, но остаются вне этого исполнения.
|
сохраняются reader-ом, но остаются вне этого исполнения.
|
||||||
|
|
||||||
|
### Начальная группировка подготовленных Control-дескрипторов
|
||||||
|
|
||||||
|
В проверенной GOG `Behavior.dll` (SHA-256
|
||||||
|
`ddfbdbde07832358fe6e35e01c8dba9b58e7313789498b6049ed51d4ed9baf99`)
|
||||||
|
`FUN_1001be10` (RVA `0x1be10`) получает список через QI с кодом `0x202` от
|
||||||
|
`owner+0x5c`. Слоты `+0x50` и `+0x1c` этого интерфейса возвращают число строк и
|
||||||
|
код-различитель для строки; цикл обрабатывает порядковые номера строк
|
||||||
|
`0..count-1` по возрастанию. Метки `1`, `2` и `30` начинают группу, добавляют
|
||||||
|
кандидата и добавляют отдельный элемент к последней группе соответственно.
|
||||||
|
Запись с меткой `2` до первого заголовка попадает в отдельный вектор; метка `30`
|
||||||
|
без группы игнорируется, как и все остальные метки.
|
||||||
|
|
||||||
|
Для источника списка проверена пара GOG DLL. В `Control.dll` SHA-256
|
||||||
|
`bcb86318089028548a178b03a21accdce15df0cc85ddd91674cf3c2f55012c6e` запрос с
|
||||||
|
кодом `0x202` отдаёт интерфейс `owner+8`; его методы-обёртки переходят к методам
|
||||||
|
чтения канонического QI `0x204`, receiver которого равен `owner+0x0c`. Метод
|
||||||
|
`Control.dll` RVA `0x8830` читает число из `[receiver+0x5b4]`, то есть
|
||||||
|
`[owner+0x5c0]`; таблица указателей находится в `[receiver+0x5b0]`, то есть
|
||||||
|
`[owner+0x5bc]`. Поле `[owner+0x5b4]` — отдельный указатель кэша, а не число
|
||||||
|
строк. Метод чтения `Control.dll` RVA `0x2c390` выбирает указатель `table[index]`,
|
||||||
|
разыменовывает указатель на дескриптор из записи `+0x48` и возвращает DWORD по
|
||||||
|
адресу этого дескриптора. Загрузчик `FUN_10021d50` (RVA `0x21d50`) сохраняет там
|
||||||
|
указатель текущего дескриптора, поэтому код-различитель — его текущая метка, а
|
||||||
|
не неизменная метка фабрики.
|
||||||
|
|
||||||
|
В оригинальном Behavior внешняя строка имеет шаг `0x34`; её `+4` получает
|
||||||
|
порядковый номер заголовка. Строка-кандидат имеет шаг `0x58`, а её `+8` хранит
|
||||||
|
порядковый номер кандидата. Номера строк с меткой `30` добавляются в отдельный
|
||||||
|
вектор последней внешней строки. Rust публикует только эти начальные номера:
|
||||||
|
`MissionBehaviorControlGroup` и
|
||||||
|
`MissionBehaviorControlGrouping` группируют строки из уже подготовленной
|
||||||
|
Control-таблицы в её исходном порядке. `MissionBehaviorControlGroupingState`
|
||||||
|
сохраняет `original_id`, `ObjectHandle` и либо результат, либо исходную причину
|
||||||
|
недоступности Control-таблицы. Полная загрузка создаёт проекцию перед публикацией
|
||||||
|
`LoadedMissionState`; предварительный просмотр возвращает пустой список. Проверки
|
||||||
|
`behavior_control_grouping_uses_current_tags_and_table_ordinals` и игнорируемый
|
||||||
|
`full_load_builds_initial_behavior_control_groupings_for_autodemo` покрывают
|
||||||
|
искусственные граничные случаи и сохранённые номера строк AutoDemo соответственно.
|
||||||
|
|
||||||
|
Это начальное разложение подготовленного списка, а не реконструкция полной
|
||||||
|
инициализации Behavior: отдельные свойства строки-кандидата, её числовые поля,
|
||||||
|
эффекты и обновление списка после изменений Control здесь не моделируются.
|
||||||
|
|
||||||
### Изменение provider-состояния Control
|
### Изменение provider-состояния Control
|
||||||
|
|
||||||
Для `Control.dll` (SHA-256
|
Для `Control.dll` (SHA-256
|
||||||
|
|||||||
@@ -260,6 +260,25 @@ Rust-тест повторно читает лицензированную ми
|
|||||||
TMA-записями и проверяет реальные `ObjectHandle`, но помечен `#[ignore]`, чтобы
|
TMA-записями и проверяет реальные `ObjectHandle`, но помечен `#[ignore]`, чтобы
|
||||||
не требовать оригинальных ресурсов от остальных разработчиков.
|
не требовать оригинальных ресурсов от остальных разработчиков.
|
||||||
|
|
||||||
|
После подготовки Control-таблицы можно разобрать и её начальную группировку для
|
||||||
|
Behavior. Важны текущая метка записи и её порядковый номер в общей таблице:
|
||||||
|
метка `1` начинает пустую группу, метка `2` становится кандидатом последней
|
||||||
|
группы, а метка `30` записывает строку в отдельный список той же группы.
|
||||||
|
Кандидат, встретившийся до первого заголовка, остаётся в списке без группы;
|
||||||
|
ранняя метка `30` игнорируется. Например,
|
||||||
|
для текущих меток `[2, 30, 1, 30, 2]` получаются незагруппированный кандидат
|
||||||
|
`[0]` и группа с заголовком `2`, кандидатом `[4]` и строкой с меткой `30` под
|
||||||
|
порядковым номером `[3]`. Все номера начинаются с нуля и отсчитываются по общей
|
||||||
|
Control-таблице, а не по ключам устройств или записям CTLD.
|
||||||
|
|
||||||
|
Полная загрузка Rust сохраняет это начальное состояние рядом с подготовленными
|
||||||
|
Control-таблицами по исходному TMA ID и `ObjectHandle`. Если таблица недоступна,
|
||||||
|
проекция сохраняет причину ошибки; доступная пустая таблица означает пустую
|
||||||
|
группировку. При предварительном просмотре список группировок не создаётся. Здесь
|
||||||
|
разбирается только подготовленный порядок текущих Control-дескрипторов. Проекция
|
||||||
|
не устанавливает, когда список присоединяется к живому Behavior, как он затем
|
||||||
|
обновляется и как работает полный игровой цикл.
|
||||||
|
|
||||||
Список распределителя хранит не только участников, но и долю каждого. При
|
Список распределителя хранит не только участников, но и долю каждого. При
|
||||||
добавлении двух объектов оба получают по половине общего веса. Если добавить
|
добавлении двух объектов оба получают по половине общего веса. Если добавить
|
||||||
третий, движок оставляет долю новой записи около одной трети, а оставшийся вес
|
третий, движок оставляет долю новой записи около одной трети, а оставшийся вес
|
||||||
|
|||||||
Reference in new issue
Block a user