feat(runtime): restore Control selector 15
Docs Deploy / Build and Deploy MkDocs (push) Successful in 36s
Docs Deploy / Build and Deploy MkDocs (push) Successful in 36s
This commit is contained in:
3 files changed
+415
-14
No files matched your search
@@ -2132,9 +2132,10 @@ pub fn loaded_mission_control_device_tables(
|
|||||||
.map(|state| state.control_device_tables.as_slice())
|
.map(|state| state.control_device_tables.as_slice())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Queries native Control global-table selector 1 (weighted ratio) or 2 (sum).
|
/// 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. Other unsupported native paths are reported as errors.
|
/// zero-denominator table or selector 15 without both cached factory rows. Other
|
||||||
|
/// unsupported native paths are reported as errors.
|
||||||
/// Inputs and intermediates must be finite and normal, or exact zero.
|
/// Inputs and intermediates must be finite and normal, or exact zero.
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
@@ -4201,7 +4202,7 @@ fn query_control_device_table_resource(
|
|||||||
table: &MissionControlDeviceTableState,
|
table: &MissionControlDeviceTableState,
|
||||||
selector: u32,
|
selector: u32,
|
||||||
) -> Result<Option<f32>, ControlResourceAccessError> {
|
) -> Result<Option<f32>, ControlResourceAccessError> {
|
||||||
if !matches!(selector, 1 | 2) {
|
if !matches!(selector, 1 | 2 | 15) {
|
||||||
return Err(ControlResourceAccessError::UnsupportedSelector(selector));
|
return Err(ControlResourceAccessError::UnsupportedSelector(selector));
|
||||||
}
|
}
|
||||||
let entries = match &table.status {
|
let entries = match &table.status {
|
||||||
@@ -4211,6 +4212,32 @@ fn query_control_device_table_resource(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if selector == 15 {
|
||||||
|
let Some(class9) = entries.iter().rev().find(|entry| entry.factory_tag == 9) else {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
|
let Some(class21) = entries.iter().rev().find(|entry| entry.factory_tag == 21) else {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut sum = control_device_budget_coefficient(class21, 5)?;
|
||||||
|
for index in (0..5).rev() {
|
||||||
|
sum = checked_control_sum(
|
||||||
|
21,
|
||||||
|
"sum",
|
||||||
|
sum,
|
||||||
|
control_device_budget_coefficient(class21, index)?,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
return checked_control_product(
|
||||||
|
9,
|
||||||
|
"selector-15 value",
|
||||||
|
control_device_selector_zero(class9)?,
|
||||||
|
sum,
|
||||||
|
)
|
||||||
|
.map(Some);
|
||||||
|
}
|
||||||
|
|
||||||
let mut numerator = 0.0_f32;
|
let mut numerator = 0.0_f32;
|
||||||
let mut denominator = 0.0_f32;
|
let mut denominator = 0.0_f32;
|
||||||
for entry in entries.iter().filter(|entry| entry.descriptor.tag == 19) {
|
for entry in entries.iter().filter(|entry| entry.descriptor.tag == 19) {
|
||||||
@@ -8148,6 +8175,251 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn control_resource_selector_15_uses_last_cached_factories_and_current_fields() {
|
||||||
|
let mut first_class9 = synthetic_effective_control_device(
|
||||||
|
9,
|
||||||
|
19,
|
||||||
|
90.0_f32.to_bits(),
|
||||||
|
Some(f32::INFINITY.to_bits()),
|
||||||
|
Some(9.0_f32.to_bits()),
|
||||||
|
);
|
||||||
|
let mut first_class21 = synthetic_effective_control_device(
|
||||||
|
21,
|
||||||
|
19,
|
||||||
|
90.0_f32.to_bits(),
|
||||||
|
Some(f32::INFINITY.to_bits()),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
let mut class9 = synthetic_effective_control_device(
|
||||||
|
9,
|
||||||
|
5,
|
||||||
|
99.0_f32.to_bits(),
|
||||||
|
Some(f32::NAN.to_bits()),
|
||||||
|
Some(99.0_f32.to_bits()),
|
||||||
|
);
|
||||||
|
let mut class21 = synthetic_effective_control_device(
|
||||||
|
21,
|
||||||
|
26,
|
||||||
|
90.0_f32.to_bits(),
|
||||||
|
Some(f32::INFINITY.to_bits()),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
first_class9.native_word_50_bits = Some(0);
|
||||||
|
first_class21.native_word_50_bits = Some(0);
|
||||||
|
class9.native_word_50_bits = Some(0);
|
||||||
|
class21.native_word_50_bits = Some(0);
|
||||||
|
class9.key = u32::MAX;
|
||||||
|
class21.key = u32::MAX;
|
||||||
|
class9.coefficient_bits.as_mut().unwrap()[0] = 3.0_f32.to_bits();
|
||||||
|
class9.class9_selector0_scale_bits = Some(4.0_f32.to_bits());
|
||||||
|
class21.coefficient_bits.as_mut().unwrap()[..6].copy_from_slice(&[
|
||||||
|
1.0_f32.to_bits(),
|
||||||
|
2.0_f32.to_bits(),
|
||||||
|
3.0_f32.to_bits(),
|
||||||
|
4.0_f32.to_bits(),
|
||||||
|
5.0_f32.to_bits(),
|
||||||
|
6.0_f32.to_bits(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
let table = synthetic_control_device_table_with_providers(
|
||||||
|
MissionControlDeviceTableStatus::Available(vec![
|
||||||
|
first_class9,
|
||||||
|
first_class21,
|
||||||
|
class9,
|
||||||
|
class21,
|
||||||
|
]),
|
||||||
|
MissionControlNodeProviderTableStatus::Unavailable(
|
||||||
|
MissionControlNodeProviderTableUnavailableReason::MissingNodeData {
|
||||||
|
unit_component_index: 0,
|
||||||
|
source: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
query_control_device_table_resource(&table, 15)
|
||||||
|
.expect("selector 15 uses current cached getter values")
|
||||||
|
.map(f32::to_bits),
|
||||||
|
Some(252.0_f32.to_bits())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn control_resource_selector_15_preserves_descending_binary32_accumulation() {
|
||||||
|
let mut class9 = synthetic_effective_control_device(
|
||||||
|
9,
|
||||||
|
19,
|
||||||
|
1.0_f32.to_bits(),
|
||||||
|
None,
|
||||||
|
Some(1.0_f32.to_bits()),
|
||||||
|
);
|
||||||
|
let mut class21 = synthetic_effective_control_device(21, 19, 0, None, None);
|
||||||
|
class9.coefficient_bits.as_mut().unwrap()[0] = 1.0_f32.to_bits();
|
||||||
|
class21.coefficient_bits.as_mut().unwrap()[..6].copy_from_slice(&[
|
||||||
|
0.0_f32.to_bits(),
|
||||||
|
0.0_f32.to_bits(),
|
||||||
|
0.0_f32.to_bits(),
|
||||||
|
(-16_777_216.0_f32).to_bits(),
|
||||||
|
16_777_216.0_f32.to_bits(),
|
||||||
|
1.0_f32.to_bits(),
|
||||||
|
]);
|
||||||
|
let forward = class21.coefficient_bits.unwrap()[..6]
|
||||||
|
.iter()
|
||||||
|
.map(|bits| f32::from_bits(*bits))
|
||||||
|
.fold(0.0_f32, |sum, value| sum + value);
|
||||||
|
assert_eq!(forward.to_bits(), 1.0_f32.to_bits());
|
||||||
|
|
||||||
|
let table =
|
||||||
|
synthetic_control_device_table(MissionControlDeviceTableStatus::Available(vec![
|
||||||
|
class9.clone(),
|
||||||
|
class21.clone(),
|
||||||
|
]));
|
||||||
|
assert_eq!(
|
||||||
|
query_control_device_table_resource(&table, 15)
|
||||||
|
.expect("descending native order")
|
||||||
|
.map(f32::to_bits),
|
||||||
|
Some(0.0_f32.to_bits())
|
||||||
|
);
|
||||||
|
|
||||||
|
class21.coefficient_bits.as_mut().unwrap()[..6].fill((-0.0_f32).to_bits());
|
||||||
|
let negative_zero =
|
||||||
|
synthetic_control_device_table(MissionControlDeviceTableStatus::Available(vec![
|
||||||
|
class9, class21,
|
||||||
|
]));
|
||||||
|
assert_eq!(
|
||||||
|
query_control_device_table_resource(&negative_zero, 15)
|
||||||
|
.expect("negative zero is retained")
|
||||||
|
.map(f32::to_bits),
|
||||||
|
Some((-0.0_f32).to_bits())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn control_resource_selector_15_returns_no_result_without_both_cached_factories() {
|
||||||
|
let empty =
|
||||||
|
synthetic_control_device_table(MissionControlDeviceTableStatus::Available(Vec::new()));
|
||||||
|
assert_eq!(query_control_device_table_resource(&empty, 15), Ok(None));
|
||||||
|
|
||||||
|
let class9_only =
|
||||||
|
synthetic_control_device_table(MissionControlDeviceTableStatus::Available(vec![
|
||||||
|
synthetic_effective_control_device(9, 35, 1, None, None),
|
||||||
|
]));
|
||||||
|
assert_eq!(
|
||||||
|
query_control_device_table_resource(&class9_only, 15),
|
||||||
|
Ok(None)
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut class21 = synthetic_effective_control_device(21, 35, 1, None, None);
|
||||||
|
class21.coefficient_bits = None;
|
||||||
|
let class21_only =
|
||||||
|
synthetic_control_device_table(MissionControlDeviceTableStatus::Available(vec![
|
||||||
|
class21,
|
||||||
|
]));
|
||||||
|
assert_eq!(
|
||||||
|
query_control_device_table_resource(&class21_only, 15),
|
||||||
|
Ok(None)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn control_resource_selector_15_reports_missing_and_unsupported_inputs() {
|
||||||
|
let mut class9 = synthetic_effective_control_device(
|
||||||
|
9,
|
||||||
|
19,
|
||||||
|
1.0_f32.to_bits(),
|
||||||
|
None,
|
||||||
|
Some(1.0_f32.to_bits()),
|
||||||
|
);
|
||||||
|
let mut class21 = synthetic_effective_control_device(21, 19, 1.0_f32.to_bits(), None, None);
|
||||||
|
class21.coefficient_bits = None;
|
||||||
|
let missing_coefficients =
|
||||||
|
synthetic_control_device_table(MissionControlDeviceTableStatus::Available(vec![
|
||||||
|
class9.clone(),
|
||||||
|
class21.clone(),
|
||||||
|
]));
|
||||||
|
assert_eq!(
|
||||||
|
query_control_device_table_resource(&missing_coefficients, 15),
|
||||||
|
Err(ControlResourceAccessError::UnsupportedField {
|
||||||
|
factory_tag: 21,
|
||||||
|
field: "coefficient table",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
class21.coefficient_bits = Some([1.0_f32.to_bits(); 16]);
|
||||||
|
class9.coefficient_bits = None;
|
||||||
|
let missing_class9_coefficient =
|
||||||
|
synthetic_control_device_table(MissionControlDeviceTableStatus::Available(vec![
|
||||||
|
class9.clone(),
|
||||||
|
class21.clone(),
|
||||||
|
]));
|
||||||
|
assert_eq!(
|
||||||
|
query_control_device_table_resource(&missing_class9_coefficient, 15),
|
||||||
|
Err(ControlResourceAccessError::UnsupportedField {
|
||||||
|
factory_tag: 9,
|
||||||
|
field: "coefficient",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
class9.coefficient_bits = Some([1.0_f32.to_bits(); 16]);
|
||||||
|
class9.class9_selector0_scale_bits = None;
|
||||||
|
let missing_scale =
|
||||||
|
synthetic_control_device_table(MissionControlDeviceTableStatus::Available(vec![
|
||||||
|
class9.clone(),
|
||||||
|
class21.clone(),
|
||||||
|
]));
|
||||||
|
assert_eq!(
|
||||||
|
query_control_device_table_resource(&missing_scale, 15),
|
||||||
|
Err(ControlResourceAccessError::UnsupportedField {
|
||||||
|
factory_tag: 9,
|
||||||
|
field: "class-9 selector-0 scale",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
class9.class9_selector0_scale_bits = Some(1.0_f32.to_bits());
|
||||||
|
class21.coefficient_bits.as_mut().unwrap()[5] = f32::INFINITY.to_bits();
|
||||||
|
let nonfinite =
|
||||||
|
synthetic_control_device_table(MissionControlDeviceTableStatus::Available(vec![
|
||||||
|
class9.clone(),
|
||||||
|
class21.clone(),
|
||||||
|
]));
|
||||||
|
assert_eq!(
|
||||||
|
query_control_device_table_resource(&nonfinite, 15),
|
||||||
|
Err(ControlResourceAccessError::NonFiniteValue {
|
||||||
|
factory_tag: 21,
|
||||||
|
field: "coefficient",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
class21.coefficient_bits.as_mut().unwrap()[5] = 1;
|
||||||
|
let subnormal =
|
||||||
|
synthetic_control_device_table(MissionControlDeviceTableStatus::Available(vec![
|
||||||
|
class9.clone(),
|
||||||
|
class21.clone(),
|
||||||
|
]));
|
||||||
|
assert_eq!(
|
||||||
|
query_control_device_table_resource(&subnormal, 15),
|
||||||
|
Err(ControlResourceAccessError::SubnormalValue {
|
||||||
|
factory_tag: 21,
|
||||||
|
field: "coefficient",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
class21.coefficient_bits.as_mut().unwrap()[5] = f32::MAX.to_bits();
|
||||||
|
class21.coefficient_bits.as_mut().unwrap()[4] = f32::MAX.to_bits();
|
||||||
|
let overflowing_sum =
|
||||||
|
synthetic_control_device_table(MissionControlDeviceTableStatus::Available(vec![
|
||||||
|
class9, class21,
|
||||||
|
]));
|
||||||
|
assert_eq!(
|
||||||
|
query_control_device_table_resource(&overflowing_sum, 15),
|
||||||
|
Err(ControlResourceAccessError::UnsupportedFloatRange {
|
||||||
|
factory_tag: Some(21),
|
||||||
|
field: "sum",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn control_resource_setter_updates_matching_rows_atomically_and_round_trips() {
|
fn control_resource_setter_updates_matching_rows_atomically_and_round_trips() {
|
||||||
let mut table =
|
let mut table =
|
||||||
@@ -13276,6 +13548,50 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore = "requires licensed corpus"]
|
||||||
|
fn full_load_queries_control_selector_15_against_frozen_native_inputs() {
|
||||||
|
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 tables = loaded_mission_control_device_tables(&engine).expect("Control tables");
|
||||||
|
let expected = [
|
||||||
|
(0, Some(0x4657_2800)),
|
||||||
|
(1, Some(0x4636_d000)),
|
||||||
|
(2, Some(0x467b_4001)),
|
||||||
|
(3, Some(0x468c_a000)),
|
||||||
|
(4, Some(0x46ec_4001)),
|
||||||
|
(5, Some(0x46ec_4001)),
|
||||||
|
(6, None),
|
||||||
|
(7, None),
|
||||||
|
];
|
||||||
|
assert_eq!(tables.len(), expected.len());
|
||||||
|
for (original_id, expected_bits) in expected {
|
||||||
|
let table = tables
|
||||||
|
.iter()
|
||||||
|
.find(|table| table.original_id == Some(OriginalObjectId(original_id)))
|
||||||
|
.expect("AutoDemo Control table for original object");
|
||||||
|
let actual = query_loaded_mission_control_resource(&engine, table.handle, 15)
|
||||||
|
.expect("selector 15 query")
|
||||||
|
.map(f32::to_bits);
|
||||||
|
assert_eq!(actual, expected_bits, "original object {original_id}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[ignore = "requires licensed corpus"]
|
#[ignore = "requires licensed corpus"]
|
||||||
fn headless_step_runs_resource_schedule_and_rolls_back_both_error_paths() {
|
fn headless_step_runs_resource_schedule_and_rolls_back_both_error_paths() {
|
||||||
|
|||||||
@@ -1060,6 +1060,71 @@ factory-класса. В Rust неизвестный текущий тег за
|
|||||||
CPU-вызов, ни локальные векторы не означают включения бюджетного шага в Control
|
CPU-вызов, ни локальные векторы не означают включения бюджетного шага в Control
|
||||||
clock.
|
clock.
|
||||||
|
|
||||||
|
#### Глобальный Control getter: сырой селектор `0x0f`
|
||||||
|
|
||||||
|
В `Control.dll` SHA-256
|
||||||
|
`bcb86318089028548a178b03a21accdce15df0cc85ddd91674cf3c2f55012c6e`
|
||||||
|
`FUN_1002b410` (RVA `0x2b410`) принимает сырой селектор в `EDX` и указатель
|
||||||
|
результата из `[ESP+4]`; общий выход использует `RET 4`. Сырой селектор `0x0f`
|
||||||
|
попадает в case RVA `0x2b8f5`. Владелец хранит ссылки на объекты, добавленные
|
||||||
|
фабриками `9` (`owner+0x5cc`) и `21` (`owner+0x5d0`).
|
||||||
|
|
||||||
|
Фабрика `21` вызывает общий getter `FUN_10021d00` (RVA `0x21d00`): selectors
|
||||||
|
`0..5` читают шесть текущих `coefficient_bits` по `this+0x54`. Общий загрузчик
|
||||||
|
RVA `0x21d50` первоначально копирует descriptor words `[11..16]` в это поле. Фабрика `9`
|
||||||
|
использует getter `FUN_100255c0` (RVA `0x255c0`), который для selector `0`
|
||||||
|
умножает коэффициент `[this+0x54]` на множитель `[this+0x11c]`. Поэтому Rust
|
||||||
|
выбирает последнюю строку каждого `factory_tag` `9` и `21` в исходном порядке
|
||||||
|
таблицы; текущий `descriptor.tag` не меняет выбранную фабрику. Если любой строки нет,
|
||||||
|
результат отсутствует до проверки полей другой строки.
|
||||||
|
|
||||||
|
Арифметика case `0x0f` сохраняет порядок и отдельное округление каждого шага:
|
||||||
|
|
||||||
|
```text
|
||||||
|
sum = f32(D5)
|
||||||
|
for k = 4, 3, 2, 1, 0:
|
||||||
|
sum = f32(sum + Dk)
|
||||||
|
out = f32(C0 * sum)
|
||||||
|
```
|
||||||
|
|
||||||
|
`Dk` — текущий коэффициент `k` из `coefficient_bits` фабрики `21`; `C0` —
|
||||||
|
selector `0` фабрики `9`, то есть
|
||||||
|
`coefficient_bits[0] * class9_selector0_scale_bits`. Общие `multiplier_bits`, `node_provider_status` и
|
||||||
|
`native_word_50_bits` в этом обратном вызове не читаются. Rust проверяет, что
|
||||||
|
входы и промежуточные результаты конечны и являются обычными числами либо
|
||||||
|
точным нулём; переполнение, денормализованные числа и отсутствующие поля
|
||||||
|
остаются типизированными ошибками. Диапазон
|
||||||
|
проверяется через `f64`, а затем операция выполняется в `f32` с округлением
|
||||||
|
после каждого сложения. Это воспроизводит описанные записи `f32`, но не
|
||||||
|
моделирует полное управляющее состояние x87. Этот getter не реализует весь Control
|
||||||
|
metadata `0x36`, Handler `43` или каталог свойств.
|
||||||
|
|
||||||
|
Лицензионный acceptance-test загружает `MISSIONS/Autodemo.00/data.tma` с явным
|
||||||
|
`MissionStartupInput { queue_time_ms: 100_000, research_creation_gate: false }`
|
||||||
|
и вызывает публичный `query_loaded_mission_control_resource(..., 15)`. Native
|
||||||
|
вызов исходного `Control.dll` того же SHA выполнен с `executionCW=0x007f` и
|
||||||
|
получил следующие биты на Rust-подготовленном снимке входных полей:
|
||||||
|
|
||||||
|
| `original_id` | Результат selector `0x0f` |
|
||||||
|
| --- | --- |
|
||||||
|
| `0` | `0x46572800` |
|
||||||
|
| `1` | `0x4636d000` |
|
||||||
|
| `2` | `0x467b4001` |
|
||||||
|
| `3` | `0x468ca000` |
|
||||||
|
| `4` | `0x46ec4001` |
|
||||||
|
| `5` | `0x46ec4001` |
|
||||||
|
|
||||||
|
Для `original_id` `6` и `7` таблица Rust не содержит обоих требуемых объектов
|
||||||
|
фабрик (`factory_tag` `9` и `21`), поэтому публичный запрос возвращает `None`.
|
||||||
|
Исходный обратный вызов для этих двух объектов отдельно не запускался; ветвь
|
||||||
|
неуспеха native getter-а проверена на отдельном векторе с отсутствующим указателем.
|
||||||
|
|
||||||
|
Сравнение шести значений проверяет формулу getter-а на исходных игровых ресурсах
|
||||||
|
и снимке входных полей, подготовленном Rust. Оно не снимает состояние фабрик,
|
||||||
|
созданных оригиналом. Время и флаг `queue_time_ms` и
|
||||||
|
`research_creation_gate` задаёт тест, поэтому эти значения не подтверждают
|
||||||
|
начальное состояние запуска AutoDemo в оригинале.
|
||||||
|
|
||||||
#### Фабричный tag 26: изменяемая добавка
|
#### Фабричный tag 26: изменяемая добавка
|
||||||
|
|
||||||
В этой сборке `Control.dll` SHA-256
|
В этой сборке `Control.dll` SHA-256
|
||||||
|
|||||||
+31
-11
@@ -992,21 +992,41 @@ descriptor tag `19` и положительным selector-0 ответом; п
|
|||||||
его vtable slot `+0x10` статически указывает на общий getter RVA `0x21d00`, но
|
его vtable slot `+0x10` статически указывает на общий getter RVA `0x21d00`, но
|
||||||
отдельный aggregate oracle этот класс не запускал. Rust хранит начальный scale
|
отдельный aggregate oracle этот класс не запускал. Rust хранит начальный scale
|
||||||
factory `9` и предоставляет `query_loaded_mission_control_resource` для selector
|
factory `9` и предоставляет `query_loaded_mission_control_resource` для selector
|
||||||
`1`/`2` и `set_loaded_mission_control_multiplier` для setter-а `1`. Вызовы
|
`1`, `2` и `15`, а также `set_loaded_mission_control_multiplier` для setter-а
|
||||||
сначала проверяют живой `ObjectHandle`, затем обращаются к таблице этого объекта
|
`1`. Вызовы сначала проверяют живой `ObjectHandle`, затем обращаются к таблице
|
||||||
из полной загрузки миссии. Синтетические тесты проверяют границы пустой таблицы,
|
этого объекта из полной загрузки миссии. Синтетические тесты проверяют границы
|
||||||
нулевого знаменателя, неподдержанных factory tags и чисел за диапазоном
|
пустой таблицы, нулевого знаменателя, отсутствующих объектов фабрик и чисел за
|
||||||
нормального `f32`; лицензированный тест сверяет реальные descriptor tags и
|
диапазоном нормального `f32`; лицензированный тест сверяет реальные descriptor
|
||||||
получает для `min_l_n1.dat` сумму `16` и отношение `1` до setter-а.
|
tags и получает для `min_l_n1.dat` сумму `16` и отношение `1` до setter-а.
|
||||||
|
|
||||||
|
Сырой селектор `0x0f` соединяет два ранее созданных объекта владельца: у строки
|
||||||
|
с `factory_tag = 9` берётся коэффициент и его текущий множитель, а у строки с
|
||||||
|
`factory_tag = 21` — шесть текущих `coefficient_bits`. `INTO` может поменять
|
||||||
|
`descriptor.tag`, не заменяя объект фабрики, поэтому query выбирает последние
|
||||||
|
строки по `factory_tag`, а не по текущему tag. Если любой объект фабрики
|
||||||
|
отсутствует, ответ — `None`. Иначе шесть коэффициентов складываются с индекса `5`
|
||||||
|
к `0` с округлением до `f32` после каждого сложения; результат умножается на
|
||||||
|
selector `0` фабрики `9` (`coefficient_bits[0] * class9_selector0_scale_bits`).
|
||||||
|
Общие `multiplier_bits`, `node_provider_status` и `native_word_50_bits` в этой
|
||||||
|
формуле не участвуют.
|
||||||
|
|
||||||
|
Лицензионная проверка загружает AutoDemo с явно переданными
|
||||||
|
`MissionStartupInput { queue_time_ms: 100_000, research_creation_gate: false }`
|
||||||
|
и вызывает `query_loaded_mission_control_resource(..., 15)` для исходных
|
||||||
|
объектов `0..5`; для `6` и `7` Rust возвращает `None`, потому что не хватает как
|
||||||
|
минимум одного объекта фабрики. Native-биты для `0..5` приведены в технической
|
||||||
|
главе. Проверка сверяет результат исходного геттера на снимке входных полей,
|
||||||
|
подготовленном Rust. Время и флаг заданы самим тестом; они не доказывают
|
||||||
|
начальный режим запуска AutoDemo в оригинале и не являются захватом его фабрик
|
||||||
|
или состояния объекта Control.
|
||||||
|
|
||||||
В этом API пока реализованы только getter selectors `1`/`2` и setter `1`.
|
|
||||||
Отдельный native setter selector `17`, tag-specific методы прочих factory tags,
|
Отдельный native setter selector `17`, tag-specific методы прочих factory tags,
|
||||||
их внешние эффекты и вызов из регулярного шага Behavior здесь не моделируются.
|
их внешние эффекты и вызов из регулярного шага Behavior здесь не моделируются.
|
||||||
Числа должны оставаться конечными и нормальными либо точным нулём; произведения
|
Числа должны оставаться конечными и нормальными либо точным нулём; произведения
|
||||||
и отношения за поддерживаемым диапазоном `f32` возвращают явную ошибку, а не
|
и суммы за поддерживаемым диапазоном `f32` возвращают явную ошибку, а не
|
||||||
подменяются нулём или `None`. При проверке диапазона Rust оценивает операцию в
|
подменяются нулём или `None`. При проверке диапазона Rust сначала оценивает
|
||||||
`f64`, затем выполняет её в `f32`; это ограниченная проверка диапазона, а не
|
границы в `f64`, затем выполняет каждую операцию в `f32`; это сохраняет
|
||||||
эмуляция полного x87 accumulator-а.
|
пошаговое округление этого getter-а, но не эмулирует весь x87 execution state.
|
||||||
|
|
||||||
Отдельная ветка того же GOG `AniMesh.dll` показывает, когда создаётся Behavior
|
Отдельная ветка того же GOG `AniMesh.dll` показывает, когда создаётся Behavior
|
||||||
для host object. В `FUN_10002ff0` (RVA `0x2ff0`, SHA-256
|
для host object. В `FUN_10002ff0` (RVA `0x2ff0`, SHA-256
|
||||||
|
|||||||
Reference in new issue
Block a user