feat(runtime): support class-9 and default Control budgets
Docs Deploy / Build and Deploy MkDocs (push) Successful in 35s
Docs Deploy / Build and Deploy MkDocs (push) Successful in 35s
This commit is contained in:
5 files changed
+986
-67
No files matched your search
@@ -201,6 +201,10 @@ pub struct EffectiveControlDevice {
|
|||||||
pub multiplier_bits: Option<u32>,
|
pub multiplier_bits: Option<u32>,
|
||||||
/// Class-9-only scale used by its selector-0 getter.
|
/// Class-9-only scale used by its selector-0 getter.
|
||||||
pub class9_selector0_scale_bits: Option<u32>,
|
pub class9_selector0_scale_bits: Option<u32>,
|
||||||
|
/// Six class-9 budget values at native `+0x98..+0xac`.
|
||||||
|
pub class9_values_bits: Option<[u32; 6]>,
|
||||||
|
/// Six accumulated class-9 changes at native `+0xb0..+0xc4`.
|
||||||
|
pub class9_deltas_bits: Option<[u32; 6]>,
|
||||||
/// Current native word at `+0x50` when it is known.
|
/// Current native word at `+0x50` when it is known.
|
||||||
pub native_word_50_bits: Option<u32>,
|
pub native_word_50_bits: Option<u32>,
|
||||||
/// Current native tag-15 byte at `+0x94`; constructors currently set zero.
|
/// Current native tag-15 byte at `+0x94`; constructors currently set zero.
|
||||||
@@ -419,7 +423,7 @@ pub enum MissionControlDeviceTableUnavailableReason {
|
|||||||
unit_component_index: usize,
|
unit_component_index: usize,
|
||||||
target_index: u32,
|
target_index: u32,
|
||||||
},
|
},
|
||||||
/// The receiver tag does not have a verified common override method.
|
/// The receiver's override behavior is not supported.
|
||||||
UnsupportedOverrideReceiver { factory_tag: u32 },
|
UnsupportedOverrideReceiver { factory_tag: u32 },
|
||||||
/// The EXTO key needs a global model-node mapping that is not available.
|
/// The EXTO key needs a global model-node mapping that is not available.
|
||||||
UnsupportedExToNodeMapping { unit_component_index: usize },
|
UnsupportedExToNodeMapping { unit_component_index: usize },
|
||||||
@@ -2278,8 +2282,9 @@ pub fn distribute_loaded_mission_control_provider_amount(
|
|||||||
|
|
||||||
/// Runs the supported loaded Control resource-budget pass (`Control.dll+0x2d340`).
|
/// Runs the supported loaded Control resource-budget pass (`Control.dll+0x2d340`).
|
||||||
///
|
///
|
||||||
/// The pass currently supports original factory callbacks for tags 15 and 19,
|
/// The pass supports the original factory callbacks for tags 9, 15, and 19,
|
||||||
/// with current descriptor tags 15 and 19 (native categories 0 and 1). It is an explicit CPU update;
|
/// plus the generic default callback pair. Current descriptor category tags are
|
||||||
|
/// supported for the native map prefix `0..=34`. This is an explicit CPU update;
|
||||||
/// the mission clock does not call it yet. Rate outputs at `+0x5dc/+0x5e0` are
|
/// the mission clock does not call it yet. Rate outputs at `+0x5dc/+0x5e0` are
|
||||||
/// stored on the table after a successful pass.
|
/// stored on the table after a successful pass.
|
||||||
///
|
///
|
||||||
@@ -2924,7 +2929,9 @@ fn control_resource_budget_pass_inner(
|
|||||||
for index in 0..device_count {
|
for index in 0..device_count {
|
||||||
let device = control_budget_device(table, index)?;
|
let device = control_budget_device(table, index)?;
|
||||||
control_budget_category(device.descriptor.tag)?;
|
control_budget_category(device.descriptor.tag)?;
|
||||||
if !matches!(device.factory_tag, 15 | 19) {
|
if !matches!(device.factory_tag, 9 | 15 | 19)
|
||||||
|
&& !control_factory_uses_generic_budget_callbacks(device.factory_tag)
|
||||||
|
{
|
||||||
return Err(ControlResourceAccessError::UnsupportedFactoryTag {
|
return Err(ControlResourceAccessError::UnsupportedFactoryTag {
|
||||||
factory_tag: device.factory_tag,
|
factory_tag: device.factory_tag,
|
||||||
});
|
});
|
||||||
@@ -3019,11 +3026,14 @@ fn control_resource_budget_pass_inner(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn control_budget_category(tag: u32) -> Result<usize, ControlResourceAccessError> {
|
fn control_budget_category(tag: u32) -> Result<usize, ControlResourceAccessError> {
|
||||||
match tag {
|
const NATIVE_CATEGORY_PREFIX: [usize; 35] = [
|
||||||
15 => Ok(0),
|
0, 4, 4, 0, 2, 3, 0, 0, 2, 5, 5, 0, 0, 0, 1, 0, 3, 2, 0, 1, 3, 5, 4, 0, 4, 0, 3, 5, 0, 0,
|
||||||
19 => Ok(1),
|
4, 0, 1, 2, 4,
|
||||||
_ => Err(ControlResourceAccessError::UnsupportedCurrentTag { tag }),
|
];
|
||||||
}
|
NATIVE_CATEGORY_PREFIX
|
||||||
|
.get(tag as usize)
|
||||||
|
.copied()
|
||||||
|
.ok_or(ControlResourceAccessError::UnsupportedCurrentTag { tag })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn control_budget_device(
|
fn control_budget_device(
|
||||||
@@ -3192,12 +3202,95 @@ fn control_device_resource_rate(
|
|||||||
let Some(provider_value) = control_device_budget_active_provider_value(table, device)? else {
|
let Some(provider_value) = control_device_budget_active_provider_value(table, device)? else {
|
||||||
return Ok(0.0);
|
return Ok(0.0);
|
||||||
};
|
};
|
||||||
let coefficient_0 = control_device_budget_coefficient(device, 0)?;
|
|
||||||
let descriptor_rate =
|
let descriptor_rate =
|
||||||
control_device_budget_descriptor_float(device, 8, "descriptor resource rate +0x20")?;
|
control_device_budget_descriptor_float(device, 8, "descriptor resource rate +0x20")?;
|
||||||
|
|
||||||
match device.factory_tag {
|
match device.factory_tag {
|
||||||
|
9 => {
|
||||||
|
let coefficient_0 = control_device_budget_coefficient(device, 0)?;
|
||||||
|
let coefficient_1 = control_device_budget_coefficient(device, 1)?;
|
||||||
|
let coefficient_2 = control_device_budget_coefficient(device, 2)?;
|
||||||
|
let class9_scale = control_device_float(
|
||||||
|
device,
|
||||||
|
device.class9_selector0_scale_bits,
|
||||||
|
"class-9 selector-0 scale",
|
||||||
|
)?;
|
||||||
|
let capacity_scale = checked_control_product(
|
||||||
|
device.factory_tag,
|
||||||
|
"class-9 scaled capacity coefficient",
|
||||||
|
coefficient_0,
|
||||||
|
class9_scale,
|
||||||
|
)?;
|
||||||
|
let values =
|
||||||
|
device
|
||||||
|
.class9_values_bits
|
||||||
|
.ok_or(ControlResourceAccessError::UnsupportedField {
|
||||||
|
factory_tag: device.factory_tag,
|
||||||
|
field: "class-9 six values +0x98",
|
||||||
|
})?;
|
||||||
|
let mut value_sum = 0.0_f32;
|
||||||
|
for bits in values {
|
||||||
|
value_sum = checked_control_sum(
|
||||||
|
device.factory_tag,
|
||||||
|
"class-9 value sum",
|
||||||
|
value_sum,
|
||||||
|
checked_control_float(
|
||||||
|
device.factory_tag,
|
||||||
|
"class-9 value",
|
||||||
|
f32::from_bits(bits),
|
||||||
|
)?,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
let free_values = checked_control_sum(
|
||||||
|
device.factory_tag,
|
||||||
|
"class-9 free value capacity",
|
||||||
|
6.0,
|
||||||
|
-value_sum,
|
||||||
|
)?;
|
||||||
|
let capacity = checked_control_product(
|
||||||
|
device.factory_tag,
|
||||||
|
"class-9 available capacity",
|
||||||
|
free_values,
|
||||||
|
capacity_scale,
|
||||||
|
)?;
|
||||||
|
let query = checked_control_product(
|
||||||
|
device.factory_tag,
|
||||||
|
"class-9 provider coefficient",
|
||||||
|
coefficient_1,
|
||||||
|
provider_value,
|
||||||
|
)?;
|
||||||
|
let query_interval = checked_control_product(
|
||||||
|
device.factory_tag,
|
||||||
|
"class-9 provider rate times interval",
|
||||||
|
query,
|
||||||
|
delta_time_seconds,
|
||||||
|
)?;
|
||||||
|
let interval_rate = checked_control_product(
|
||||||
|
device.factory_tag,
|
||||||
|
"class-9 interval descriptor rate",
|
||||||
|
delta_time_seconds,
|
||||||
|
descriptor_rate,
|
||||||
|
)?;
|
||||||
|
let selected = if capacity < query_interval {
|
||||||
|
capacity
|
||||||
|
} else {
|
||||||
|
query_interval
|
||||||
|
};
|
||||||
|
let coefficient_cost = checked_control_product(
|
||||||
|
device.factory_tag,
|
||||||
|
"class-9 coefficient cost",
|
||||||
|
coefficient_2,
|
||||||
|
selected,
|
||||||
|
)?;
|
||||||
|
checked_control_sum(
|
||||||
|
device.factory_tag,
|
||||||
|
"class-9 resource rate",
|
||||||
|
-interval_rate,
|
||||||
|
-coefficient_cost,
|
||||||
|
)
|
||||||
|
}
|
||||||
19 => {
|
19 => {
|
||||||
|
let coefficient_0 = control_device_budget_coefficient(device, 0)?;
|
||||||
let multiplier = control_device_float(device, device.multiplier_bits, "multiplier")?;
|
let multiplier = control_device_float(device, device.multiplier_bits, "multiplier")?;
|
||||||
let mut raw = checked_control_product(
|
let mut raw = checked_control_product(
|
||||||
device.factory_tag,
|
device.factory_tag,
|
||||||
@@ -3236,6 +3329,7 @@ fn control_device_resource_rate(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
15 => {
|
15 => {
|
||||||
|
let coefficient_0 = control_device_budget_coefficient(device, 0)?;
|
||||||
let coefficient_1 = control_device_budget_coefficient(device, 1)?;
|
let coefficient_1 = control_device_budget_coefficient(device, 1)?;
|
||||||
let interval_rate = checked_control_product(
|
let interval_rate = checked_control_product(
|
||||||
device.factory_tag,
|
device.factory_tag,
|
||||||
@@ -3284,6 +3378,15 @@ fn control_device_resource_rate(
|
|||||||
-coefficient_cost,
|
-coefficient_cost,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
factory_tag if control_factory_uses_generic_budget_callbacks(factory_tag) => {
|
||||||
|
let interval_rate = checked_control_product(
|
||||||
|
device.factory_tag,
|
||||||
|
"generic Control interval descriptor rate",
|
||||||
|
delta_time_seconds,
|
||||||
|
descriptor_rate,
|
||||||
|
)?;
|
||||||
|
Ok(-interval_rate)
|
||||||
|
}
|
||||||
factory_tag => Err(ControlResourceAccessError::UnsupportedFactoryTag { factory_tag }),
|
factory_tag => Err(ControlResourceAccessError::UnsupportedFactoryTag { factory_tag }),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3354,6 +3457,82 @@ fn control_device_resource_update(
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
match device.factory_tag {
|
match device.factory_tag {
|
||||||
|
9 => {
|
||||||
|
set_control_device_multiplier(table, device_index, allocation)?;
|
||||||
|
let device = control_budget_device(table, device_index)?;
|
||||||
|
let rate = control_device_resource_rate(table, device, delta_time_seconds)?;
|
||||||
|
let allocated_rate = checked_control_product(
|
||||||
|
device.factory_tag,
|
||||||
|
"class-9 allocated rate",
|
||||||
|
rate,
|
||||||
|
allocation,
|
||||||
|
)?;
|
||||||
|
let interval_rate = checked_control_product(
|
||||||
|
device.factory_tag,
|
||||||
|
"class-9 interval descriptor rate",
|
||||||
|
delta_time_seconds,
|
||||||
|
control_device_budget_descriptor_float(
|
||||||
|
device,
|
||||||
|
8,
|
||||||
|
"descriptor resource rate +0x20",
|
||||||
|
)?,
|
||||||
|
)?;
|
||||||
|
let remaining = checked_control_sum(
|
||||||
|
device.factory_tag,
|
||||||
|
"class-9 remaining adjustment",
|
||||||
|
-allocated_rate,
|
||||||
|
-interval_rate,
|
||||||
|
)?;
|
||||||
|
if remaining == 0.0 {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let coefficient_2 = control_device_budget_coefficient(device, 2)?;
|
||||||
|
let raw_change = if coefficient_2 > 0.0 {
|
||||||
|
checked_control_ratio("class-9 adjustment coefficient", remaining, coefficient_2)?
|
||||||
|
} else {
|
||||||
|
let coefficient_1 = control_device_budget_coefficient(device, 1)?;
|
||||||
|
let query = checked_control_product(
|
||||||
|
device.factory_tag,
|
||||||
|
"class-9 provider coefficient",
|
||||||
|
coefficient_1,
|
||||||
|
provider_value,
|
||||||
|
)?;
|
||||||
|
let query_interval = checked_control_product(
|
||||||
|
device.factory_tag,
|
||||||
|
"class-9 provider rate times interval",
|
||||||
|
query,
|
||||||
|
delta_time_seconds,
|
||||||
|
)?;
|
||||||
|
if remaining > 0.0 {
|
||||||
|
query_interval
|
||||||
|
} else {
|
||||||
|
-query_interval
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let class9_scale = control_device_float(
|
||||||
|
device,
|
||||||
|
device.class9_selector0_scale_bits,
|
||||||
|
"class-9 selector-0 scale",
|
||||||
|
)?;
|
||||||
|
let coefficient_0 = control_device_budget_coefficient(device, 0)?;
|
||||||
|
let capacity_scale = checked_control_product(
|
||||||
|
device.factory_tag,
|
||||||
|
"class-9 scaled capacity coefficient",
|
||||||
|
coefficient_0,
|
||||||
|
class9_scale,
|
||||||
|
)?;
|
||||||
|
if !table.node_provider_distribution_suspended && capacity_scale > 0.0 {
|
||||||
|
let divisor = checked_control_product(
|
||||||
|
device.factory_tag,
|
||||||
|
"class-9 six-value scale",
|
||||||
|
capacity_scale,
|
||||||
|
6.0,
|
||||||
|
)?;
|
||||||
|
let change =
|
||||||
|
checked_control_ratio("class-9 value adjustment", raw_change, divisor)?;
|
||||||
|
adjust_control_class9_values(table, device_index, change)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
19 => {
|
19 => {
|
||||||
let coefficient_0 = control_device_budget_coefficient(&device, 0)?;
|
let coefficient_0 = control_device_budget_coefficient(&device, 0)?;
|
||||||
if coefficient_0 < 0.0 {
|
if coefficient_0 < 0.0 {
|
||||||
@@ -3484,6 +3663,9 @@ fn control_device_resource_update(
|
|||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
factory_tag if control_factory_uses_generic_budget_callbacks(factory_tag) => {
|
||||||
|
set_control_device_multiplier(table, device_index, allocation)?;
|
||||||
|
}
|
||||||
factory_tag => {
|
factory_tag => {
|
||||||
return Err(ControlResourceAccessError::UnsupportedFactoryTag { factory_tag })
|
return Err(ControlResourceAccessError::UnsupportedFactoryTag { factory_tag })
|
||||||
}
|
}
|
||||||
@@ -3491,6 +3673,157 @@ fn control_device_resource_update(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_control_device_multiplier(
|
||||||
|
table: &mut MissionControlDeviceTableState,
|
||||||
|
device_index: usize,
|
||||||
|
value: f32,
|
||||||
|
) -> Result<(), ControlResourceAccessError> {
|
||||||
|
let device = control_budget_device(table, device_index)?;
|
||||||
|
let factory_tag = device.factory_tag;
|
||||||
|
let value = checked_control_float(factory_tag, "multiplier", value)?;
|
||||||
|
match &mut table.status {
|
||||||
|
MissionControlDeviceTableStatus::Available(devices) => {
|
||||||
|
let Some(device) = devices.get_mut(device_index) else {
|
||||||
|
return Err(ControlResourceAccessError::DeviceIndexOutOfRange {
|
||||||
|
device_index,
|
||||||
|
device_count: devices.len(),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
device.multiplier_bits = Some(value.to_bits());
|
||||||
|
}
|
||||||
|
MissionControlDeviceTableStatus::Unavailable(reason) => {
|
||||||
|
return Err(ControlResourceAccessError::TableUnavailable(reason.clone()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn adjust_control_class9_values(
|
||||||
|
table: &mut MissionControlDeviceTableState,
|
||||||
|
device_index: usize,
|
||||||
|
change: f32,
|
||||||
|
) -> Result<(), ControlResourceAccessError> {
|
||||||
|
const CLASS9_AVERAGE_FACTOR: f32 = f32::from_bits(0x3e2a_aaab);
|
||||||
|
|
||||||
|
let device = control_budget_device(table, device_index)?;
|
||||||
|
let values = device
|
||||||
|
.class9_values_bits
|
||||||
|
.ok_or(ControlResourceAccessError::UnsupportedField {
|
||||||
|
factory_tag: device.factory_tag,
|
||||||
|
field: "class-9 six values +0x98",
|
||||||
|
})?;
|
||||||
|
let deltas = device
|
||||||
|
.class9_deltas_bits
|
||||||
|
.ok_or(ControlResourceAccessError::UnsupportedField {
|
||||||
|
factory_tag: device.factory_tag,
|
||||||
|
field: "class-9 six deltas +0xb0",
|
||||||
|
})?;
|
||||||
|
let factory_tag = device.factory_tag;
|
||||||
|
let mut sum = 0.0_f32;
|
||||||
|
for bits in values {
|
||||||
|
sum = checked_control_sum(
|
||||||
|
factory_tag,
|
||||||
|
"class-9 value sum",
|
||||||
|
sum,
|
||||||
|
checked_control_float(factory_tag, "class-9 value", f32::from_bits(bits))?,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
let average = checked_control_product(
|
||||||
|
factory_tag,
|
||||||
|
"class-9 value average",
|
||||||
|
sum,
|
||||||
|
CLASS9_AVERAGE_FACTOR,
|
||||||
|
)?;
|
||||||
|
let mut updated_values = values;
|
||||||
|
let mut updated_deltas = deltas;
|
||||||
|
if change > 0.0 && average != 1.0 {
|
||||||
|
let remaining =
|
||||||
|
checked_control_sum(factory_tag, "class-9 remaining average", 1.0, -average)?;
|
||||||
|
let factor =
|
||||||
|
checked_control_ratio("class-9 positive adjustment factor", change, remaining)?;
|
||||||
|
let factor = if factor > 1.0 { 1.0 } else { factor };
|
||||||
|
for index in 0..6 {
|
||||||
|
let value =
|
||||||
|
checked_control_float(factory_tag, "class-9 value", f32::from_bits(values[index]))?;
|
||||||
|
let delta = checked_control_float(
|
||||||
|
factory_tag,
|
||||||
|
"class-9 accumulated delta",
|
||||||
|
f32::from_bits(deltas[index]),
|
||||||
|
)?;
|
||||||
|
let room = checked_control_sum(factory_tag, "class-9 value room", 1.0, -value)?;
|
||||||
|
let adjustment = checked_control_product(
|
||||||
|
factory_tag,
|
||||||
|
"class-9 positive value adjustment",
|
||||||
|
room,
|
||||||
|
factor,
|
||||||
|
)?;
|
||||||
|
updated_values[index] = checked_control_sum(
|
||||||
|
factory_tag,
|
||||||
|
"class-9 value after positive adjustment",
|
||||||
|
value,
|
||||||
|
adjustment,
|
||||||
|
)?
|
||||||
|
.to_bits();
|
||||||
|
updated_deltas[index] = checked_control_sum(
|
||||||
|
factory_tag,
|
||||||
|
"class-9 delta after positive adjustment",
|
||||||
|
delta,
|
||||||
|
adjustment,
|
||||||
|
)?
|
||||||
|
.to_bits();
|
||||||
|
}
|
||||||
|
} else if change < 0.0 && average != 0.0 {
|
||||||
|
let factor = checked_control_ratio("class-9 negative adjustment factor", -change, average)?;
|
||||||
|
let factor = if factor > 1.0 { 1.0 } else { factor };
|
||||||
|
for index in 0..6 {
|
||||||
|
let value =
|
||||||
|
checked_control_float(factory_tag, "class-9 value", f32::from_bits(values[index]))?;
|
||||||
|
let delta = checked_control_float(
|
||||||
|
factory_tag,
|
||||||
|
"class-9 accumulated delta",
|
||||||
|
f32::from_bits(deltas[index]),
|
||||||
|
)?;
|
||||||
|
let removed = checked_control_product(
|
||||||
|
factory_tag,
|
||||||
|
"class-9 negative value adjustment",
|
||||||
|
factor,
|
||||||
|
value,
|
||||||
|
)?;
|
||||||
|
updated_values[index] = checked_control_sum(
|
||||||
|
factory_tag,
|
||||||
|
"class-9 value after negative adjustment",
|
||||||
|
value,
|
||||||
|
-removed,
|
||||||
|
)?
|
||||||
|
.to_bits();
|
||||||
|
updated_deltas[index] = checked_control_sum(
|
||||||
|
factory_tag,
|
||||||
|
"class-9 delta after negative adjustment",
|
||||||
|
delta,
|
||||||
|
-removed,
|
||||||
|
)?
|
||||||
|
.to_bits();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match &mut table.status {
|
||||||
|
MissionControlDeviceTableStatus::Available(devices) => {
|
||||||
|
let Some(device) = devices.get_mut(device_index) else {
|
||||||
|
return Err(ControlResourceAccessError::DeviceIndexOutOfRange {
|
||||||
|
device_index,
|
||||||
|
device_count: devices.len(),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
device.class9_values_bits = Some(updated_values);
|
||||||
|
device.class9_deltas_bits = Some(updated_deltas);
|
||||||
|
}
|
||||||
|
MissionControlDeviceTableStatus::Unavailable(reason) => {
|
||||||
|
return Err(ControlResourceAccessError::TableUnavailable(reason.clone()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn apply_control_node_delta(
|
fn apply_control_node_delta(
|
||||||
table: &mut MissionControlDeviceTableState,
|
table: &mut MissionControlDeviceTableState,
|
||||||
key: u32,
|
key: u32,
|
||||||
@@ -4598,8 +4931,7 @@ fn append_effective_control_device(
|
|||||||
let provenance = control_device_record_provenance(component_index, record_index, control);
|
let provenance = control_device_record_provenance(component_index, record_index, control);
|
||||||
let mut coefficient_words = [0; 16];
|
let mut coefficient_words = [0; 16];
|
||||||
coefficient_words.copy_from_slice(&descriptor.fixed_words[11..27]);
|
coefficient_words.copy_from_slice(&descriptor.fixed_words[11..27]);
|
||||||
let coefficient_bits =
|
let coefficient_bits = Some(coefficient_words);
|
||||||
control_factory_uses_common_setter(descriptor.tag).then_some(coefficient_words);
|
|
||||||
let (multiplier_bits, default_word_50) = native_control_factory_defaults(descriptor.tag);
|
let (multiplier_bits, default_word_50) = native_control_factory_defaults(descriptor.tag);
|
||||||
let native_byte_94 = native_control_factory_byte_94(descriptor.tag);
|
let native_byte_94 = native_control_factory_byte_94(descriptor.tag);
|
||||||
let native_word_50_bits = if descriptor.fixed_words[6] == u32::MAX {
|
let native_word_50_bits = if descriptor.fixed_words[6] == u32::MAX {
|
||||||
@@ -4614,6 +4946,8 @@ fn append_effective_control_device(
|
|||||||
coefficient_bits,
|
coefficient_bits,
|
||||||
multiplier_bits,
|
multiplier_bits,
|
||||||
class9_selector0_scale_bits: (descriptor.tag == 9).then_some(0x3f80_0000),
|
class9_selector0_scale_bits: (descriptor.tag == 9).then_some(0x3f80_0000),
|
||||||
|
class9_values_bits: (descriptor.tag == 9).then_some([0x3f80_0000; 6]),
|
||||||
|
class9_deltas_bits: (descriptor.tag == 9).then_some([0; 6]),
|
||||||
native_word_50_bits,
|
native_word_50_bits,
|
||||||
native_byte_94,
|
native_byte_94,
|
||||||
descriptor: descriptor.clone(),
|
descriptor: descriptor.clone(),
|
||||||
@@ -4640,23 +4974,24 @@ fn apply_effective_control_override(
|
|||||||
control_device_record_provenance(component_index, record_index, control);
|
control_device_record_provenance(component_index, record_index, control);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn control_factory_uses_common_setter(factory_tag: u32) -> bool {
|
fn control_factory_uses_generic_budget_callbacks(factory_tag: u32) -> bool {
|
||||||
matches!(
|
!matches!(factory_tag, 1 | 2 | 5 | 9 | 10 | 15 | 17 | 19 | 26 | 30)
|
||||||
factory_tag,
|
}
|
||||||
1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 15 | 17 | 19 | 21 | 25 | 26 | 27 | 30
|
|
||||||
)
|
fn control_factory_override_is_supported(factory_tag: u32) -> bool {
|
||||||
|
!matches!(factory_tag, 1 | 17 | 26 | 30)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn native_control_factory_defaults(factory_tag: u32) -> (Option<u32>, Option<u32>) {
|
fn native_control_factory_defaults(factory_tag: u32) -> (Option<u32>, Option<u32>) {
|
||||||
match factory_tag {
|
match factory_tag {
|
||||||
1 | 4 => (Some(0x3f80_0000), Some(0x200)),
|
1 | 4 => (Some(0x3f80_0000), Some(0x200)),
|
||||||
3 | 5 | 8 | 21 | 26 => (Some(0x3f80_0000), Some(0x5)),
|
3 | 5 | 8 | 21 | 25 | 26 => (Some(0x3f80_0000), Some(0x5)),
|
||||||
10 => (Some(0x3f80_0000), Some(0x2000)),
|
10 => (Some(0x3f80_0000), Some(0x2000)),
|
||||||
17 | 19 => (Some(0x3f80_0000), Some(0x20)),
|
17 | 19 => (Some(0x3f80_0000), Some(0x20)),
|
||||||
2 | 27 => (Some(0x3f80_0000), Some(0)),
|
2 | 27 => (Some(0x3f80_0000), Some(0)),
|
||||||
9 | 25 => (Some(0x3f80_0000), None),
|
9 => (Some(0x3f80_0000), Some(0x20)),
|
||||||
15 => (Some(0x3f80_0000), Some(0)),
|
15 => (Some(0x3f80_0000), Some(0)),
|
||||||
_ => (None, None),
|
_ => (Some(0x3f80_0000), Some(0x5)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5029,10 +5364,7 @@ fn build_control_device_projection(
|
|||||||
target_index,
|
target_index,
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
if !matches!(
|
if !control_factory_override_is_supported(device.factory_tag) {
|
||||||
device.factory_tag,
|
|
||||||
2 | 3 | 4 | 5 | 8 | 9 | 10 | 15 | 19 | 21 | 25 | 27
|
|
||||||
) {
|
|
||||||
return Err(Unavailable::UnsupportedOverrideReceiver {
|
return Err(Unavailable::UnsupportedOverrideReceiver {
|
||||||
factory_tag: device.factory_tag,
|
factory_tag: device.factory_tag,
|
||||||
});
|
});
|
||||||
@@ -5927,6 +6259,8 @@ mod tests {
|
|||||||
coefficient_bits: Some(coefficients),
|
coefficient_bits: Some(coefficients),
|
||||||
multiplier_bits,
|
multiplier_bits,
|
||||||
class9_selector0_scale_bits: class9_scale_bits,
|
class9_selector0_scale_bits: class9_scale_bits,
|
||||||
|
class9_values_bits: (factory_tag == 9).then_some([0x3f80_0000; 6]),
|
||||||
|
class9_deltas_bits: (factory_tag == 9).then_some([0; 6]),
|
||||||
native_word_50_bits,
|
native_word_50_bits,
|
||||||
native_byte_94: native_control_factory_byte_94(factory_tag),
|
native_byte_94: native_control_factory_byte_94(factory_tag),
|
||||||
descriptor,
|
descriptor,
|
||||||
@@ -6079,6 +6413,201 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn control_budget_class9_device_matches_native_rate_and_update_vectors() {
|
||||||
|
let mut device = synthetic_effective_control_device(
|
||||||
|
9,
|
||||||
|
9,
|
||||||
|
0.75_f32.to_bits(),
|
||||||
|
Some(0.25_f32.to_bits()),
|
||||||
|
Some(1.25_f32.to_bits()),
|
||||||
|
);
|
||||||
|
device.key = 0;
|
||||||
|
device.native_word_50_bits = Some(0x20);
|
||||||
|
device.descriptor.fixed_words[8] = 0.2_f32.to_bits();
|
||||||
|
let coefficients = device.coefficient_bits.as_mut().expect("coefficient table");
|
||||||
|
coefficients[1] = 0.6_f32.to_bits();
|
||||||
|
coefficients[2] = 2.0_f32.to_bits();
|
||||||
|
device.class9_values_bits = Some([
|
||||||
|
0.0_f32.to_bits(),
|
||||||
|
0.25_f32.to_bits(),
|
||||||
|
0.5_f32.to_bits(),
|
||||||
|
0.75_f32.to_bits(),
|
||||||
|
1.0_f32.to_bits(),
|
||||||
|
0.9_f32.to_bits(),
|
||||||
|
]);
|
||||||
|
device.class9_deltas_bits = Some([
|
||||||
|
0.1_f32.to_bits(),
|
||||||
|
(-0.2_f32).to_bits(),
|
||||||
|
0.0_f32.to_bits(),
|
||||||
|
0.3_f32.to_bits(),
|
||||||
|
(-0.4_f32).to_bits(),
|
||||||
|
0.5_f32.to_bits(),
|
||||||
|
]);
|
||||||
|
let provider = synthetic_node_provider_row(0, 0, 0.4_f32.to_bits());
|
||||||
|
let mut table = synthetic_control_device_table_with_providers(
|
||||||
|
MissionControlDeviceTableStatus::Available(vec![device]),
|
||||||
|
MissionControlNodeProviderTableStatus::Available(vec![provider]),
|
||||||
|
);
|
||||||
|
|
||||||
|
let rate = {
|
||||||
|
let device = control_budget_device(&table, 0).expect("class-9 device");
|
||||||
|
control_device_resource_rate(&table, device, 0.5).expect("class-9 rate")
|
||||||
|
};
|
||||||
|
assert_eq!(rate.to_bits(), 0xbeae_147b);
|
||||||
|
control_device_resource_update(&mut table, 0, 0.5, 1.0).expect("class-9 update");
|
||||||
|
|
||||||
|
let MissionControlDeviceTableStatus::Available(devices) = &table.status else {
|
||||||
|
unreachable!();
|
||||||
|
};
|
||||||
|
assert_eq!(devices[0].multiplier_bits, Some(0x3f80_0000));
|
||||||
|
assert_eq!(
|
||||||
|
devices[0].class9_values_bits,
|
||||||
|
Some([
|
||||||
|
0x3d49_a637,
|
||||||
|
0x3e92_e795,
|
||||||
|
0x3f06_4d32,
|
||||||
|
0x3f43_2699,
|
||||||
|
0x3f80_0000,
|
||||||
|
0x3f67_a90a,
|
||||||
|
])
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
devices[0].class9_deltas_bits,
|
||||||
|
Some([
|
||||||
|
0x3e18_cff4,
|
||||||
|
0xbe26_fda3,
|
||||||
|
0x3cc9_a637,
|
||||||
|
0x3e9f_e6cc,
|
||||||
|
0xbecc_cccd,
|
||||||
|
0x3f01_42a4,
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut decreasing = synthetic_effective_control_device(
|
||||||
|
9,
|
||||||
|
9,
|
||||||
|
10.0_f32.to_bits(),
|
||||||
|
Some(1.0_f32.to_bits()),
|
||||||
|
Some(1.0_f32.to_bits()),
|
||||||
|
);
|
||||||
|
decreasing.key = 0;
|
||||||
|
decreasing.native_word_50_bits = Some(0x20);
|
||||||
|
decreasing.descriptor.fixed_words[8] = 0.2_f32.to_bits();
|
||||||
|
let coefficients = decreasing
|
||||||
|
.coefficient_bits
|
||||||
|
.as_mut()
|
||||||
|
.expect("coefficient table");
|
||||||
|
coefficients[1] = 0.6_f32.to_bits();
|
||||||
|
coefficients[2] = 2.0_f32.to_bits();
|
||||||
|
decreasing.class9_values_bits = Some([0x3f80_0000; 6]);
|
||||||
|
decreasing.class9_deltas_bits = Some([0; 6]);
|
||||||
|
let provider = synthetic_node_provider_row(0, 0, 0.4_f32.to_bits());
|
||||||
|
let mut table = synthetic_control_device_table_with_providers(
|
||||||
|
MissionControlDeviceTableStatus::Available(vec![decreasing]),
|
||||||
|
MissionControlNodeProviderTableStatus::Available(vec![provider]),
|
||||||
|
);
|
||||||
|
let rate = {
|
||||||
|
let device = control_budget_device(&table, 0).expect("class-9 device");
|
||||||
|
control_device_resource_rate(&table, device, 0.5).expect("class-9 rate")
|
||||||
|
};
|
||||||
|
assert_eq!(rate.to_bits(), 0xbdcc_cccd);
|
||||||
|
control_device_resource_update(&mut table, 0, 0.5, 0.5).expect("class-9 negative update");
|
||||||
|
let MissionControlDeviceTableStatus::Available(devices) = &table.status else {
|
||||||
|
unreachable!();
|
||||||
|
};
|
||||||
|
assert_eq!(devices[0].multiplier_bits, Some(0x3f00_0000));
|
||||||
|
assert_eq!(devices[0].class9_values_bits, Some([0x3f7f_e4b1; 6]));
|
||||||
|
assert_eq!(devices[0].class9_deltas_bits, Some([0xb9da_740e; 6]));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn control_class9_adjustment_matches_native_stored_average_factor() {
|
||||||
|
let mut device = synthetic_effective_control_device(
|
||||||
|
9,
|
||||||
|
9,
|
||||||
|
1.0_f32.to_bits(),
|
||||||
|
Some(1.0_f32.to_bits()),
|
||||||
|
Some(1.0_f32.to_bits()),
|
||||||
|
);
|
||||||
|
device.class9_values_bits = Some([
|
||||||
|
0,
|
||||||
|
0x3f80_0000,
|
||||||
|
0x3f80_0000,
|
||||||
|
0x3f80_0000,
|
||||||
|
0x3f80_0000,
|
||||||
|
0x3f80_0000,
|
||||||
|
]);
|
||||||
|
device.class9_deltas_bits = Some([0; 6]);
|
||||||
|
let mut table =
|
||||||
|
synthetic_control_device_table(MissionControlDeviceTableStatus::Available(vec![
|
||||||
|
device,
|
||||||
|
]));
|
||||||
|
|
||||||
|
adjust_control_class9_values(&mut table, 0, 0.1).expect("class-9 adjustment");
|
||||||
|
|
||||||
|
let MissionControlDeviceTableStatus::Available(devices) = &table.status else {
|
||||||
|
unreachable!();
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
devices[0].class9_values_bits,
|
||||||
|
Some([
|
||||||
|
0x3f19_999c,
|
||||||
|
0x3f80_0000,
|
||||||
|
0x3f80_0000,
|
||||||
|
0x3f80_0000,
|
||||||
|
0x3f80_0000,
|
||||||
|
0x3f80_0000
|
||||||
|
])
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
devices[0].class9_deltas_bits,
|
||||||
|
Some([0x3f19_999c, 0, 0, 0, 0, 0])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn class9_into_preserves_runtime_values_and_deltas() {
|
||||||
|
let mut device = synthetic_effective_control_device(
|
||||||
|
9,
|
||||||
|
9,
|
||||||
|
1.0_f32.to_bits(),
|
||||||
|
Some(1.0_f32.to_bits()),
|
||||||
|
Some(1.0_f32.to_bits()),
|
||||||
|
);
|
||||||
|
let values = [
|
||||||
|
0x3d49_a637,
|
||||||
|
0x3e92_e795,
|
||||||
|
0x3f06_4d32,
|
||||||
|
0x3f43_2699,
|
||||||
|
0x3f80_0000,
|
||||||
|
0x3f67_a90a,
|
||||||
|
];
|
||||||
|
let deltas = [
|
||||||
|
0x3e18_cff4,
|
||||||
|
0xbe26_fda3,
|
||||||
|
0x3cc9_a637,
|
||||||
|
0x3e9f_e6cc,
|
||||||
|
0xbecc_cccd,
|
||||||
|
0x3f01_42a4,
|
||||||
|
];
|
||||||
|
device.class9_values_bits = Some(values);
|
||||||
|
device.class9_deltas_bits = Some(deltas);
|
||||||
|
let replacement = synthetic_control_record(19, 99, 0x30, None);
|
||||||
|
let control = synthetic_component_control(
|
||||||
|
1,
|
||||||
|
vec![replacement.clone()],
|
||||||
|
PreparedControlEffectSection::Parsed(Vec::new()),
|
||||||
|
);
|
||||||
|
|
||||||
|
apply_effective_control_override(&mut device, &replacement, 1, 0, &control);
|
||||||
|
|
||||||
|
assert_eq!(device.factory_tag, 9);
|
||||||
|
assert_eq!(device.descriptor.tag, 19);
|
||||||
|
assert_eq!(device.class9_values_bits, Some(values));
|
||||||
|
assert_eq!(device.class9_deltas_bits, Some(deltas));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn control_budget_dispatch_matches_non_degenerate_native_pair() {
|
fn control_budget_dispatch_matches_non_degenerate_native_pair() {
|
||||||
let mut table = control_budget_test_table(0.25);
|
let mut table = control_budget_test_table(0.25);
|
||||||
@@ -6110,6 +6639,65 @@ mod tests {
|
|||||||
assert_eq!(rows[0].current_accumulated_delta_bits, 0x3d2e_1400);
|
assert_eq!(rows[0].current_accumulated_delta_bits, 0x3d2e_1400);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn control_budget_generic_default_callbacks_match_native_rate_and_update() {
|
||||||
|
for factory_tag in [3, 4, 6, 8, 12, 13, 21, 25, 27, 34] {
|
||||||
|
let mut device = synthetic_effective_control_device(
|
||||||
|
factory_tag,
|
||||||
|
factory_tag,
|
||||||
|
1.0_f32.to_bits(),
|
||||||
|
Some(1.0_f32.to_bits()),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
device.key = 0;
|
||||||
|
device.native_word_50_bits = Some(5);
|
||||||
|
device.descriptor.fixed_words[8] = 0.2_f32.to_bits();
|
||||||
|
let provider = synthetic_node_provider_row(0, 0, 0.4_f32.to_bits());
|
||||||
|
let mut table = synthetic_control_device_table_with_providers(
|
||||||
|
MissionControlDeviceTableStatus::Available(vec![device]),
|
||||||
|
MissionControlNodeProviderTableStatus::Available(vec![provider]),
|
||||||
|
);
|
||||||
|
|
||||||
|
let rate = {
|
||||||
|
let device = control_budget_device(&table, 0).expect("generic device");
|
||||||
|
control_device_resource_rate(&table, device, 0.5).expect("generic base rate")
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
rate.to_bits(),
|
||||||
|
(-0.1_f32).to_bits(),
|
||||||
|
"factory {factory_tag}"
|
||||||
|
);
|
||||||
|
control_device_resource_update(&mut table, 0, 0.5, 0.375).expect("generic base update");
|
||||||
|
let MissionControlDeviceTableStatus::Available(devices) = &table.status else {
|
||||||
|
unreachable!();
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
devices[0].multiplier_bits,
|
||||||
|
Some(0.375_f32.to_bits()),
|
||||||
|
"factory {factory_tag}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn control_budget_current_tag_categories_match_native_prefix() {
|
||||||
|
let categories = [
|
||||||
|
0, 4, 4, 0, 2, 3, 0, 0, 2, 5, 5, 0, 0, 0, 1, 0, 3, 2, 0, 1, 3, 5, 4, 0, 4, 0, 3, 5, 0,
|
||||||
|
0, 4, 0, 1, 2, 4,
|
||||||
|
];
|
||||||
|
for (tag, category) in categories.into_iter().enumerate() {
|
||||||
|
assert_eq!(
|
||||||
|
control_budget_category(tag as u32),
|
||||||
|
Ok(category),
|
||||||
|
"current descriptor tag {tag}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
assert_eq!(
|
||||||
|
control_budget_category(35),
|
||||||
|
Err(ControlResourceAccessError::UnsupportedCurrentTag { tag: 35 })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn control_budget_dispatch_keeps_callbacks_but_honors_owner_suspension() {
|
fn control_budget_dispatch_keeps_callbacks_but_honors_owner_suspension() {
|
||||||
let mut table = control_budget_test_table(1.0);
|
let mut table = control_budget_test_table(1.0);
|
||||||
@@ -6162,7 +6750,7 @@ mod tests {
|
|||||||
let mut table = control_budget_test_table(1.0);
|
let mut table = control_budget_test_table(1.0);
|
||||||
let unsupported = synthetic_effective_control_device(
|
let unsupported = synthetic_effective_control_device(
|
||||||
26,
|
26,
|
||||||
26,
|
35,
|
||||||
1.0_f32.to_bits(),
|
1.0_f32.to_bits(),
|
||||||
Some(1.0_f32.to_bits()),
|
Some(1.0_f32.to_bits()),
|
||||||
None,
|
None,
|
||||||
@@ -6175,7 +6763,7 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
control_resource_budget_pass(&mut table, 0.5),
|
control_resource_budget_pass(&mut table, 0.5),
|
||||||
Err(ControlResourceAccessError::UnsupportedCurrentTag { tag: 26 })
|
Err(ControlResourceAccessError::UnsupportedCurrentTag { tag: 35 })
|
||||||
);
|
);
|
||||||
assert_eq!(table, before);
|
assert_eq!(table, before);
|
||||||
}
|
}
|
||||||
@@ -7657,9 +8245,14 @@ mod tests {
|
|||||||
(3, 5),
|
(3, 5),
|
||||||
(4, 0x200),
|
(4, 0x200),
|
||||||
(5, 5),
|
(5, 5),
|
||||||
|
(6, 5),
|
||||||
(8, 5),
|
(8, 5),
|
||||||
|
(9, 0x20),
|
||||||
(10, 0x2000),
|
(10, 0x2000),
|
||||||
|
(12, 5),
|
||||||
|
(13, 5),
|
||||||
(21, 5),
|
(21, 5),
|
||||||
|
(25, 5),
|
||||||
(27, 0),
|
(27, 0),
|
||||||
] {
|
] {
|
||||||
let mut root = synthetic_control_record(factory_tag, 2, 0x20, None);
|
let mut root = synthetic_control_record(factory_tag, 2, 0x20, None);
|
||||||
@@ -7697,15 +8290,22 @@ mod tests {
|
|||||||
assert_eq!(device.multiplier_bits, Some(1.0_f32.to_bits()));
|
assert_eq!(device.multiplier_bits, Some(1.0_f32.to_bits()));
|
||||||
assert_eq!(device.native_word_50_bits, Some(word_50));
|
assert_eq!(device.native_word_50_bits, Some(word_50));
|
||||||
assert_eq!(device.coefficient_bits.unwrap()[0], 0x30);
|
assert_eq!(device.coefficient_bits.unwrap()[0], 0x30);
|
||||||
|
if factory_tag == 9 {
|
||||||
|
assert_eq!(device.class9_values_bits, Some([0x3f80_0000; 6]));
|
||||||
|
assert_eq!(device.class9_deltas_bits, Some([0; 6]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for factory_tag in [1, 17, 26, 30] {
|
||||||
|
assert!(!control_factory_override_is_supported(factory_tag));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn control_factory_multiplier_defaults_preserve_unknown_word_50() {
|
fn control_factory_defaults_match_native_class_construction() {
|
||||||
for factory_tag in [9, 25] {
|
for (factory_tag, word_50) in [(9, 0x20), (12, 5), (13, 5), (25, 5), (34, 5)] {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
native_control_factory_defaults(factory_tag),
|
native_control_factory_defaults(factory_tag),
|
||||||
(Some(0x3f80_0000), None)
|
(Some(0x3f80_0000), Some(word_50))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -8831,6 +9431,16 @@ mod tests {
|
|||||||
(21, 21),
|
(21, 21),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
assert_eq!(mine_control_devices[2].native_word_50_bits, Some(0x20));
|
||||||
|
assert_eq!(
|
||||||
|
mine_control_devices[2].class9_selector0_scale_bits,
|
||||||
|
Some(0x3f80_0000)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
mine_control_devices[2].class9_values_bits,
|
||||||
|
Some([0x3f80_0000; 6])
|
||||||
|
);
|
||||||
|
assert_eq!(mine_control_devices[2].class9_deltas_bits, Some([0; 6]));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
advance_loaded_mission_control_budget(&mut mine_engine, mine_control_handle, f32::NAN),
|
advance_loaded_mission_control_budget(&mut mine_engine, mine_control_handle, f32::NAN),
|
||||||
Err(ControlResourceAccessError::NonFiniteValue {
|
Err(ControlResourceAccessError::NonFiniteValue {
|
||||||
@@ -8840,7 +9450,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
advance_loaded_mission_control_budget(&mut mine_engine, mine_control_handle, 0.5),
|
advance_loaded_mission_control_budget(&mut mine_engine, mine_control_handle, 0.5),
|
||||||
Err(ControlResourceAccessError::UnsupportedCurrentTag { tag: 9 })
|
Err(ControlResourceAccessError::UnsupportedFactoryTag { factory_tag: 26 })
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
loaded_mission_control_device_tables(&mine_engine),
|
loaded_mission_control_device_tables(&mine_engine),
|
||||||
|
|||||||
@@ -54,6 +54,11 @@ internal static class BehaviorDistributorTickOracle {
|
|||||||
private const int ControlResourceTag9ConstructorRva = 0x25460;
|
private const int ControlResourceTag9ConstructorRva = 0x25460;
|
||||||
private const int ControlResourceTag15ConstructorRva = 0x22ae0;
|
private const int ControlResourceTag15ConstructorRva = 0x22ae0;
|
||||||
private const int ControlResourceTag25ConstructorRva = 0x20800;
|
private const int ControlResourceTag25ConstructorRva = 0x20800;
|
||||||
|
private const int ControlResourceTag9RateRva = 0x25700;
|
||||||
|
private const int ControlResourceTag9UpdateRva = 0x257b0;
|
||||||
|
private const int ControlResourceTag9AdjustRva = 0x25a90;
|
||||||
|
private const int ControlResourceDefaultRateRva = 0x21860;
|
||||||
|
private const int ControlResourceDefaultUpdateRva = 0x21890;
|
||||||
private const int ControlResourceTag15RateRva = 0x22b20;
|
private const int ControlResourceTag15RateRva = 0x22b20;
|
||||||
private const int ControlResourceTag15UpdateRva = 0x22bb0;
|
private const int ControlResourceTag15UpdateRva = 0x22bb0;
|
||||||
private const int ControlResourceBudgetUpdateRva = 0x2d340;
|
private const int ControlResourceBudgetUpdateRva = 0x2d340;
|
||||||
@@ -119,6 +124,7 @@ internal static class BehaviorDistributorTickOracle {
|
|||||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)] private delegate IntPtr NativeControlResourceDeviceConstructor(IntPtr self);
|
[UnmanagedFunctionPointer(CallingConvention.ThisCall)] private delegate IntPtr NativeControlResourceDeviceConstructor(IntPtr self);
|
||||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)] private delegate void NativeControlResourceBudgetUpdate(IntPtr self, float deltaSeconds);
|
[UnmanagedFunctionPointer(CallingConvention.ThisCall)] private delegate void NativeControlResourceBudgetUpdate(IntPtr self, float deltaSeconds);
|
||||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)] private delegate float NativeControlResourceDeviceRate(IntPtr self, float deltaSeconds);
|
[UnmanagedFunctionPointer(CallingConvention.ThisCall)] private delegate float NativeControlResourceDeviceRate(IntPtr self, float deltaSeconds);
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.ThisCall)] private delegate void NativeControlResourceTag9Update(IntPtr self, float deltaSeconds, float allocation);
|
||||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)] private delegate int NativeControlResourceDeviceGate(IntPtr self);
|
[UnmanagedFunctionPointer(CallingConvention.ThisCall)] private delegate int NativeControlResourceDeviceGate(IntPtr self);
|
||||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)] private delegate float NativeControlResourceAvailability(IntPtr self, byte flags);
|
[UnmanagedFunctionPointer(CallingConvention.ThisCall)] private delegate float NativeControlResourceAvailability(IntPtr self, byte flags);
|
||||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)] private delegate IntPtr NativeControlDeviceFactory(IntPtr self, uint componentId, uint keyOffset, IntPtr record);
|
[UnmanagedFunctionPointer(CallingConvention.ThisCall)] private delegate IntPtr NativeControlDeviceFactory(IntPtr self, uint componentId, uint keyOffset, IntPtr record);
|
||||||
@@ -706,6 +712,8 @@ internal static class BehaviorDistributorTickOracle {
|
|||||||
RunNativeControlTimedUpdateBaselineCases(controlModule);
|
RunNativeControlTimedUpdateBaselineCases(controlModule);
|
||||||
RunNativeControlAggregateResourceCases(controlModule);
|
RunNativeControlAggregateResourceCases(controlModule);
|
||||||
RunNativeControlResourceBudgetTag15Case(controlModule);
|
RunNativeControlResourceBudgetTag15Case(controlModule);
|
||||||
|
RunNativeControlResourceBudgetClass9Case(controlModule);
|
||||||
|
RunNativeControlGenericDefaultBudgetCase(controlModule);
|
||||||
RunNativeControlActivationCase(controlModule);
|
RunNativeControlActivationCase(controlModule);
|
||||||
RunNativeBehaviorProfileMapCases(module);
|
RunNativeBehaviorProfileMapCases(module);
|
||||||
RunNativeBehaviorVarSyntheticCase(module);
|
RunNativeBehaviorVarSyntheticCase(module);
|
||||||
@@ -1790,6 +1798,198 @@ internal static class BehaviorDistributorTickOracle {
|
|||||||
return device;
|
return device;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void RunNativeControlResourceBudgetClass9Case(IntPtr module) {
|
||||||
|
NativeControlOwnerConstructor constructOwner = (NativeControlOwnerConstructor)
|
||||||
|
Marshal.GetDelegateForFunctionPointer(IntPtr.Add(module, ControlOwnerConstructorRva),
|
||||||
|
typeof(NativeControlOwnerConstructor));
|
||||||
|
NativeControlResourceDeviceConstructor constructDevice = (NativeControlResourceDeviceConstructor)
|
||||||
|
Marshal.GetDelegateForFunctionPointer(IntPtr.Add(module, ControlResourceTag9ConstructorRva),
|
||||||
|
typeof(NativeControlResourceDeviceConstructor));
|
||||||
|
NativeControlProviderRowConstructor constructProvider = (NativeControlProviderRowConstructor)
|
||||||
|
Marshal.GetDelegateForFunctionPointer(IntPtr.Add(module, ControlProviderRowConstructorRva),
|
||||||
|
typeof(NativeControlProviderRowConstructor));
|
||||||
|
NativeControlResourceDeviceRate rate = (NativeControlResourceDeviceRate)
|
||||||
|
Marshal.GetDelegateForFunctionPointer(IntPtr.Add(module, ControlResourceTag9RateRva),
|
||||||
|
typeof(NativeControlResourceDeviceRate));
|
||||||
|
NativeControlResourceTag9Update update = (NativeControlResourceTag9Update)
|
||||||
|
Marshal.GetDelegateForFunctionPointer(IntPtr.Add(module, ControlResourceTag9UpdateRva),
|
||||||
|
typeof(NativeControlResourceTag9Update));
|
||||||
|
NativeControlContactRateUpdate adjust = (NativeControlContactRateUpdate)
|
||||||
|
Marshal.GetDelegateForFunctionPointer(IntPtr.Add(module, ControlResourceTag9AdjustRva),
|
||||||
|
typeof(NativeControlContactRateUpdate));
|
||||||
|
|
||||||
|
using (Fixture fixture = NewFixture()) {
|
||||||
|
IntPtr owner = fixture.Allocate(0x664);
|
||||||
|
Expect(constructOwner(owner, IntPtr.Zero, 4) == owner, "native class-9 budget owner constructor");
|
||||||
|
IntPtr ndp = fixture.Allocate(0x4c);
|
||||||
|
Marshal.WriteInt32(ndp, 4, FloatBits(100.0f));
|
||||||
|
IntPtr provider = fixture.Allocate(0x2c);
|
||||||
|
constructProvider(provider, ndp);
|
||||||
|
Marshal.WriteInt32(provider, 0x14, FloatBits(0.4f));
|
||||||
|
Marshal.WriteIntPtr(owner, 0x55c, provider);
|
||||||
|
Marshal.WriteInt32(owner, 0x560, 1);
|
||||||
|
|
||||||
|
IntPtr device = fixture.Allocate(0x120);
|
||||||
|
Expect(constructDevice(device) == device &&
|
||||||
|
Marshal.ReadIntPtr(device) == IntPtr.Add(module, ControlResourceTag9VtableRva),
|
||||||
|
"native class-9 constructor installs its original vtable");
|
||||||
|
Expect(Marshal.ReadInt32(device, 0x50) == 0x20,
|
||||||
|
"native class-9 constructor enables its budget callback");
|
||||||
|
for (int i = 0; i < 6; i++) {
|
||||||
|
Expect(Marshal.ReadInt32(device, 0x98 + i * 4) == FloatBits(1.0f) &&
|
||||||
|
Marshal.ReadInt32(device, 0xb0 + i * 4) == 0,
|
||||||
|
"native class-9 constructor initializes value/delta row " + i);
|
||||||
|
}
|
||||||
|
IntPtr descriptor = fixture.Allocate(0xb4);
|
||||||
|
Marshal.WriteInt32(descriptor, 0, 9);
|
||||||
|
Marshal.WriteInt32(descriptor, 0x20, FloatBits(0.2f));
|
||||||
|
Marshal.WriteIntPtr(device, 0x3c, owner);
|
||||||
|
Marshal.WriteIntPtr(device, 0x44, IntPtr.Add(owner, 4));
|
||||||
|
Marshal.WriteIntPtr(device, 0x48, descriptor);
|
||||||
|
Marshal.WriteInt32(device, 0x54, FloatBits(0.75f));
|
||||||
|
Marshal.WriteInt32(device, 0x58, FloatBits(0.6f));
|
||||||
|
Marshal.WriteInt32(device, 0x5c, FloatBits(2.0f));
|
||||||
|
Marshal.WriteInt32(device, 0x11c, FloatBits(1.25f));
|
||||||
|
float[] values = { 0.0f, 0.25f, 0.5f, 0.75f, 1.0f, 0.9f };
|
||||||
|
float[] deltas = { 0.1f, -0.2f, 0.0f, 0.3f, -0.4f, 0.5f };
|
||||||
|
for (int i = 0; i < 6; i++) {
|
||||||
|
Marshal.WriteInt32(device, 0x98 + i * 4, FloatBits(values[i]));
|
||||||
|
Marshal.WriteInt32(device, 0xb0 + i * 4, FloatBits(deltas[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
Expect(FloatBits(rate(device, 0.5f)) == unchecked((int)0xbeae147b),
|
||||||
|
"native class-9 rate uses six values and selector-0 scale");
|
||||||
|
update(device, 0.5f, 1.0f);
|
||||||
|
int[] expectedValues = {
|
||||||
|
unchecked((int)0x3d49a637), unchecked((int)0x3e92e795),
|
||||||
|
unchecked((int)0x3f064d32), unchecked((int)0x3f432699),
|
||||||
|
unchecked((int)0x3f800000), unchecked((int)0x3f67a90a),
|
||||||
|
};
|
||||||
|
int[] expectedDeltas = {
|
||||||
|
unchecked((int)0x3e18cff4), unchecked((int)0xbe26fda3),
|
||||||
|
unchecked((int)0x3cc9a637), unchecked((int)0x3e9fe6cc),
|
||||||
|
unchecked((int)0xbecccccd), unchecked((int)0x3f0142a4),
|
||||||
|
};
|
||||||
|
Expect(Marshal.ReadInt32(device, 0x4c) == FloatBits(1.0f),
|
||||||
|
"native class-9 update stores its allocation multiplier");
|
||||||
|
for (int i = 0; i < 6; i++) {
|
||||||
|
Expect(Marshal.ReadInt32(device, 0x98 + i * 4) == expectedValues[i] &&
|
||||||
|
Marshal.ReadInt32(device, 0xb0 + i * 4) == expectedDeltas[i],
|
||||||
|
"native class-9 exact value/delta vector row " + i);
|
||||||
|
}
|
||||||
|
Console.WriteLine("native-control-class9-budget: controlHash=" + ExpectedControlSha256 +
|
||||||
|
" rateRva=0x25700 updateRva=0x257b0 adjustRva=0x25a90 dt=0.5" +
|
||||||
|
" rate=0xbeae147b values=" + String.Join(",", Array.ConvertAll(expectedValues,
|
||||||
|
value => Hex(unchecked((uint)value)))) +
|
||||||
|
" deltas=" + String.Join(",", Array.ConvertAll(expectedDeltas,
|
||||||
|
value => Hex(unchecked((uint)value)))));
|
||||||
|
|
||||||
|
IntPtr decreasingDevice = fixture.Allocate(0x120);
|
||||||
|
Expect(constructDevice(decreasingDevice) == decreasingDevice,
|
||||||
|
"native class-9 negative-update constructor");
|
||||||
|
IntPtr decreasingDescriptor = fixture.Allocate(0xb4);
|
||||||
|
Marshal.WriteInt32(decreasingDescriptor, 0, 9);
|
||||||
|
Marshal.WriteInt32(decreasingDescriptor, 0x20, FloatBits(0.2f));
|
||||||
|
Marshal.WriteIntPtr(decreasingDevice, 0x3c, owner);
|
||||||
|
Marshal.WriteIntPtr(decreasingDevice, 0x44, IntPtr.Add(owner, 4));
|
||||||
|
Marshal.WriteIntPtr(decreasingDevice, 0x48, decreasingDescriptor);
|
||||||
|
Marshal.WriteInt32(decreasingDevice, 0x54, FloatBits(10.0f));
|
||||||
|
Marshal.WriteInt32(decreasingDevice, 0x58, FloatBits(0.6f));
|
||||||
|
Marshal.WriteInt32(decreasingDevice, 0x5c, FloatBits(2.0f));
|
||||||
|
Marshal.WriteInt32(decreasingDevice, 0x11c, FloatBits(1.0f));
|
||||||
|
Expect(FloatBits(rate(decreasingDevice, 0.5f)) == unchecked((int)0xbdcccccd),
|
||||||
|
"native class-9 negative case rate");
|
||||||
|
update(decreasingDevice, 0.5f, 0.5f);
|
||||||
|
Expect(Marshal.ReadInt32(decreasingDevice, 0x4c) == FloatBits(0.5f),
|
||||||
|
"native class-9 negative case allocation multiplier");
|
||||||
|
for (int i = 0; i < 6; i++) {
|
||||||
|
Expect(Marshal.ReadInt32(decreasingDevice, 0x98 + i * 4) == unchecked((int)0x3f7fe4b1) &&
|
||||||
|
Marshal.ReadInt32(decreasingDevice, 0xb0 + i * 4) == unchecked((int)0xb9da740e),
|
||||||
|
"native class-9 negative value/delta row " + i);
|
||||||
|
}
|
||||||
|
Console.WriteLine("native-control-class9-negative: rate=0xbdcccccd M=0x3f000000" +
|
||||||
|
" values=0x3f7fe4b1x6 deltas=0xb9da740ex6");
|
||||||
|
|
||||||
|
IntPtr averageDevice = fixture.Allocate(0x120);
|
||||||
|
Expect(constructDevice(averageDevice) == averageDevice, "native class-9 average constructor");
|
||||||
|
for (int i = 0; i < 6; i++) {
|
||||||
|
Marshal.WriteInt32(averageDevice, 0x98 + i * 4, FloatBits(i == 0 ? 0.0f : 1.0f));
|
||||||
|
Marshal.WriteInt32(averageDevice, 0xb0 + i * 4, 0);
|
||||||
|
}
|
||||||
|
adjust(averageDevice, 0.1f);
|
||||||
|
Expect(Marshal.ReadInt32(averageDevice, 0x98) == unchecked((int)0x3f19999c) &&
|
||||||
|
Marshal.ReadInt32(averageDevice, 0xb0) == unchecked((int)0x3f19999c),
|
||||||
|
"native class-9 adjustment multiplies by the stored 1/6 constant");
|
||||||
|
for (int i = 1; i < 6; i++) {
|
||||||
|
Expect(Marshal.ReadInt32(averageDevice, 0x98 + i * 4) == FloatBits(1.0f) &&
|
||||||
|
Marshal.ReadInt32(averageDevice, 0xb0 + i * 4) == 0,
|
||||||
|
"native class-9 constant-average leaves full rows unchanged");
|
||||||
|
}
|
||||||
|
Console.WriteLine("native-control-class9-average: first=0x3f19999c delta=0x3f19999c");
|
||||||
|
}
|
||||||
|
GC.KeepAlive(rate);
|
||||||
|
GC.KeepAlive(update);
|
||||||
|
GC.KeepAlive(adjust);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RunNativeControlGenericDefaultBudgetCase(IntPtr module) {
|
||||||
|
NativeControlOwnerConstructor constructOwner = (NativeControlOwnerConstructor)
|
||||||
|
Marshal.GetDelegateForFunctionPointer(IntPtr.Add(module, ControlOwnerConstructorRva),
|
||||||
|
typeof(NativeControlOwnerConstructor));
|
||||||
|
NativeControlResourceDeviceConstructor constructDefault = (NativeControlResourceDeviceConstructor)
|
||||||
|
Marshal.GetDelegateForFunctionPointer(IntPtr.Add(module, ControlResourceTag25ConstructorRva),
|
||||||
|
typeof(NativeControlResourceDeviceConstructor));
|
||||||
|
NativeControlResourceDeviceRate rate = (NativeControlResourceDeviceRate)
|
||||||
|
Marshal.GetDelegateForFunctionPointer(IntPtr.Add(module, ControlResourceDefaultRateRva),
|
||||||
|
typeof(NativeControlResourceDeviceRate));
|
||||||
|
NativeControlResourceTag9Update update = (NativeControlResourceTag9Update)
|
||||||
|
Marshal.GetDelegateForFunctionPointer(IntPtr.Add(module, ControlResourceDefaultUpdateRva),
|
||||||
|
typeof(NativeControlResourceTag9Update));
|
||||||
|
NativeControlProviderRowConstructor constructProvider = (NativeControlProviderRowConstructor)
|
||||||
|
Marshal.GetDelegateForFunctionPointer(IntPtr.Add(module, ControlProviderRowConstructorRva),
|
||||||
|
typeof(NativeControlProviderRowConstructor));
|
||||||
|
|
||||||
|
using (Fixture fixture = NewFixture()) {
|
||||||
|
IntPtr owner = fixture.Allocate(0x664);
|
||||||
|
Expect(constructOwner(owner, IntPtr.Zero, 4) == owner,
|
||||||
|
"native generic-budget owner constructor");
|
||||||
|
IntPtr ndp = fixture.Allocate(0x4c);
|
||||||
|
Marshal.WriteInt32(ndp, 4, FloatBits(100.0f));
|
||||||
|
IntPtr provider = fixture.Allocate(0x2c);
|
||||||
|
constructProvider(provider, ndp);
|
||||||
|
Marshal.WriteInt32(provider, 0x14, FloatBits(0.4f));
|
||||||
|
Marshal.WriteIntPtr(owner, 0x55c, provider);
|
||||||
|
Marshal.WriteInt32(owner, 0x560, 1);
|
||||||
|
|
||||||
|
IntPtr device = fixture.Allocate(0xa4);
|
||||||
|
Expect(constructDefault(device) == device, "native generic default-family constructor");
|
||||||
|
IntPtr vtable = Marshal.ReadIntPtr(device);
|
||||||
|
Expect(vtable == IntPtr.Add(module, ControlResourceTag25VtableRva) &&
|
||||||
|
Marshal.ReadIntPtr(vtable, 0x14) == IntPtr.Add(module, ControlResourceDefaultRateRva) &&
|
||||||
|
Marshal.ReadIntPtr(vtable, 0x18) == IntPtr.Add(module, ControlResourceDefaultUpdateRva),
|
||||||
|
"generic default-family vtable installs native rate/update callbacks");
|
||||||
|
Expect(Marshal.ReadInt32(device, 0x50) == 5 &&
|
||||||
|
Marshal.ReadInt32(device, 0x4c) == FloatBits(1.0f),
|
||||||
|
"generic default-family constructor defaults");
|
||||||
|
IntPtr descriptor = fixture.Allocate(0xb4);
|
||||||
|
Marshal.WriteInt32(descriptor, 0, 12);
|
||||||
|
Marshal.WriteInt32(descriptor, 0x20, FloatBits(0.2f));
|
||||||
|
Marshal.WriteIntPtr(device, 0x3c, owner);
|
||||||
|
Marshal.WriteIntPtr(device, 0x44, IntPtr.Add(owner, 4));
|
||||||
|
Marshal.WriteIntPtr(device, 0x48, descriptor);
|
||||||
|
Expect(FloatBits(rate(device, 0.5f)) == FloatBits(-0.1f),
|
||||||
|
"generic native callback rate is negative descriptor rate times dt");
|
||||||
|
update(device, 0.5f, 0.375f);
|
||||||
|
Expect(Marshal.ReadInt32(device, 0x4c) == FloatBits(0.375f),
|
||||||
|
"generic native callback stores allocation multiplier");
|
||||||
|
Console.WriteLine("native-control-generic-default-budget: controlHash=" + ExpectedControlSha256 +
|
||||||
|
" constructorRva=0x20800 vtable=0x3c448 rateRva=0x21860 updateRva=0x21890" +
|
||||||
|
" descriptor=12 B=0.2 dt=0.5 rate=-0.1 allocation=0.375");
|
||||||
|
}
|
||||||
|
GC.KeepAlive(rate);
|
||||||
|
GC.KeepAlive(update);
|
||||||
|
}
|
||||||
|
|
||||||
private static void RunNativeControlResourceBudgetTag15Case(IntPtr module) {
|
private static void RunNativeControlResourceBudgetTag15Case(IntPtr module) {
|
||||||
RunNativeControlResourceBudgetEmptyCases(module);
|
RunNativeControlResourceBudgetEmptyCases(module);
|
||||||
NativeControlOwnerConstructor constructOwner = (NativeControlOwnerConstructor)Marshal.GetDelegateForFunctionPointer(
|
NativeControlOwnerConstructor constructOwner = (NativeControlOwnerConstructor)Marshal.GetDelegateForFunctionPointer(
|
||||||
@@ -2533,6 +2733,10 @@ internal static class BehaviorDistributorTickOracle {
|
|||||||
0x3cbd8, 0x29940, applyOverride);
|
0x3cbd8, 0x29940, applyOverride);
|
||||||
RunNativeControlOverrideReceiverCase(module, 3, 0xa4, 0x20800,
|
RunNativeControlOverrideReceiverCase(module, 3, 0xa4, 0x20800,
|
||||||
0x3c448, ControlResourceNoOpStringPairRva, applyOverride);
|
0x3c448, ControlResourceNoOpStringPairRva, applyOverride);
|
||||||
|
RunNativeControlOverrideReceiverCase(module, 12, 0xa4, 0x20800,
|
||||||
|
0x3c448, ControlResourceNoOpStringPairRva, applyOverride);
|
||||||
|
RunNativeControlOverrideReceiverCase(module, 13, 0xa4, 0x20800,
|
||||||
|
0x3c448, ControlResourceNoOpStringPairRva, applyOverride);
|
||||||
GC.KeepAlive(applyOverride);
|
GC.KeepAlive(applyOverride);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2546,14 +2750,21 @@ internal static class BehaviorDistributorTickOracle {
|
|||||||
"tag-" + tag + " vtable reader resolves to its native implementation");
|
"tag-" + tag + " vtable reader resolves to its native implementation");
|
||||||
Expect(Marshal.ReadIntPtr(expectedVtable, 0x24) == IntPtr.Add(module, stringPairRva),
|
Expect(Marshal.ReadIntPtr(expectedVtable, 0x24) == IntPtr.Add(module, stringPairRva),
|
||||||
"tag-" + tag + " vtable string-pair slot matches its native callback");
|
"tag-" + tag + " vtable string-pair slot matches its native callback");
|
||||||
|
if (vtableRva == 0x3c448 || vtableRva == 0x3c720 ||
|
||||||
|
vtableRva == 0x3c800 || vtableRva == 0x3c528) {
|
||||||
|
Expect(Marshal.ReadIntPtr(expectedVtable, 0x14) == IntPtr.Add(module, ControlResourceDefaultRateRva) &&
|
||||||
|
Marshal.ReadIntPtr(expectedVtable, 0x18) == IntPtr.Add(module, ControlResourceDefaultUpdateRva),
|
||||||
|
"tag-" + tag + " vtable uses the shared default budget callbacks");
|
||||||
|
}
|
||||||
using (Fixture fixture = NewFixture()) {
|
using (Fixture fixture = NewFixture()) {
|
||||||
IntPtr owner = fixture.Allocate(0x5c8);
|
IntPtr owner = fixture.Allocate(0x5c8);
|
||||||
IntPtr deviceArray = fixture.Allocate(2 * IntPtr.Size);
|
IntPtr deviceArray = fixture.Allocate(2 * IntPtr.Size);
|
||||||
IntPtr targetDevice = (tag == 3 || tag == 5) ? IntPtr.Zero : fixture.Allocate(objectSize);
|
bool defaultFactory = tag == 3 || tag == 5 || tag == 12 || tag == 13;
|
||||||
|
IntPtr targetDevice = defaultFactory ? IntPtr.Zero : fixture.Allocate(objectSize);
|
||||||
bool variableTail = tag == 2 || tag == 3 || tag == 4 || tag == 5 || tag == 8 || tag == 10 || tag == 21 || tag == 27;
|
bool variableTail = tag == 2 || tag == 3 || tag == 4 || tag == 5 || tag == 8 || tag == 10 || tag == 21 || tag == 27;
|
||||||
IntPtr typedRecord = fixture.Allocate(variableTail ? 0xc0 : 0xb4);
|
IntPtr typedRecord = fixture.Allocate(variableTail ? 0xc0 : 0xb4);
|
||||||
if (tag == 3 || tag == 5) {
|
if (defaultFactory) {
|
||||||
// Tag 3 follows the default factory; case 5 also assigns a derived vtable.
|
// Tags 3/12/13 follow the default factory; case 5 assigns a derived vtable.
|
||||||
Marshal.WriteIntPtr(owner, 0x5bc, deviceArray);
|
Marshal.WriteIntPtr(owner, 0x5bc, deviceArray);
|
||||||
Marshal.WriteInt32(owner, 0x5c4, 2);
|
Marshal.WriteInt32(owner, 0x5c4, 2);
|
||||||
Marshal.WriteInt32(typedRecord, 0, tag);
|
Marshal.WriteInt32(typedRecord, 0, tag);
|
||||||
@@ -2573,6 +2784,10 @@ internal static class BehaviorDistributorTickOracle {
|
|||||||
Marshal.ReadInt32(targetDevice, 0x4c) == FloatBits(1.0f),
|
Marshal.ReadInt32(targetDevice, 0x4c) == FloatBits(1.0f),
|
||||||
"native extended factory constructor defaults");
|
"native extended factory constructor defaults");
|
||||||
}
|
}
|
||||||
|
if (tag == 12 || tag == 13)
|
||||||
|
Expect(Marshal.ReadInt32(targetDevice, 0x50) == 5 &&
|
||||||
|
Marshal.ReadInt32(targetDevice, 0x4c) == FloatBits(1.0f),
|
||||||
|
"native default-factory tag " + tag + " constructor defaults");
|
||||||
Marshal.WriteIntPtr(deviceArray, 0, targetDevice);
|
Marshal.WriteIntPtr(deviceArray, 0, targetDevice);
|
||||||
Marshal.WriteIntPtr(owner, 0x5bc, deviceArray);
|
Marshal.WriteIntPtr(owner, 0x5bc, deviceArray);
|
||||||
Marshal.WriteInt32(owner, 0x5c0, 1);
|
Marshal.WriteInt32(owner, 0x5c0, 1);
|
||||||
|
|||||||
@@ -360,13 +360,16 @@ API `distribute_loaded_mission_control_provider_amount` переносит
|
|||||||
|
|
||||||
Три API работают только с CPU-проекцией и не подключены к GPU/window path или
|
Три API работают только с CPU-проекцией и не подключены к GPU/window path или
|
||||||
игровому Control clock. Отдельный
|
игровому Control clock. Отдельный
|
||||||
`advance_loaded_mission_control_budget` переносит `0x2d340` для factory
|
`advance_loaded_mission_control_budget` переносит `0x2d340` для фабричных
|
||||||
callbacks `15/19` и current descriptor tags `15/19` (категории `0/1`); ставки сохраняются
|
callbacks `9`, `15`, `19` и общей default-пары; category lookup использует
|
||||||
в текущих полях `+0x5dc/+0x5e0`. Он выполняется транзакционно, а неизвестный
|
текущий descriptor tag из подтверждённого префикса `0..=34`. Ставки сохраняются
|
||||||
callback, provider или диапазон числа возвращает ошибку без частичного
|
в `+0x5dc/+0x5e0`. Шаг выполняется транзакционно, а неподдержанный
|
||||||
|
специализированный callback, provider или диапазон числа возвращает ошибку без частичного
|
||||||
изменения. Тест с нулевым знаменателем генератора подтверждает откат после уже
|
изменения. Тест с нулевым знаменателем генератора подтверждает откат после уже
|
||||||
выполненного изменения потребителем. Пустая таблица всё равно записывает в оба поля отрицательный ноль;
|
выполненного изменения потребителем. Пустая таблица всё равно записывает в оба поля отрицательный ноль;
|
||||||
неактивный callback не требует provider lookup. Gate `+0x618` изначально
|
неактивный callback не требует provider lookup. Class-9 constructor начинает
|
||||||
|
шесть значений `+0x98..+0xac` с `1.0` и шесть дельт `+0xb0..+0xc4` с нуля;
|
||||||
|
`INTO` сохраняет эти runtime-массивы. Gate `+0x618` изначально
|
||||||
выключен; событие `7` задаёт его по правилу `gate = (arg == 2)` и вызывает
|
выключен; событие `7` задаёт его по правилу `gate = (arg == 2)` и вызывает
|
||||||
`0x2dc20`, только если gate изменился. События Control в Rust пока не
|
`0x2dc20`, только если gate изменился. События Control в Rust пока не
|
||||||
исполняются, поэтому ресурсный шаг вызывается явно и не подключён к часам.
|
исполняются, поэтому ресурсный шаг вызывается явно и не подключён к часам.
|
||||||
@@ -418,13 +421,16 @@ rate, изменение provider и собственного множителя
|
|||||||
подтверждают текущие ставки `+0x5dc/+0x5e0`, включая значения `0.2`/`0.4` у
|
подтверждают текущие ставки `+0x5dc/+0x5e0`, включая значения `0.2`/`0.4` у
|
||||||
потребителя tag 15, пустой результат `-0.0` и невырожденную пару исходных
|
потребителя tag 15, пустой результат `-0.0` и невырожденную пару исходных
|
||||||
устройств tag 19/tag 15 при `dt = 0.375`; Rust-тест повторяет поля этой пары
|
устройств tag 19/tag 15 при `dt = 0.375`; Rust-тест повторяет поля этой пары
|
||||||
на синтетической таблице. Реализация принимает только описанные factory и
|
на синтетической таблице. Rust переносит class-9 rate/update и шестизначную
|
||||||
category tags; остальные callbacks и состояния остаются явными ошибками.
|
adjustment `0x25a90`, общую default callback-пару `0x21860/0x21890`, а также
|
||||||
|
category map только для current descriptor tags `0..=34`. Специализированные
|
||||||
|
factory callbacks вне этого набора остаются явными ошибками.
|
||||||
Лицензированный `min_l_n1.dat` содержит исходные factory/current tags
|
Лицензированный `min_l_n1.dat` содержит исходные factory/current tags
|
||||||
`[19, 19, 9, 15, 12, 12, 13, 13, 26, 25, 21]`. Публичный API оставляет всю
|
`[19, 19, 9, 15, 12, 12, 13, 13, 26, 25, 21]`. Публичный API оставляет всю
|
||||||
таблицу без изменений при не конечном интервале и при первом неподдержанном
|
таблицу без изменений при не конечном интервале. Для конечного интервала
|
||||||
current tag `9`; полный бюджетный проход для этой реальной таблицы поэтому не
|
предварительная проверка распознаёт категории, но находит неподдержанный
|
||||||
проверен. Успешные бюджетные native-векторы используют синтетические
|
factory tag `26` и отклоняет вызов до вычисления ставок и обновлений; полный
|
||||||
|
бюджетный проход для этой реальной таблицы поэтому не проверен. Успешные бюджетные native-векторы используют синтетические
|
||||||
NDP/Node38-строки и подтверждают локальные переходы и порядок вызовов, а не
|
NDP/Node38-строки и подтверждают локальные переходы и порядок вызовов, а не
|
||||||
полный Control tick на миссии. Прямые oracle-вызовы `0x11220` для одиночной
|
полный Control tick на миссии. Прямые oracle-вызовы `0x11220` для одиночной
|
||||||
строки используют `updateEffects = false`, что исключает ветвь Control FX и
|
строки используют `updateEffects = false`, что исключает ветвь Control FX и
|
||||||
|
|||||||
@@ -792,21 +792,23 @@ provider getter и изменение количества не подменен
|
|||||||
|
|
||||||
Rust предоставляет явный CPU-вызов
|
Rust предоставляет явный CPU-вызов
|
||||||
`advance_loaded_mission_control_budget(engine, handle, dt)`. Он переносит
|
`advance_loaded_mission_control_budget(engine, handle, dt)`. Он переносит
|
||||||
диспетчер и callbacks исходных factory tags `15` и `19`, если текущий
|
исходные callbacks factory tags `9`, `15`, `19` и общий default callback, а
|
||||||
descriptor tag равен `15` или `19` (native categories `0` или `1`). Rates `+0x5dc/+0x5e0`
|
категории выбирает по текущему descriptor tag. Native map подтверждён только
|
||||||
сохраняются отдельно. Вызов работает на копии полного состояния: неизвестный
|
для префикса тегов `0..=34`; неизвестная категория, специализированный
|
||||||
descriptor, factory callback, provider или численный диапазон возвращает
|
callback, provider или численный диапазон возвращает ошибку без публикации
|
||||||
ошибку без публикации частичных изменений. Вызов ещё не включён в Control clock;
|
частичных изменений. Ставки `+0x5dc/+0x5e0` сохраняются отдельно, а сам вызов
|
||||||
нативный oracle также проверяет пустую таблицу с отрицательным нулём в обеих
|
ещё не включён в Control clock.
|
||||||
выходных ставках и невырожденную пару исходных устройств tag 19/tag 15 при
|
|
||||||
`dt = 0.375`. Этот парный вектор проверяет изменения amount, processed amount,
|
Нативный oracle проверяет пустую таблицу с отрицательным нулём в обеих выходных
|
||||||
накопителя, доли, обоих множителей и ставок `+0x5dc/+0x5e0` по битам. Rust-тест
|
ставках, невырожденную пару исходных устройств tag 19/tag 15 при `dt = 0.375`
|
||||||
повторяет его на синтетической provider-таблице. Для лицензированного
|
и tag 9 с шестью значениями и шестью накопителями изменений. Rust-тесты
|
||||||
`min_l_n1.dat` отдельный тест подтверждает состав factory/current tags и то, что
|
повторяют их по битам, включая положительное и отрицательное обновления tag 9.
|
||||||
первый неподдержанный current tag `9` отклоняет весь шаг без изменений; полного
|
В лицензированном `min_l_n1.dat` предварительная проверка распознаёт категории,
|
||||||
успешного бюджетного прохода для реальной миссионной таблицы пока нет.
|
но находит неподдержанный factory tag `26` и отклоняет бюджет до расчёта ставок
|
||||||
Rust также отклоняет нулевой знаменатель обновления tag 19; тест вызывает эту
|
или вызова устройств; транзакционный тест подтверждает, что исходная таблица
|
||||||
ошибку после изменения provider потребителем и подтверждает откат всей таблицы.
|
не меняется. Полный бюджетный проход на реальной миссии пока не выполнен. Rust также отклоняет нулевой знаменатель
|
||||||
|
обновления tag 19; тест вызывает эту ошибку после изменения provider
|
||||||
|
потребителем и подтверждает откат всей таблицы.
|
||||||
|
|
||||||
#### Генератор tag 19 в том же бюджетном проходе
|
#### Генератор tag 19 в том же бюджетном проходе
|
||||||
|
|
||||||
@@ -846,6 +848,74 @@ tag `15`: смысл изменения этого поля зависит от
|
|||||||
Поддержанные Rust-векторы не означают, что любой Control descriptor или
|
Поддержанные Rust-векторы не означают, что любой Control descriptor или
|
||||||
полный миссионный цикл уже работает.
|
полный миссионный цикл уже работает.
|
||||||
|
|
||||||
|
#### Класс 9: ставка и шесть изменяемых значений
|
||||||
|
|
||||||
|
У `Control.dll` SHA-256
|
||||||
|
`bcb86318089028548a178b03a21accdce15df0cc85ddd91674cf3c2f55012c6e`
|
||||||
|
класс `9` использует constructor RVA `0x25460`, vtable `0x3c938`, rate
|
||||||
|
`0x25700`, update `0x257b0` и adjustment `0x25a90`. Constructor начинает шесть
|
||||||
|
значений в `+0x98..+0xac` с `1.0`, а шесть накопителей в `+0xb0..+0xc4` — с
|
||||||
|
нуля. Текущий `INTO` заменяет descriptor и общие коэффициенты, сохраняя эти
|
||||||
|
runtime-массивы.
|
||||||
|
|
||||||
|
Для rate `q` — текущее значение provider, `B` — descriptor `+0x20`, `C0..C2` —
|
||||||
|
коэффициенты, а `scale` — class-9 поле `+0x11c`. Сначала отдельно округляются
|
||||||
|
`G0 = C0 * scale`, сумма шести значений, и свободная величина `6 - sum`; затем
|
||||||
|
вычисляются доступная часть `(6 - sum) * G0` и запрос `(C1 * q) * dt`. Rate
|
||||||
|
равна `-(dt * B) - C2 * min(доступная часть, запрос)`. В update множитель
|
||||||
|
`+0x4c` сначала получает allocation, затем вычисляется
|
||||||
|
`S = -(rate(dt) * allocation) - (dt * B)`. При `C2 > 0` изменение равно
|
||||||
|
`S / C2`; иначе его знак выбирает `±((C1 * q) * dt)`. Если `+0x618` не
|
||||||
|
приостанавливает обновление, а `G0 > 0`, изменение делится на `G0 * 6` и
|
||||||
|
передаётся `0x25a90`.
|
||||||
|
|
||||||
|
Adjustment умножает упорядоченную сумму шести значений на единственную
|
||||||
|
binary32-константу `0x3e2aaaab` — приблизительное `1/6`. Дополнительного деления
|
||||||
|
на шесть нет. При положительном изменении native ограничивает фактор сверху
|
||||||
|
единицей и приближает каждое значение к `1`; при отрицательном — уменьшает
|
||||||
|
каждое пропорционально ему. Каждый фактический шаг также прибавляется к своему
|
||||||
|
накопителю. Oracle запускает исходные entry points на оригинальном Control.dll
|
||||||
|
при x87 control word `0x007f`:
|
||||||
|
|
||||||
|
| Вектор | Вход | Rate / multiplier | Шесть выходных значений | Шесть выходных дельт |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| Уменьшение | `q=.4, dt=.5, B=.2, C0=10, scale=1, C1=.6, C2=2`, allocation `.5`; все значения `1`, дельты `0` | `0xbdcccccd`, `0x3f000000` | `0x3f7fe4b1` во всех строках | `0xb9da740e` во всех строках |
|
||||||
|
| Смешанный набор | `q=.4, dt=.5, B=.2, C0=.75, scale=1.25, C1=.6, C2=2`, allocation `1`; значения `[0,.25,.5,.75,1,.9]`, дельты `[.1,-.2,0,.3,-.4,.5]` | `0xbeae147b`, `0x3f800000` | `[3d49a637,3e92e795,3f064d32,3f432699,3f800000,3f67a90a]` | `[3e18cff4,be26fda3,3cc9a637,3e9fe6cc,becccccd,3f0142a4]` |
|
||||||
|
|
||||||
|
Отдельный прямой вызов `0x25a90` для значения `[0,1,1,1,1,1]`, нулевых
|
||||||
|
накопителей и добавки `.1` записывает `0x3f19999c` в первую пару value/delta;
|
||||||
|
остальные пять строк остаются неизменными. Этот vector фиксирует умножение на
|
||||||
|
сохранённую константу, а не вычисление `sum / 6`. Rust хранит оба массива
|
||||||
|
отдельно и сохраняет их при descriptor override; ошибки численного диапазона
|
||||||
|
не публикуют частичный результат.
|
||||||
|
|
||||||
|
#### Общие default budget callbacks и категории
|
||||||
|
|
||||||
|
Ветка фабрики Control RVA `0x2d4b0` направляет обычные классы в constructor
|
||||||
|
`0x20800`; он устанавливает vtable `0x3c448` и word `+0x50 = 5`. Rate и update
|
||||||
|
этого семейства — исходные `0x21860` и `0x21890`: первая возвращает
|
||||||
|
`-(dt * descriptor[+0x20])`, второй записывает allocation в `+0x4c` после
|
||||||
|
общей проверки активности provider. Фабричные теги `12`, `13` и `25` идут по
|
||||||
|
ветке default; tag `21` явно выбирает ту же пару. У tags `4`, `8` и `27` свои
|
||||||
|
vtable/constructor, но их rate/update entries указывают на ту же пару. Native
|
||||||
|
oracle проверяет factory routing тегов `12/13`, vtable pointers и прямой
|
||||||
|
вызов rate/update общей пары. Rust повторяет формулу и сохраняет исходные
|
||||||
|
word-50 defaults, в частности `0x200` у тега `4` и ноль у тега `27`.
|
||||||
|
|
||||||
|
Descriptor category lookup `0x2c3a0` возвращает следующие значения для
|
||||||
|
проверенного префикса `0..=34`:
|
||||||
|
|
||||||
|
```text
|
||||||
|
[0,4,4,0,2,3,0,0,2,5,5,0,0,0,1,0,3,2,0,1,3,5,4,0,4,0,3,5,0,0,4,0,1,2,4]
|
||||||
|
```
|
||||||
|
|
||||||
|
Именно текущий descriptor выбирает категорию; callback остаётся у исходного
|
||||||
|
factory-класса. В Rust неизвестный текущий тег за концом префикса и
|
||||||
|
специализированный factory callback не подменяются общей формулой. На
|
||||||
|
лицензированном Unit DAT полный проход доходит до factory `26` и возвращает
|
||||||
|
ошибку без частичного изменения таблицы. Ни этот CPU-вызов, ни локальные
|
||||||
|
векторы не означают включения бюджетного шага в Control clock.
|
||||||
|
|
||||||
#### Активация и начальные часы
|
#### Активация и начальные часы
|
||||||
|
|
||||||
Для Control класса `4` oracle вызывает исходный конструктор RVA `0x31490`
|
Для Control класса `4` oracle вызывает исходный конструктор RVA `0x31490`
|
||||||
|
|||||||
@@ -725,6 +725,14 @@ component ID и multiplier `2.5` при копировании новых коэ
|
|||||||
и дополнительная строка `fx` возвращают cursor `record + 0xbf`.
|
и дополнительная строка `fx` возвращают cursor `record + 0xbf`.
|
||||||
Слово CTLD `+0x18 == -1` сохраняет фабричное `+0x50` и при INTO-замене.
|
Слово CTLD `+0x18 == -1` сохраняет фабричное `+0x50` и при INTO-замене.
|
||||||
|
|
||||||
|
Ветвь фабрики `0x2d4b0` направляет factory tags без отдельного случая в
|
||||||
|
конструктор `0x20800`; его vtable использует общий reader `0x21d50` в slot
|
||||||
|
`+0x1c`. Поэтому common-field override применяется и к этой default-семье,
|
||||||
|
включая tag `6`; Rust-тест проверяет его вместе с тегами `12` и `13`. Tags
|
||||||
|
`1`, `17`, `26` и `30` остаются явными границами: фабрика выбирает для них
|
||||||
|
отдельные классы, а проверки текущей реализации ещё не покрывают их полное
|
||||||
|
setter-поведение.
|
||||||
|
|
||||||
У tag `2` slot `+0x1c` указывает на обёртку `0x29650`. Она вызывает тот же
|
У tag `2` slot `+0x1c` указывает на обёртку `0x29650`. Она вызывает тот же
|
||||||
reader, затем сохраняет selector `1` в `+0x120`, округляет selector `0` через
|
reader, затем сохраняет selector `1` в `+0x120`, округляет selector `0` через
|
||||||
x87 `FISTP` в `+0x124` и заново обнуляет массив `+0x12c` по накопленному числу
|
x87 `FISTP` в `+0x124` и заново обнуляет массив `+0x12c` по накопленному числу
|
||||||
@@ -1061,15 +1069,25 @@ Rust переносит этот порядок, сохраняет native на
|
|||||||
в массиве, его обновление перечитывает уже изменённую долю после обслуживания
|
в массиве, его обновление перечитывает уже изменённую долю после обслуживания
|
||||||
категории tag 15. Этот явный вызов хранит также две выходные ставки `+0x5dc` и
|
категории tag 15. Этот явный вызов хранит также две выходные ставки `+0x5dc` и
|
||||||
`+0x5e0`, но сам по себе ещё не запускается игровыми часами.
|
`+0x5e0`, но сам по себе ещё не запускается игровыми часами.
|
||||||
Поддержаны factory callbacks `15/19` и текущие descriptor tags `15/19`
|
Помимо класса `15` и генератора `19` поддержан класс `9`: он считает ставку
|
||||||
(категории `0/1`); неизвестное устройство,
|
через шесть собственных значений и при распределении обновляет их вместе с
|
||||||
недоступные данные provider или нулевой знаменатель останавливают весь шаг без
|
накопленными изменениями. Например, для доли provider `0.4`, интервала `0.5`,
|
||||||
частичных изменений.
|
`B = 0.2`, коэффициентов `C0 = 0.75`, `C1 = 0.6`, `C2 = 2` и масштаба
|
||||||
Другие категории и полный миссионный цикл пока не подтверждены. В лицензированном
|
`1.25` исходная ставка имеет биты `0xbeae147b`; после полного выделения
|
||||||
`min_l_n1.dat` рядом с тегами `19` и `15` есть также factory tags `9`, `12`,
|
обновлённые шесть значений совпадают с native побитно.
|
||||||
`13`, `21`, `25` и `26`; поэтому его полный проход пока явно отклоняется на
|
|
||||||
первой неподдержанной категории `9`. Проверка сохраняет всю таблицу без
|
Несколько фабричных классов используют общий callback: ставка равна
|
||||||
изменений и не доказывает успешный бюджетный шаг на реальной миссии.
|
`-(dt * B)`, а обновление записывает долю выделения в множитель. К этому
|
||||||
|
семейству относятся фабричные теги `12`, `13`, `25` и `21`, а также другие
|
||||||
|
классы, которые исходный диспетчер направляет в фабрику по умолчанию. Текущая
|
||||||
|
категория читается из descriptor: её native map подтверждён только для тегов
|
||||||
|
`0..=34`. Неподдержанный специализированный callback, недоступный provider или
|
||||||
|
неподдержанное число останавливают шаг без частичных изменений. В
|
||||||
|
лицензированном `min_l_n1.dat` предварительная проверка распознаёт текущие
|
||||||
|
категории и выявляет неподдержанный factory tag `26`; она отклоняет бюджетный
|
||||||
|
вызов до расчёта ставок и обновлений, сохраняя таблицу целиком. Полный
|
||||||
|
бюджетный проход на реальной миссии и подключение к игровым часам пока не
|
||||||
|
подтверждены.
|
||||||
|
|
||||||
Отдельный путь объекта класса `4` умножает интервал на контактный коэффициент и
|
Отдельный путь объекта класса `4` умножает интервал на контактный коэффициент и
|
||||||
передаёт изменение строке. В проверенном примере коэффициент `1`, интервал `2`,
|
передаёт изменение строке. В проверенном примере коэффициент `1`, интервал `2`,
|
||||||
|
|||||||
Reference in new issue
Block a user