feat(runtime): support tag-26 Control resource budgets
Docs Deploy / Build and Deploy MkDocs (push) Successful in 41s
Docs Deploy / Build and Deploy MkDocs (push) Successful in 41s
This commit is contained in:
5 files changed
+688
-42
No files matched your search
@@ -201,6 +201,8 @@ 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>,
|
||||||
|
/// Factory-26 mutable resource-rate addition at native `+0xa4`.
|
||||||
|
pub tag26_extra_rate_bits: Option<u32>,
|
||||||
/// Six class-9 budget values at native `+0x98..+0xac`.
|
/// Six class-9 budget values at native `+0x98..+0xac`.
|
||||||
pub class9_values_bits: Option<[u32; 6]>,
|
pub class9_values_bits: Option<[u32; 6]>,
|
||||||
/// Six accumulated class-9 changes at native `+0xb0..+0xc4`.
|
/// Six accumulated class-9 changes at native `+0xb0..+0xc4`.
|
||||||
@@ -1973,8 +1975,9 @@ pub fn query_loaded_mission_control_resource(
|
|||||||
/// Queries one loaded Control device through its original factory getter.
|
/// Queries one loaded Control device through its original factory getter.
|
||||||
///
|
///
|
||||||
/// Selector `0xe00` is currently modeled for current descriptor tag 26 with
|
/// Selector `0xe00` is currently modeled for current descriptor tag 26 with
|
||||||
/// factory tag 26. The loaded NDP provider value is its initial native state;
|
/// factory tag 26. The query reads the current provider, coefficient, and
|
||||||
/// later Control updates are not synchronized into this snapshot.
|
/// multiplier in the CPU projection. Explicit Control update calls can change
|
||||||
|
/// those values; the mission clock does not advance them automatically.
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// Returns an error for an invalid handle, unavailable mission/table/provider,
|
/// Returns an error for an invalid handle, unavailable mission/table/provider,
|
||||||
@@ -1999,14 +2002,46 @@ pub fn query_loaded_mission_control_device_selector(
|
|||||||
query_control_device_table_selector(table, device_index, selector)
|
query_control_device_table_selector(table, device_index, selector)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets a supported native Control device property.
|
||||||
|
///
|
||||||
|
/// Selector `0x500` writes the factory-26 resource-rate addition at `+0xa4`.
|
||||||
|
/// A current descriptor tag other than 26 follows the native no-result path
|
||||||
|
/// and returns `Ok(false)`; successful writes return `Ok(true)`.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// Returns an error for an invalid handle, unavailable mission/table, an
|
||||||
|
/// out-of-range device index, an unsupported selector/factory layout, or a
|
||||||
|
/// non-finite, subnormal, or out-of-range float.
|
||||||
|
pub fn set_loaded_mission_control_device_selector(
|
||||||
|
engine: &mut Engine,
|
||||||
|
handle: ObjectHandle,
|
||||||
|
device_index: usize,
|
||||||
|
selector: u32,
|
||||||
|
value: f32,
|
||||||
|
) -> Result<bool, ControlResourceAccessError> {
|
||||||
|
transform_state(&engine.world, handle).map_err(ControlResourceAccessError::World)?;
|
||||||
|
let loaded = engine
|
||||||
|
.loaded
|
||||||
|
.as_mut()
|
||||||
|
.ok_or(ControlResourceAccessError::MissionUnavailable)?;
|
||||||
|
let table = loaded
|
||||||
|
.control_device_tables
|
||||||
|
.iter_mut()
|
||||||
|
.find(|table| table.handle == handle)
|
||||||
|
.ok_or(ControlResourceAccessError::ObjectUnavailable { handle })?;
|
||||||
|
set_control_device_table_selector(table, device_index, selector, value)
|
||||||
|
}
|
||||||
|
|
||||||
/// Queries the recovered Behavior resource selectors `0x1002` and `0x1005`.
|
/// Queries the recovered Behavior resource selectors `0x1002` and `0x1005`.
|
||||||
///
|
///
|
||||||
/// Selector `0x1005` returns the loaded profile's `Transfer_Power_Out` value.
|
/// Selector `0x1005` returns the loaded profile's `Transfer_Power_Out` value.
|
||||||
/// Selector `0x1002` sums supported Control `0xe00` results in native table
|
/// Selector `0x1002` sums supported Control `0xe00` results in native table
|
||||||
/// order, rounding each addition to `f32`, then multiplies by the profile's
|
/// order, rounding each addition to `f32`, then multiplies by the profile's
|
||||||
/// `Transfer_Ore_OffBoard` value. Unsupported table/query paths return an error.
|
/// `Transfer_Ore_OffBoard` value. Unsupported table/query paths return an error.
|
||||||
/// Profile and provider values are initial full-load snapshots; later damage,
|
/// Profile values come from full mission loading, while `0xe00` reads the
|
||||||
/// Control updates, and profile changes are not synchronized into this state.
|
/// current CPU-projected provider and device state. Explicit Control updates
|
||||||
|
/// can therefore change `0x1002`; the mission clock does not update it
|
||||||
|
/// automatically.
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// Returns an error for an invalid handle, unavailable mission/profile/table,
|
/// Returns an error for an invalid handle, unavailable mission/profile/table,
|
||||||
@@ -2282,7 +2317,7 @@ 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 supports the original factory callbacks for tags 9, 15, and 19,
|
/// The pass supports the original factory callbacks for tags 9, 15, 19, and 26,
|
||||||
/// plus the generic default callback pair. Current descriptor category tags are
|
/// 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;
|
/// 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
|
||||||
@@ -2929,7 +2964,7 @@ 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, 9 | 15 | 19)
|
if !matches!(device.factory_tag, 9 | 15 | 19 | 26)
|
||||||
&& !control_factory_uses_generic_budget_callbacks(device.factory_tag)
|
&& !control_factory_uses_generic_budget_callbacks(device.factory_tag)
|
||||||
{
|
{
|
||||||
return Err(ControlResourceAccessError::UnsupportedFactoryTag {
|
return Err(ControlResourceAccessError::UnsupportedFactoryTag {
|
||||||
@@ -3289,6 +3324,22 @@ fn control_device_resource_rate(
|
|||||||
-coefficient_cost,
|
-coefficient_cost,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
26 => {
|
||||||
|
let extra_rate =
|
||||||
|
control_device_float(device, device.tag26_extra_rate_bits, "tag-26 extra rate")?;
|
||||||
|
let rate = checked_control_sum(
|
||||||
|
device.factory_tag,
|
||||||
|
"tag-26 descriptor plus extra rate",
|
||||||
|
descriptor_rate,
|
||||||
|
extra_rate,
|
||||||
|
)?;
|
||||||
|
Ok(-checked_control_product(
|
||||||
|
device.factory_tag,
|
||||||
|
"tag-26 resource rate times interval",
|
||||||
|
rate,
|
||||||
|
delta_time_seconds,
|
||||||
|
)?)
|
||||||
|
}
|
||||||
19 => {
|
19 => {
|
||||||
let coefficient_0 = control_device_budget_coefficient(device, 0)?;
|
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")?;
|
||||||
@@ -3663,6 +3714,7 @@ fn control_device_resource_update(
|
|||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
26 => set_control_device_multiplier(table, device_index, allocation)?,
|
||||||
factory_tag if control_factory_uses_generic_budget_callbacks(factory_tag) => {
|
factory_tag if control_factory_uses_generic_budget_callbacks(factory_tag) => {
|
||||||
set_control_device_multiplier(table, device_index, allocation)?;
|
set_control_device_multiplier(table, device_index, allocation)?;
|
||||||
}
|
}
|
||||||
@@ -4066,6 +4118,50 @@ fn query_control_device_table_selector(
|
|||||||
.map(Some)
|
.map(Some)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_control_device_table_selector(
|
||||||
|
table: &mut MissionControlDeviceTableState,
|
||||||
|
device_index: usize,
|
||||||
|
selector: u32,
|
||||||
|
value: f32,
|
||||||
|
) -> Result<bool, ControlResourceAccessError> {
|
||||||
|
if selector != 0x500 {
|
||||||
|
return Err(ControlResourceAccessError::UnsupportedSelector(selector));
|
||||||
|
}
|
||||||
|
let devices = match &table.status {
|
||||||
|
MissionControlDeviceTableStatus::Available(devices) => devices,
|
||||||
|
MissionControlDeviceTableStatus::Unavailable(reason) => {
|
||||||
|
return Err(ControlResourceAccessError::TableUnavailable(reason.clone()))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let device =
|
||||||
|
devices
|
||||||
|
.get(device_index)
|
||||||
|
.ok_or(ControlResourceAccessError::DeviceIndexOutOfRange {
|
||||||
|
device_index,
|
||||||
|
device_count: devices.len(),
|
||||||
|
})?;
|
||||||
|
if device.descriptor.tag != 26 {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
if device.factory_tag != 26 {
|
||||||
|
return Err(ControlResourceAccessError::UnsupportedFactoryTag {
|
||||||
|
factory_tag: device.factory_tag,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if device.tag26_extra_rate_bits.is_none() {
|
||||||
|
return Err(ControlResourceAccessError::UnsupportedField {
|
||||||
|
factory_tag: device.factory_tag,
|
||||||
|
field: "tag-26 extra rate +0xa4",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let value = checked_control_float(device.factory_tag, "tag-26 extra rate", value)?;
|
||||||
|
let MissionControlDeviceTableStatus::Available(devices) = &mut table.status else {
|
||||||
|
unreachable!("table status validated before mutation");
|
||||||
|
};
|
||||||
|
devices[device_index].tag26_extra_rate_bits = Some(value.to_bits());
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
|
||||||
fn query_behavior_profile_ore_rate(
|
fn query_behavior_profile_ore_rate(
|
||||||
table: &MissionControlDeviceTableState,
|
table: &MissionControlDeviceTableState,
|
||||||
transfer_ore_off_board_bits: u32,
|
transfer_ore_off_board_bits: u32,
|
||||||
@@ -4946,6 +5042,7 @@ 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),
|
||||||
|
tag26_extra_rate_bits: (descriptor.tag == 26).then_some(0),
|
||||||
class9_values_bits: (descriptor.tag == 9).then_some([0x3f80_0000; 6]),
|
class9_values_bits: (descriptor.tag == 9).then_some([0x3f80_0000; 6]),
|
||||||
class9_deltas_bits: (descriptor.tag == 9).then_some([0; 6]),
|
class9_deltas_bits: (descriptor.tag == 9).then_some([0; 6]),
|
||||||
native_word_50_bits,
|
native_word_50_bits,
|
||||||
@@ -4979,7 +5076,7 @@ fn control_factory_uses_generic_budget_callbacks(factory_tag: u32) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn control_factory_override_is_supported(factory_tag: u32) -> bool {
|
fn control_factory_override_is_supported(factory_tag: u32) -> bool {
|
||||||
!matches!(factory_tag, 1 | 17 | 26 | 30)
|
!matches!(factory_tag, 1 | 17 | 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>) {
|
||||||
@@ -6259,6 +6356,7 @@ 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,
|
||||||
|
tag26_extra_rate_bits: (factory_tag == 26).then_some(0),
|
||||||
class9_values_bits: (factory_tag == 9).then_some([0x3f80_0000; 6]),
|
class9_values_bits: (factory_tag == 9).then_some([0x3f80_0000; 6]),
|
||||||
class9_deltas_bits: (factory_tag == 9).then_some([0; 6]),
|
class9_deltas_bits: (factory_tag == 9).then_some([0; 6]),
|
||||||
native_word_50_bits,
|
native_word_50_bits,
|
||||||
@@ -6679,6 +6777,262 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn control_budget_tag26_rate_matches_native_additive_cost() {
|
||||||
|
let mut device = synthetic_effective_control_device(
|
||||||
|
26,
|
||||||
|
26,
|
||||||
|
1.0_f32.to_bits(),
|
||||||
|
Some(1.0_f32.to_bits()),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
device.key = 0;
|
||||||
|
device.descriptor.fixed_words[8] = 0.2_f32.to_bits();
|
||||||
|
device.tag26_extra_rate_bits = Some(0.3_f32.to_bits());
|
||||||
|
let provider = synthetic_node_provider_row(0, 0, 0.4_f32.to_bits());
|
||||||
|
let table = synthetic_control_device_table_with_providers(
|
||||||
|
MissionControlDeviceTableStatus::Available(vec![device.clone()]),
|
||||||
|
MissionControlNodeProviderTableStatus::Available(vec![provider]),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
control_device_resource_rate(&table, &device, 0.5)
|
||||||
|
.expect("tag-26 resource rate")
|
||||||
|
.to_bits(),
|
||||||
|
(-0.25_f32).to_bits()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
control_device_resource_rate(&table, &device, 0.7)
|
||||||
|
.expect("tag-26 fadd-before-fmul vector")
|
||||||
|
.to_bits(),
|
||||||
|
0xbeb3_3333
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn control_budget_tag26_factory_defaults_and_into_preserve_extra_rate() {
|
||||||
|
let mut device = synthetic_effective_control_device(
|
||||||
|
26,
|
||||||
|
26,
|
||||||
|
1.0_f32.to_bits(),
|
||||||
|
Some(1.0_f32.to_bits()),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
assert_eq!(device.tag26_extra_rate_bits, Some(0));
|
||||||
|
device.tag26_extra_rate_bits = Some(0.3_f32.to_bits());
|
||||||
|
|
||||||
|
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, 26);
|
||||||
|
assert_eq!(device.descriptor.tag, 19);
|
||||||
|
assert_eq!(device.tag26_extra_rate_bits, Some(0.3_f32.to_bits()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn control_budget_tag26_gate_returns_zero_before_provider_and_rate_reads() {
|
||||||
|
let mut device = synthetic_effective_control_device(
|
||||||
|
26,
|
||||||
|
26,
|
||||||
|
1.0_f32.to_bits(),
|
||||||
|
Some(1.0_f32.to_bits()),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
device.key = 9;
|
||||||
|
let mut table = synthetic_control_device_table_with_providers(
|
||||||
|
MissionControlDeviceTableStatus::Available(vec![device.clone()]),
|
||||||
|
MissionControlNodeProviderTableStatus::Available(Vec::new()),
|
||||||
|
);
|
||||||
|
|
||||||
|
device.native_word_50_bits = Some(0);
|
||||||
|
assert_eq!(
|
||||||
|
control_device_resource_rate(&table, &device, 0.5).expect("disabled tag-26 gate"),
|
||||||
|
0.0
|
||||||
|
);
|
||||||
|
|
||||||
|
device.native_word_50_bits = Some(5);
|
||||||
|
device.key = 0;
|
||||||
|
let mut zero_provider = synthetic_node_provider_row(0, 0, 0.0_f32.to_bits());
|
||||||
|
zero_provider.current_type1_value_bits = 0.0_f32.to_bits();
|
||||||
|
table.node_provider_status =
|
||||||
|
MissionControlNodeProviderTableStatus::Available(vec![zero_provider]);
|
||||||
|
assert_eq!(
|
||||||
|
control_device_resource_rate(&table, &device, 0.5).expect("zero provider tag-26 gate"),
|
||||||
|
0.0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn control_device_selector_500_sets_only_matching_factory26_extra_rate() {
|
||||||
|
let device = synthetic_effective_control_device(
|
||||||
|
26,
|
||||||
|
26,
|
||||||
|
1.0_f32.to_bits(),
|
||||||
|
Some(1.0_f32.to_bits()),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
let mut table =
|
||||||
|
synthetic_control_device_table(MissionControlDeviceTableStatus::Available(vec![
|
||||||
|
device,
|
||||||
|
]));
|
||||||
|
assert_eq!(
|
||||||
|
set_control_device_table_selector(&mut table, 0, 0x500, 0.25),
|
||||||
|
Ok(true)
|
||||||
|
);
|
||||||
|
let MissionControlDeviceTableStatus::Available(devices) = &table.status else {
|
||||||
|
unreachable!();
|
||||||
|
};
|
||||||
|
assert_eq!(devices[0].tag26_extra_rate_bits, Some(0.25_f32.to_bits()));
|
||||||
|
|
||||||
|
let mut other_current_tag =
|
||||||
|
synthetic_control_device_table(MissionControlDeviceTableStatus::Available(vec![
|
||||||
|
synthetic_effective_control_device(
|
||||||
|
26,
|
||||||
|
19,
|
||||||
|
1.0_f32.to_bits(),
|
||||||
|
Some(1.0_f32.to_bits()),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
]));
|
||||||
|
assert_eq!(
|
||||||
|
set_control_device_table_selector(&mut other_current_tag, 0, 0x500, 0.5),
|
||||||
|
Ok(false)
|
||||||
|
);
|
||||||
|
let MissionControlDeviceTableStatus::Available(devices) = &other_current_tag.status else {
|
||||||
|
unreachable!();
|
||||||
|
};
|
||||||
|
assert_eq!(devices[0].tag26_extra_rate_bits, Some(0));
|
||||||
|
|
||||||
|
let mut wrong_factory =
|
||||||
|
synthetic_control_device_table(MissionControlDeviceTableStatus::Available(vec![
|
||||||
|
synthetic_effective_control_device(
|
||||||
|
9,
|
||||||
|
26,
|
||||||
|
1.0_f32.to_bits(),
|
||||||
|
Some(1.0_f32.to_bits()),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
]));
|
||||||
|
let wrong_factory_before = wrong_factory.clone();
|
||||||
|
assert_eq!(
|
||||||
|
set_control_device_table_selector(&mut wrong_factory, 0, 0x500, 0.5),
|
||||||
|
Err(ControlResourceAccessError::UnsupportedFactoryTag { factory_tag: 9 })
|
||||||
|
);
|
||||||
|
assert_eq!(wrong_factory, wrong_factory_before);
|
||||||
|
|
||||||
|
let before_bad_index = table.clone();
|
||||||
|
assert_eq!(
|
||||||
|
set_control_device_table_selector(&mut table, 1, 0x500, 0.5),
|
||||||
|
Err(ControlResourceAccessError::DeviceIndexOutOfRange {
|
||||||
|
device_index: 1,
|
||||||
|
device_count: 1,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
assert_eq!(table, before_bad_index);
|
||||||
|
|
||||||
|
for (selector, value, expected) in [
|
||||||
|
(
|
||||||
|
0x501,
|
||||||
|
0.5,
|
||||||
|
ControlResourceAccessError::UnsupportedSelector(0x501),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
0x500,
|
||||||
|
f32::NAN,
|
||||||
|
ControlResourceAccessError::NonFiniteValue {
|
||||||
|
factory_tag: 26,
|
||||||
|
field: "tag-26 extra rate",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
0x500,
|
||||||
|
f32::from_bits(1),
|
||||||
|
ControlResourceAccessError::SubnormalValue {
|
||||||
|
factory_tag: 26,
|
||||||
|
field: "tag-26 extra rate",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
] {
|
||||||
|
let before = table.clone();
|
||||||
|
assert_eq!(
|
||||||
|
set_control_device_table_selector(&mut table, 0, selector, value),
|
||||||
|
Err(expected)
|
||||||
|
);
|
||||||
|
assert_eq!(table, before);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn control_budget_tag26_update_uses_native_allocation_multiplier() {
|
||||||
|
let mut device = synthetic_effective_control_device(
|
||||||
|
26,
|
||||||
|
26,
|
||||||
|
1.0_f32.to_bits(),
|
||||||
|
Some(1.0_f32.to_bits()),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
device.key = 0;
|
||||||
|
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]),
|
||||||
|
);
|
||||||
|
|
||||||
|
control_device_resource_update(&mut table, 0, 0.5, 0.375)
|
||||||
|
.expect("tag-26 allocation update");
|
||||||
|
|
||||||
|
let MissionControlDeviceTableStatus::Available(devices) = &table.status else {
|
||||||
|
unreachable!();
|
||||||
|
};
|
||||||
|
assert_eq!(devices[0].multiplier_bits, Some(0x3ec0_0000));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn control_budget_dispatch_matches_tag26_supply_and_demand() {
|
||||||
|
let mut producer = synthetic_effective_control_device(
|
||||||
|
19,
|
||||||
|
19,
|
||||||
|
1.0_f32.to_bits(),
|
||||||
|
Some(1.0_f32.to_bits()),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
producer.key = 0;
|
||||||
|
producer.descriptor.fixed_words[8] = 0.625_f32.to_bits();
|
||||||
|
|
||||||
|
let mut consumer = synthetic_effective_control_device(
|
||||||
|
26,
|
||||||
|
26,
|
||||||
|
1.0_f32.to_bits(),
|
||||||
|
Some(1.0_f32.to_bits()),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
consumer.key = 0;
|
||||||
|
consumer.descriptor.fixed_words[8] = 0.2_f32.to_bits();
|
||||||
|
consumer.tag26_extra_rate_bits = Some(0.3_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![producer, consumer]),
|
||||||
|
MissionControlNodeProviderTableStatus::Available(vec![provider]),
|
||||||
|
);
|
||||||
|
|
||||||
|
control_resource_budget_pass(&mut table, 0.5)
|
||||||
|
.expect("tag-26 demand is covered by the tag-19 supply");
|
||||||
|
|
||||||
|
let MissionControlDeviceTableStatus::Available(devices) = &table.status else {
|
||||||
|
unreachable!();
|
||||||
|
};
|
||||||
|
assert_eq!(devices[0].multiplier_bits, Some(0x3f30_0000));
|
||||||
|
assert_eq!(devices[1].multiplier_bits, Some(0x3f00_0000));
|
||||||
|
assert_eq!(table.current_output_rate_5dc_bits, 0x8000_0000);
|
||||||
|
assert_eq!(table.current_output_rate_5e0_bits, 0x3e80_0000);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn control_budget_current_tag_categories_match_native_prefix() {
|
fn control_budget_current_tag_categories_match_native_prefix() {
|
||||||
let categories = [
|
let categories = [
|
||||||
@@ -7259,6 +7613,10 @@ mod tests {
|
|||||||
query_loaded_mission_control_device_selector(&engine, handle, 0, 0xe00),
|
query_loaded_mission_control_device_selector(&engine, handle, 0, 0xe00),
|
||||||
Err(ControlResourceAccessError::MissionUnavailable)
|
Err(ControlResourceAccessError::MissionUnavailable)
|
||||||
);
|
);
|
||||||
|
assert_eq!(
|
||||||
|
set_loaded_mission_control_device_selector(&mut engine, handle, 0, 0x500, 0.25),
|
||||||
|
Err(ControlResourceAccessError::MissionUnavailable)
|
||||||
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
query_loaded_mission_behavior_resource(&engine, handle, 0x1005),
|
query_loaded_mission_behavior_resource(&engine, handle, 0x1005),
|
||||||
Err(ControlResourceAccessError::MissionUnavailable)
|
Err(ControlResourceAccessError::MissionUnavailable)
|
||||||
@@ -7318,6 +7676,12 @@ mod tests {
|
|||||||
fparkan_world::WorldError::InvalidHandle
|
fparkan_world::WorldError::InvalidHandle
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
|
assert_eq!(
|
||||||
|
set_loaded_mission_control_device_selector(&mut engine, invalid, 0, 0x500, 0.25),
|
||||||
|
Err(ControlResourceAccessError::World(
|
||||||
|
fparkan_world::WorldError::InvalidHandle
|
||||||
|
))
|
||||||
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
query_loaded_mission_control_resource(&engine, stale, 1),
|
query_loaded_mission_control_resource(&engine, stale, 1),
|
||||||
Err(ControlResourceAccessError::World(
|
Err(ControlResourceAccessError::World(
|
||||||
@@ -7336,6 +7700,12 @@ mod tests {
|
|||||||
fparkan_world::WorldError::StaleHandle
|
fparkan_world::WorldError::StaleHandle
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
|
assert_eq!(
|
||||||
|
set_loaded_mission_control_device_selector(&mut engine, stale, 0, 0x500, 0.25),
|
||||||
|
Err(ControlResourceAccessError::World(
|
||||||
|
fparkan_world::WorldError::StaleHandle
|
||||||
|
))
|
||||||
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
query_loaded_mission_behavior_resource(&engine, stale, 0x1005),
|
query_loaded_mission_behavior_resource(&engine, stale, 0x1005),
|
||||||
Err(ControlResourceAccessError::World(
|
Err(ControlResourceAccessError::World(
|
||||||
@@ -8253,6 +8623,7 @@ mod tests {
|
|||||||
(13, 5),
|
(13, 5),
|
||||||
(21, 5),
|
(21, 5),
|
||||||
(25, 5),
|
(25, 5),
|
||||||
|
(26, 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);
|
||||||
@@ -8290,12 +8661,15 @@ 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 == 26 {
|
||||||
|
assert_eq!(device.tag26_extra_rate_bits, Some(0));
|
||||||
|
}
|
||||||
if factory_tag == 9 {
|
if factory_tag == 9 {
|
||||||
assert_eq!(device.class9_values_bits, Some([0x3f80_0000; 6]));
|
assert_eq!(device.class9_values_bits, Some([0x3f80_0000; 6]));
|
||||||
assert_eq!(device.class9_deltas_bits, Some([0; 6]));
|
assert_eq!(device.class9_deltas_bits, Some([0; 6]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for factory_tag in [1, 17, 26, 30] {
|
for factory_tag in [1, 17, 30] {
|
||||||
assert!(!control_factory_override_is_supported(factory_tag));
|
assert!(!control_factory_override_is_supported(factory_tag));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9441,6 +9815,7 @@ mod tests {
|
|||||||
Some([0x3f80_0000; 6])
|
Some([0x3f80_0000; 6])
|
||||||
);
|
);
|
||||||
assert_eq!(mine_control_devices[2].class9_deltas_bits, Some([0; 6]));
|
assert_eq!(mine_control_devices[2].class9_deltas_bits, Some([0; 6]));
|
||||||
|
assert_eq!(mine_control_devices[8].tag26_extra_rate_bits, Some(0));
|
||||||
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 {
|
||||||
@@ -9448,14 +9823,38 @@ mod tests {
|
|||||||
field: "Control resource interval",
|
field: "Control resource interval",
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
assert_eq!(
|
|
||||||
advance_loaded_mission_control_budget(&mut mine_engine, mine_control_handle, 0.5),
|
|
||||||
Err(ControlResourceAccessError::UnsupportedFactoryTag { factory_tag: 26 })
|
|
||||||
);
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
loaded_mission_control_device_tables(&mine_engine),
|
loaded_mission_control_device_tables(&mine_engine),
|
||||||
Some(mine_control_before.as_slice())
|
Some(mine_control_before.as_slice())
|
||||||
);
|
);
|
||||||
|
advance_loaded_mission_control_budget(&mut mine_engine, mine_control_handle, 0.5)
|
||||||
|
.expect("whole licensed mine table passes supported Control budget callbacks");
|
||||||
|
let mine_control_after = loaded_mission_control_device_tables(&mine_engine)
|
||||||
|
.expect("licensed mine Control table after budget pass");
|
||||||
|
let mine_table_after = &mine_control_after[0];
|
||||||
|
let MissionControlDeviceTableStatus::Available(mine_devices_after) =
|
||||||
|
&mine_table_after.status
|
||||||
|
else {
|
||||||
|
panic!("licensed mine table became unavailable after budget pass");
|
||||||
|
};
|
||||||
|
let mine_multipliers = mine_devices_after
|
||||||
|
.iter()
|
||||||
|
.map(|device| device.multiplier_bits.map(|bits| format!("{bits:08x}")))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
println!(
|
||||||
|
"licensed mine full Control budget: devices={} outputs={:08x},{:08x} multipliers={mine_multipliers:?} tag26Extra={:?}",
|
||||||
|
mine_devices_after.len(),
|
||||||
|
mine_table_after.current_output_rate_5dc_bits,
|
||||||
|
mine_table_after.current_output_rate_5e0_bits,
|
||||||
|
mine_devices_after[8].tag26_extra_rate_bits,
|
||||||
|
);
|
||||||
|
assert_eq!(mine_devices_after.len(), 11);
|
||||||
|
assert_eq!(mine_devices_after[8].tag26_extra_rate_bits, Some(0));
|
||||||
|
assert_eq!(mine_table_after.current_output_rate_5dc_bits, 0x3c23_d70a);
|
||||||
|
assert_eq!(mine_table_after.current_output_rate_5e0_bits, 0x3c23_d70a);
|
||||||
|
assert_eq!(mine_devices_after[0].multiplier_bits, Some(0x3f7f_d70a));
|
||||||
|
assert_eq!(mine_devices_after[1].multiplier_bits, Some(0x3f7f_d70a));
|
||||||
|
assert_ne!(mine_control_after, mine_control_before.as_slice());
|
||||||
|
|
||||||
let draft = &loaded_mission_object_drafts(&engine).expect("object drafts")[0];
|
let draft = &loaded_mission_object_drafts(&engine).expect("object drafts")[0];
|
||||||
assert_eq!(draft.unit_components.len(), 6);
|
assert_eq!(draft.unit_components.len(), 6);
|
||||||
|
|||||||
@@ -53,6 +53,10 @@ internal static class BehaviorDistributorTickOracle {
|
|||||||
private const int ControlResourceCommonSetterRva = 0x21d50;
|
private const int ControlResourceCommonSetterRva = 0x21d50;
|
||||||
private const int ControlResourceTag9ConstructorRva = 0x25460;
|
private const int ControlResourceTag9ConstructorRva = 0x25460;
|
||||||
private const int ControlResourceTag15ConstructorRva = 0x22ae0;
|
private const int ControlResourceTag15ConstructorRva = 0x22ae0;
|
||||||
|
private const int ControlResourceTag26ConstructorRva = 0x2d4b0;
|
||||||
|
private const int ControlResourceTag26SetterRva = 0x2c2b0;
|
||||||
|
private const int ControlResourceTag26RateRva = 0x2e540;
|
||||||
|
private const int ControlResourceTag26UpdateRva = 0x21890;
|
||||||
private const int ControlResourceTag25ConstructorRva = 0x20800;
|
private const int ControlResourceTag25ConstructorRva = 0x20800;
|
||||||
private const int ControlResourceTag9RateRva = 0x25700;
|
private const int ControlResourceTag9RateRva = 0x25700;
|
||||||
private const int ControlResourceTag9UpdateRva = 0x257b0;
|
private const int ControlResourceTag9UpdateRva = 0x257b0;
|
||||||
@@ -68,6 +72,7 @@ internal static class BehaviorDistributorTickOracle {
|
|||||||
private const int ControlResourceTag15GateRva = 0x21820;
|
private const int ControlResourceTag15GateRva = 0x21820;
|
||||||
private const int ControlResourceTag9VtableRva = 0x3c938;
|
private const int ControlResourceTag9VtableRva = 0x3c938;
|
||||||
private const int ControlResourceTag15VtableRva = 0x3c648;
|
private const int ControlResourceTag15VtableRva = 0x3c648;
|
||||||
|
private const int ControlResourceTag26VtableRva = 0x3ce38;
|
||||||
private const int ControlResourceTag25VtableRva = 0x3c448;
|
private const int ControlResourceTag25VtableRva = 0x3c448;
|
||||||
private const int ControlResourceTag9StringPairRva = 0x25600;
|
private const int ControlResourceTag9StringPairRva = 0x25600;
|
||||||
private const int ControlResourceNoOpStringPairRva = 0x0bcd0;
|
private const int ControlResourceNoOpStringPairRva = 0x0bcd0;
|
||||||
@@ -125,6 +130,8 @@ internal static class BehaviorDistributorTickOracle {
|
|||||||
[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 void NativeControlResourceTag9Update(IntPtr self, float deltaSeconds, float allocation);
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.ThisCall)] private delegate void NativeControlResourceDeviceUpdate(IntPtr self, float deltaSeconds, float allocation);
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate byte NativeControlDeviceSelectorSetter(IntPtr ownerInterface, uint deviceIndex, uint selector, IntPtr value);
|
||||||
[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);
|
||||||
@@ -712,6 +719,7 @@ internal static class BehaviorDistributorTickOracle {
|
|||||||
RunNativeControlTimedUpdateBaselineCases(controlModule);
|
RunNativeControlTimedUpdateBaselineCases(controlModule);
|
||||||
RunNativeControlAggregateResourceCases(controlModule);
|
RunNativeControlAggregateResourceCases(controlModule);
|
||||||
RunNativeControlResourceBudgetTag15Case(controlModule);
|
RunNativeControlResourceBudgetTag15Case(controlModule);
|
||||||
|
RunNativeControlResourceBudgetTag26Case(controlModule);
|
||||||
RunNativeControlResourceBudgetClass9Case(controlModule);
|
RunNativeControlResourceBudgetClass9Case(controlModule);
|
||||||
RunNativeControlGenericDefaultBudgetCase(controlModule);
|
RunNativeControlGenericDefaultBudgetCase(controlModule);
|
||||||
RunNativeControlActivationCase(controlModule);
|
RunNativeControlActivationCase(controlModule);
|
||||||
@@ -2362,6 +2370,168 @@ internal static class BehaviorDistributorTickOracle {
|
|||||||
GC.KeepAlive(update);
|
GC.KeepAlive(update);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void RunNativeControlResourceBudgetTag26Case(IntPtr module) {
|
||||||
|
NativeControlOwnerConstructor constructOwner = (NativeControlOwnerConstructor)
|
||||||
|
Marshal.GetDelegateForFunctionPointer(IntPtr.Add(module, ControlOwnerConstructorRva), typeof(NativeControlOwnerConstructor));
|
||||||
|
NativeControlProviderRowConstructor constructProviderRow = (NativeControlProviderRowConstructor)
|
||||||
|
Marshal.GetDelegateForFunctionPointer(IntPtr.Add(module, ControlProviderRowConstructorRva), typeof(NativeControlProviderRowConstructor));
|
||||||
|
NativeControlDeviceFactory createDevice = (NativeControlDeviceFactory)
|
||||||
|
Marshal.GetDelegateForFunctionPointer(IntPtr.Add(module, ControlResourceTag26ConstructorRva), typeof(NativeControlDeviceFactory));
|
||||||
|
NativeControlResourceDeviceConstructor constructTag19 = (NativeControlResourceDeviceConstructor)
|
||||||
|
Marshal.GetDelegateForFunctionPointer(IntPtr.Add(module, ControlResourceDeviceConstructorRva), typeof(NativeControlResourceDeviceConstructor));
|
||||||
|
NativeControlResourceDeviceRate rate19 = (NativeControlResourceDeviceRate)
|
||||||
|
Marshal.GetDelegateForFunctionPointer(IntPtr.Add(module, 0x229a0), typeof(NativeControlResourceDeviceRate));
|
||||||
|
NativeControlResourceComponentOverride applyInto = (NativeControlResourceComponentOverride)
|
||||||
|
Marshal.GetDelegateForFunctionPointer(IntPtr.Add(module, ControlResourceIntoRva), typeof(NativeControlResourceComponentOverride));
|
||||||
|
NativeControlResourceDeviceRate rate = (NativeControlResourceDeviceRate)
|
||||||
|
Marshal.GetDelegateForFunctionPointer(IntPtr.Add(module, ControlResourceTag26RateRva), typeof(NativeControlResourceDeviceRate));
|
||||||
|
NativeControlResourceDeviceGate gate = (NativeControlResourceDeviceGate)
|
||||||
|
Marshal.GetDelegateForFunctionPointer(IntPtr.Add(module, ControlResourceTag15GateRva), typeof(NativeControlResourceDeviceGate));
|
||||||
|
NativeControlResourceDeviceUpdate update = (NativeControlResourceDeviceUpdate)
|
||||||
|
Marshal.GetDelegateForFunctionPointer(IntPtr.Add(module, ControlResourceTag26UpdateRva), typeof(NativeControlResourceDeviceUpdate));
|
||||||
|
NativeControlResourceBudgetUpdate budgetStep = (NativeControlResourceBudgetUpdate)
|
||||||
|
Marshal.GetDelegateForFunctionPointer(IntPtr.Add(module, ControlResourceBudgetUpdateRva), typeof(NativeControlResourceBudgetUpdate));
|
||||||
|
NativeControlDeviceSelectorSetter setSelector = (NativeControlDeviceSelectorSetter)
|
||||||
|
Marshal.GetDelegateForFunctionPointer(
|
||||||
|
WriteStdCallFourArgsToFastcallThunk(0x380, IntPtr.Add(module, ControlResourceTag26SetterRva)),
|
||||||
|
typeof(NativeControlDeviceSelectorSetter));
|
||||||
|
|
||||||
|
using (Fixture fixture = NewFixture()) {
|
||||||
|
IntPtr owner = fixture.Allocate(0x664);
|
||||||
|
Expect(constructOwner(owner, IntPtr.Zero, 4) == owner, "tag-26 budget owner constructor");
|
||||||
|
IntPtr providerRow = fixture.Allocate(0x2c);
|
||||||
|
IntPtr ndpDescriptor = fixture.Allocate(0x4c);
|
||||||
|
Marshal.WriteInt32(ndpDescriptor, 4, FloatBits(100.0f));
|
||||||
|
constructProviderRow(providerRow, ndpDescriptor);
|
||||||
|
Marshal.WriteInt32(providerRow, 4, -1);
|
||||||
|
Marshal.WriteInt32(providerRow, 0x24, -1);
|
||||||
|
Marshal.WriteInt32(providerRow, 8, FloatBits(40.0f));
|
||||||
|
Marshal.WriteInt32(providerRow, 0x0c, FloatBits(40.0f));
|
||||||
|
Marshal.WriteInt32(providerRow, 0x14, FloatBits(0.4f));
|
||||||
|
Marshal.WriteIntPtr(owner, 0x55c, providerRow);
|
||||||
|
Marshal.WriteInt32(owner, 0x560, 1);
|
||||||
|
Marshal.WriteInt32(owner, 0x65c, FloatBits(1.0f));
|
||||||
|
Marshal.WriteInt32(owner, 0x58c, FloatBits(100.0f));
|
||||||
|
Marshal.WriteInt32(owner, 0x590, FloatBits(40.0f));
|
||||||
|
IntPtr devices = fixture.Allocate(8);
|
||||||
|
Marshal.WriteIntPtr(owner, 0x5bc, devices);
|
||||||
|
Marshal.WriteInt32(owner, 0x5c4, 2); // Factory device vector capacity.
|
||||||
|
IntPtr record = fixture.Allocate(0xb4);
|
||||||
|
Marshal.WriteInt32(record, 0, 26);
|
||||||
|
Marshal.WriteInt32(record, 0x18, -1); // Keep factory enable word +0x50.
|
||||||
|
Marshal.WriteInt32(record, 0x20, FloatBits(0.2f));
|
||||||
|
Expect(createDevice(owner, 3, 0, record) == IntPtr.Add(record, 0xb4),
|
||||||
|
"tag-26 native factory consumes one descriptor");
|
||||||
|
IntPtr device = Marshal.ReadIntPtr(devices);
|
||||||
|
Marshal.WriteInt32(owner, 0x5c0, 1);
|
||||||
|
Expect(device != IntPtr.Zero, "tag-26 factory publishes its allocated device pointer");
|
||||||
|
IntPtr vtable = Marshal.ReadIntPtr(device);
|
||||||
|
Expect(vtable == IntPtr.Add(module, ControlResourceTag26VtableRva) &&
|
||||||
|
Marshal.ReadIntPtr(vtable, 0x14) == IntPtr.Add(module, ControlResourceTag26RateRva) &&
|
||||||
|
Marshal.ReadIntPtr(vtable, 0x18) == IntPtr.Add(module, ControlResourceTag26UpdateRva),
|
||||||
|
"tag-26 factory keeps its native rate and update methods");
|
||||||
|
Expect(Marshal.ReadIntPtr(vtable, 0x1c) == IntPtr.Add(module, ControlResourceCommonSetterRva),
|
||||||
|
"tag-26 vtable uses the original common reader 0x21d50");
|
||||||
|
Expect(Marshal.ReadInt32(device, 0xa4) == 0, "tag-26 constructor initializes +0xa4 to zero");
|
||||||
|
// The full owner build path is outside this bounded callback fixture.
|
||||||
|
Marshal.WriteIntPtr(device, 0x3c, owner);
|
||||||
|
Marshal.WriteIntPtr(device, 0x44, IntPtr.Add(owner, 4));
|
||||||
|
Expect(Marshal.ReadInt32(device, 0x50) == 5, "tag-26 common setter preserves enabled default");
|
||||||
|
Expect(gate(device) == 0, "tag-26 provider gate is active");
|
||||||
|
|
||||||
|
IntPtr extra = fixture.Allocate(4);
|
||||||
|
Marshal.WriteInt32(extra, 0, FloatBits(0.3f));
|
||||||
|
byte setterResult = setSelector(IntPtr.Add(owner, 0x0c), 0, 0x500, extra);
|
||||||
|
Expect(setterResult == 1 && Marshal.ReadInt32(device, 0xa4) == FloatBits(0.3f),
|
||||||
|
"native selector 0x500 stores tag-26 extra rate");
|
||||||
|
float firstRate = rate(device, 0.5f);
|
||||||
|
Expect(FloatBits(firstRate) == FloatBits(-0.25f), "tag-26 rate adds +0xa4 before multiplying dt");
|
||||||
|
float orderingRate = rate(device, 0.7f);
|
||||||
|
Expect(unchecked((uint)FloatBits(orderingRate)) == 0xbeb33333u,
|
||||||
|
"native tag-26 f32 order rounds the sum before multiplying dt");
|
||||||
|
update(device, 0.5f, 0.375f);
|
||||||
|
Expect(Marshal.ReadInt32(device, 0x4c) == FloatBits(0.375f) &&
|
||||||
|
Marshal.ReadInt32(device, 0xa4) == FloatBits(0.3f),
|
||||||
|
"tag-26 update writes allocation multiplier and preserves extra rate");
|
||||||
|
|
||||||
|
IntPtr intoRecord = fixture.Allocate(0xb4);
|
||||||
|
Marshal.WriteInt32(intoRecord, 0, 26);
|
||||||
|
Marshal.WriteInt32(intoRecord, 0x18, -1);
|
||||||
|
Marshal.WriteInt32(intoRecord, 0x20, FloatBits(0.1f));
|
||||||
|
Expect(applyInto(owner, 0, intoRecord) == IntPtr.Add(intoRecord, 0xb4), "tag-26 INTO descriptor update");
|
||||||
|
Expect(Marshal.ReadInt32(device, 0xa4) == FloatBits(0.3f) &&
|
||||||
|
FloatBits(rate(device, 0.5f)) == FloatBits(-0.2f),
|
||||||
|
"common INTO setter preserves tag-26 +0xa4 across descriptor replacement");
|
||||||
|
Console.WriteLine("native-control-tag26-callbacks: controlHash=" + ExpectedControlSha256 +
|
||||||
|
" ctorExtra=0 setter500=1 extra=" + Hex(unchecked((uint)Marshal.ReadInt32(device, 0xa4))) +
|
||||||
|
" rateDt0.5=" + Hex(unchecked((uint)FloatBits(firstRate))) +
|
||||||
|
" rateDt0.7=" + Hex(unchecked((uint)FloatBits(orderingRate))) +
|
||||||
|
" updateMultiplier=" + Hex(unchecked((uint)Marshal.ReadInt32(device, 0x4c))) +
|
||||||
|
" intoRate=" + Hex(unchecked((uint)FloatBits(rate(device, 0.5f)))) +
|
||||||
|
" intoExtra=" + Hex(unchecked((uint)Marshal.ReadInt32(device, 0xa4))));
|
||||||
|
|
||||||
|
IntPtr otherTagRecord = fixture.Allocate(0xb4);
|
||||||
|
Marshal.WriteInt32(otherTagRecord, 0, 19);
|
||||||
|
Marshal.WriteInt32(otherTagRecord, 0x18, -1);
|
||||||
|
Marshal.WriteInt32(otherTagRecord, 0x20, FloatBits(0.2f));
|
||||||
|
applyInto(owner, 0, otherTagRecord);
|
||||||
|
byte otherTagSetter = setSelector(IntPtr.Add(owner, 0x0c), 0, 0x500, extra);
|
||||||
|
Expect(otherTagSetter == 0 &&
|
||||||
|
Marshal.ReadInt32(device, 0xa4) == FloatBits(0.3f),
|
||||||
|
"selector 0x500 returns no-result for a different current descriptor tag");
|
||||||
|
Console.WriteLine("native-control-tag26-setter: selector=0x500 tag26=" + setterResult +
|
||||||
|
" otherCurrentTag=" + otherTagSetter + " preservedExtra=" +
|
||||||
|
Hex(unchecked((uint)Marshal.ReadInt32(device, 0xa4))));
|
||||||
|
|
||||||
|
IntPtr budgetRecord = fixture.Allocate(0xb4);
|
||||||
|
Marshal.WriteInt32(budgetRecord, 0, 26);
|
||||||
|
Marshal.WriteInt32(budgetRecord, 0x18, -1);
|
||||||
|
Marshal.WriteInt32(budgetRecord, 0x20, FloatBits(0.2f));
|
||||||
|
applyInto(owner, 0, budgetRecord);
|
||||||
|
|
||||||
|
// The original factory-19 callbacks supply +0.125 against the
|
||||||
|
// original factory-26 demand -0.25; native dispatch allocates one
|
||||||
|
// half to tag26 and updates the generator reserve to 0.6875.
|
||||||
|
IntPtr producer = fixture.Allocate(0x94);
|
||||||
|
Expect(constructTag19(producer) == producer, "native tag-19 supplier constructor");
|
||||||
|
IntPtr producerVtable = Marshal.ReadIntPtr(producer);
|
||||||
|
Expect(producerVtable == IntPtr.Add(module, ControlResourceTag19VtableRva) &&
|
||||||
|
Marshal.ReadIntPtr(producerVtable, 0x14) == IntPtr.Add(module, 0x229a0) &&
|
||||||
|
Marshal.ReadIntPtr(producerVtable, 0x18) == IntPtr.Add(module, 0x22a60),
|
||||||
|
"native tag-19 supplier keeps its original rate and update callbacks");
|
||||||
|
Marshal.WriteIntPtr(producer, 0x3c, owner);
|
||||||
|
Marshal.WriteIntPtr(producer, 0x44, IntPtr.Add(owner, 4));
|
||||||
|
IntPtr producerRecord = fixture.Allocate(0xb4);
|
||||||
|
Marshal.WriteInt32(producerRecord, 0, 19);
|
||||||
|
Marshal.WriteInt32(producerRecord, 0x18, -1);
|
||||||
|
Marshal.WriteInt32(producerRecord, 0x20, FloatBits(0.625f));
|
||||||
|
Marshal.WriteInt32(producerRecord, 0x2c, FloatBits(1.0f));
|
||||||
|
Marshal.WriteIntPtr(devices, 0, producer);
|
||||||
|
Marshal.WriteIntPtr(devices, 4, device);
|
||||||
|
Marshal.WriteInt32(owner, 0x5c0, 2);
|
||||||
|
Expect(applyInto(owner, 0, producerRecord) == IntPtr.Add(producerRecord, 0xb4),
|
||||||
|
"native tag-19 supplier descriptor install");
|
||||||
|
Expect(gate(producer) == 0 && FloatBits(rate19(producer, 0.5f)) == FloatBits(0.125f),
|
||||||
|
"original tag-19 provider-backed supply is 0.125");
|
||||||
|
|
||||||
|
budgetStep(owner, 0.5f);
|
||||||
|
uint outputA = unchecked((uint)Marshal.ReadInt32(owner, 0x5dc));
|
||||||
|
uint outputB = unchecked((uint)Marshal.ReadInt32(owner, 0x5e0));
|
||||||
|
uint consumerScale = unchecked((uint)Marshal.ReadInt32(device, 0x4c));
|
||||||
|
uint producerScale = unchecked((uint)Marshal.ReadInt32(producer, 0x4c));
|
||||||
|
Console.WriteLine("native-control-tag26-budget: controlHash=" + ExpectedControlSha256 +
|
||||||
|
" factory=0x2d4b0 case26Ctor=0x20800 setter=0x2c2b0 rate=0x2e540 update=0x21890 budget=0x2d340" +
|
||||||
|
" provider=0.4 descriptorRate=0.2 extra=0.3 dt=0.5 producerSupply=0.125" +
|
||||||
|
" consumerAllocation=" + Float(ReadFloat(device, 0x4c)) +
|
||||||
|
" producerMultiplier=" + Float(ReadFloat(producer, 0x4c)) +
|
||||||
|
" outputs=" + Hex(outputA) + "," + Hex(outputB));
|
||||||
|
Expect(consumerScale == FloatBits(0.5f) && producerScale == FloatBits(0.6875f),
|
||||||
|
"native budget dispatcher allocates tag-26 demand against a category-1 supplier");
|
||||||
|
Expect(outputA == 0x80000000u && outputB == FloatBits(0.25f),
|
||||||
|
"native tag-26 mixed budget outputs use the scaled factory-26 rate");
|
||||||
|
GC.KeepAlive(setSelector);
|
||||||
|
}
|
||||||
|
}
|
||||||
private static void RunNativeControlActivationCase(IntPtr module) {
|
private static void RunNativeControlActivationCase(IntPtr module) {
|
||||||
NativeControlOwnerConstructor construct = (NativeControlOwnerConstructor)Marshal.GetDelegateForFunctionPointer(
|
NativeControlOwnerConstructor construct = (NativeControlOwnerConstructor)Marshal.GetDelegateForFunctionPointer(
|
||||||
IntPtr.Add(module, 0x31490), typeof(NativeControlOwnerConstructor));
|
IntPtr.Add(module, 0x31490), typeof(NativeControlOwnerConstructor));
|
||||||
@@ -3692,6 +3862,17 @@ internal static class BehaviorDistributorTickOracle {
|
|||||||
Marshal.Copy(code, 0, thunk, code.Length);
|
Marshal.Copy(code, 0, thunk, code.Length);
|
||||||
return thunk;
|
return thunk;
|
||||||
}
|
}
|
||||||
|
private static IntPtr WriteStdCallFourArgsToFastcallThunk(int offset, IntPtr fastcallTarget) {
|
||||||
|
// Adapt stdcall(owner, index, selector, value) to ECX=owner, EDX=index,
|
||||||
|
// stack(selector, value) for the native fastcall property setter.
|
||||||
|
byte[] code = { 0xff,0x74,0x24,0x10, 0xff,0x74,0x24,0x10,
|
||||||
|
0x8b,0x54,0x24,0x10, 0x8b,0x4c,0x24,0x0c,
|
||||||
|
0xb8,0,0,0,0, 0xff,0xd0, 0xc2,0x10,0x00 };
|
||||||
|
WriteImm32(code, 17, fastcallTarget.ToInt32());
|
||||||
|
IntPtr thunk = IntPtr.Add(fastcallThunks, offset);
|
||||||
|
Marshal.Copy(code, 0, thunk, code.Length);
|
||||||
|
return thunk;
|
||||||
|
}
|
||||||
private static IntPtr WriteX87ControlWordReader(int offset) {
|
private static IntPtr WriteX87ControlWordReader(int offset) {
|
||||||
byte[] code = { 0xd9, 0x3d, 0, 0, 0, 0, 0xc3 }; // fnstcw [absolute address]; ret
|
byte[] code = { 0xd9, 0x3d, 0, 0, 0, 0, 0xc3 }; // fnstcw [absolute address]; ret
|
||||||
WriteImm32(code, 2, x87ControlWordCell.ToInt32());
|
WriteImm32(code, 2, x87ControlWordCell.ToInt32());
|
||||||
|
|||||||
@@ -361,7 +361,7 @@ 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` для фабричных
|
`advance_loaded_mission_control_budget` переносит `0x2d340` для фабричных
|
||||||
callbacks `9`, `15`, `19` и общей default-пары; category lookup использует
|
callbacks `9`, `15`, `19`, `26` и общей default-пары; category lookup использует
|
||||||
текущий descriptor tag из подтверждённого префикса `0..=34`. Ставки сохраняются
|
текущий descriptor tag из подтверждённого префикса `0..=34`. Ставки сохраняются
|
||||||
в `+0x5dc/+0x5e0`. Шаг выполняется транзакционно, а неподдержанный
|
в `+0x5dc/+0x5e0`. Шаг выполняется транзакционно, а неподдержанный
|
||||||
специализированный callback, provider или диапазон числа возвращает ошибку без частичного
|
специализированный callback, provider или диапазон числа возвращает ошибку без частичного
|
||||||
@@ -417,20 +417,30 @@ visibility transition `0x11920` с исходными AniMesh callbacks. Отд
|
|||||||
в этом векторе подставлен mock-ом. Отдельный вектор с исходным генератором
|
в этом векторе подставлен mock-ом. Отдельный вектор с исходным генератором
|
||||||
tag `19` (`0x22980`, `0x229a0`, `0x22a60`) проверяет полную и ограниченную
|
tag `19` (`0x22980`, `0x229a0`, `0x22a60`) проверяет полную и ограниченную
|
||||||
rate, изменение provider и собственного множителя генератора; отрицательные
|
rate, изменение provider и собственного множителя генератора; отрицательные
|
||||||
параметры и нулевой знаменатель не проверены. Native-векторы бюджетного шага
|
параметры и нулевой знаменатель не проверены. Новый оригинальный вектор
|
||||||
|
`0x2d340` смешивает tag `19` и tag `26`: supply `0.125` покрывает половину
|
||||||
|
demand `0.25`, итоговые множители равны `0.6875` и `0.5`, а выходы —
|
||||||
|
`0x80000000` и `0x3e800000`. Тот же oracle подтверждает tag-26 selector `0x500`,
|
||||||
|
общий reader `0x21d50` и сохранение поля `+0xa4` при `INTO`. Rust-тест
|
||||||
|
повторяет бюджетную пару и разрешает tag `26` в projection INTO guard, оставляя
|
||||||
|
теги `1`, `17` и `30` явными границами. Native-векторы бюджетного шага
|
||||||
подтверждают текущие ставки `+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-тест повторяет поля этой пары
|
||||||
на синтетической таблице. Rust переносит class-9 rate/update и шестизначную
|
на синтетической таблице. Rust переносит class-9 rate/update и шестизначную
|
||||||
adjustment `0x25a90`, общую default callback-пару `0x21860/0x21890`, а также
|
adjustment `0x25a90`, tag-26 rate/update и setter selector `0x500`, общую
|
||||||
category map только для current descriptor tags `0..=34`. Специализированные
|
default callback-пару `0x21860/0x21890`, а также category map только для current
|
||||||
factory callbacks вне этого набора остаются явными ошибками.
|
descriptor tags `0..=34`. Остальные ещё не описанные специализированные
|
||||||
|
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 оставляет всю
|
||||||
таблицу без изменений при не конечном интервале. Для конечного интервала
|
таблицу без изменений при не конечном интервале. Для конечного интервала
|
||||||
предварительная проверка распознаёт категории, но находит неподдержанный
|
ignored Rust-тест проходит budget по всем 11 строкам без фильтра. При `dt = 0.5`
|
||||||
factory tag `26` и отклоняет вызов до вычисления ставок и обновлений; полный
|
обе output rates равны `0x3c23d70a`, первые два множителя tag `19` равны
|
||||||
бюджетный проход для этой реальной таблицы поэтому не проверен. Успешные бюджетные native-векторы используют синтетические
|
`0x3f7fd70a`, остальные множители остаются `1.0`, а добавка tag `26` остаётся
|
||||||
|
нулевой. Эта проверка подтверждает только CPU-проекцию реальной таблицы; она
|
||||||
|
не запускает native полный mission tick и не подключает Control clock.
|
||||||
|
Успешные бюджетные native-векторы используют синтетические
|
||||||
NDP/Node38-строки и подтверждают локальные переходы и порядок вызовов, а не
|
NDP/Node38-строки и подтверждают локальные переходы и порядок вызовов, а не
|
||||||
полный Control tick на миссии. Прямые oracle-вызовы `0x11220` для одиночной
|
полный Control tick на миссии. Прямые oracle-вызовы `0x11220` для одиночной
|
||||||
строки используют `updateEffects = false`, что исключает ветвь Control FX и
|
строки используют `updateEffects = false`, что исключает ветвь Control FX и
|
||||||
|
|||||||
@@ -792,23 +792,19 @@ 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 `9`, `15`, `19` и общий default callback, а
|
исходные callbacks factory tags `9`, `15`, `19`, `26` и общий default callback,
|
||||||
категории выбирает по текущему descriptor tag. Native map подтверждён только
|
а категории выбирает по текущему descriptor tag. Native map подтверждён только
|
||||||
для префикса тегов `0..=34`; неизвестная категория, специализированный
|
для префикса тегов `0..=34`; неизвестная категория, неподдержанный
|
||||||
callback, provider или численный диапазон возвращает ошибку без публикации
|
специализированный callback, provider или численный диапазон возвращает ошибку
|
||||||
частичных изменений. Ставки `+0x5dc/+0x5e0` сохраняются отдельно, а сам вызов
|
без публикации частичных изменений. Ставки `+0x5dc/+0x5e0` сохраняются отдельно,
|
||||||
ещё не включён в Control clock.
|
а сам вызов ещё не включён в Control clock.
|
||||||
|
|
||||||
Нативный oracle проверяет пустую таблицу с отрицательным нулём в обеих выходных
|
Нативный oracle проверяет пустую таблицу с отрицательным нулём в обеих выходных
|
||||||
ставках, невырожденную пару исходных устройств tag 19/tag 15 при `dt = 0.375`
|
ставках, невырожденную пару исходных устройств tag 19/tag 15 при `dt = 0.375`
|
||||||
и tag 9 с шестью значениями и шестью накопителями изменений. Rust-тесты
|
и tag 9 с шестью значениями и шестью накопителями изменений. Rust-тесты
|
||||||
повторяют их по битам, включая положительное и отрицательное обновления tag 9.
|
повторяют их по битам, включая положительное и отрицательное обновления tag 9.
|
||||||
В лицензированном `min_l_n1.dat` предварительная проверка распознаёт категории,
|
Rust также отклоняет нулевой знаменатель обновления tag 19; тест вызывает эту
|
||||||
но находит неподдержанный factory tag `26` и отклоняет бюджет до расчёта ставок
|
ошибку после изменения provider потребителем и подтверждает откат всей таблицы.
|
||||||
или вызова устройств; транзакционный тест подтверждает, что исходная таблица
|
|
||||||
не меняется. Полный бюджетный проход на реальной миссии пока не выполнен. Rust также отклоняет нулевой знаменатель
|
|
||||||
обновления tag 19; тест вызывает эту ошибку после изменения provider
|
|
||||||
потребителем и подтверждает откат всей таблицы.
|
|
||||||
|
|
||||||
#### Генератор tag 19 в том же бюджетном проходе
|
#### Генератор tag 19 в том же бюджетном проходе
|
||||||
|
|
||||||
@@ -911,10 +907,51 @@ Descriptor category lookup `0x2c3a0` возвращает следующие з
|
|||||||
|
|
||||||
Именно текущий descriptor выбирает категорию; callback остаётся у исходного
|
Именно текущий descriptor выбирает категорию; callback остаётся у исходного
|
||||||
factory-класса. В Rust неизвестный текущий тег за концом префикса и
|
factory-класса. В Rust неизвестный текущий тег за концом префикса и
|
||||||
специализированный factory callback не подменяются общей формулой. На
|
неподдержанный специализированный factory callback не подменяются общей
|
||||||
лицензированном Unit DAT полный проход доходит до factory `26` и возвращает
|
формулой. Tag `26` поддержан отдельной парой callbacks, описанной ниже. Ни этот
|
||||||
ошибку без частичного изменения таблицы. Ни этот CPU-вызов, ни локальные
|
CPU-вызов, ни локальные векторы не означают включения бюджетного шага в Control
|
||||||
векторы не означают включения бюджетного шага в Control clock.
|
clock.
|
||||||
|
|
||||||
|
#### Фабричный tag 26: изменяемая добавка
|
||||||
|
|
||||||
|
В этой сборке `Control.dll` SHA-256
|
||||||
|
`bcb86318089028548a178b03a21accdce15df0cc85ddd91674cf3c2f55012c6e` фабричный
|
||||||
|
tag `26` обрабатывается в функции-фабрике RVA `0x2d4b0`: её ветка вызывает общий
|
||||||
|
конструктор `0x20800`, обнуляет поле устройства `+0xa4` и устанавливает vtable
|
||||||
|
`0x3ce38`. Rate
|
||||||
|
callback RVA `0x2e540` сначала проходит проверку доступности provider, затем
|
||||||
|
считает `-((descriptor[+0x20] + device[+0xa4]) * dt)`; прибавление до умножения
|
||||||
|
важно для битового результата. Update slot `+0x18` ведёт на RVA `0x21890` и
|
||||||
|
сохраняет выделенную долю как обычный multiplier `+0x4c`, не трогая `+0xa4`.
|
||||||
|
|
||||||
|
Vtable slot `+0x1c` ведёт на общий reader `0x21d50`. Native override RVA
|
||||||
|
`0x2d890` через этот reader заменяет descriptor и коэффициенты, сохраняя
|
||||||
|
изменяемую добавку. Oracle проверяет сам slot и повторный rate после `INTO`.
|
||||||
|
Rust теперь включает factory tag `26` в projection guard для `INTO` по этой
|
||||||
|
проверке; границы для фабричных тегов `1`, `17` и `30` остаются закрытыми.
|
||||||
|
|
||||||
|
Отдельный Control setter RVA `0x2c2b0` поддерживает selector `0x500`: только
|
||||||
|
при текущем descriptor tag `26` он записывает переданные биты в `+0xa4` и
|
||||||
|
возвращает успех. Behavior.dll с SHA-256
|
||||||
|
`ddfbdbde07832358fe6e35e01c8dba9b58e7313789498b6049ed51d4ed9baf99` вызывает
|
||||||
|
его через RVA `0x19880`. Callers `0x29910` и `0x2f400` передают ноль, а
|
||||||
|
`0x29ba0` передаёт поле `+0x64` рядом с кодом `NewfOreCost`; смысл последнего
|
||||||
|
значения не установлен, поэтому Rust пока не запускает этот Behavior путь.
|
||||||
|
|
||||||
|
При `B = 0.2`, добавке `0.3` и `dt = 0.7` исходная rate имеет биты
|
||||||
|
`0xbeb33333`. Смешанная проверка использует оригинальный tag-19 генератор
|
||||||
|
(`B = 0.625`, `C0 = 1`, provider `0.4`) и tag-26 потребитель (`B = 0.2`,
|
||||||
|
добавка `0.3`) при `dt = 0.5`: первый даёт `0.125`, второй запрашивает `0.25`.
|
||||||
|
Native dispatcher выделяет tag `26` половину, сохраняет множители
|
||||||
|
`0x3f300000`/`0x3f000000`, а ставки `+0x5dc/+0x5e0` получают
|
||||||
|
`0x80000000`/`0x3e800000`. Постоянный x86 oracle вызывает исходные callbacks и
|
||||||
|
Rust-тест повторяет этот вектор.
|
||||||
|
|
||||||
|
Ignored Rust-тест также проходит полный неотфильтрованный Control budget по
|
||||||
|
одиннадцати исходным устройствам `min_l_n1.dat` при `dt = 0.5`; выходы равны
|
||||||
|
`0x3c23d70a`/`0x3c23d70a`, первые два множителя tag `19` — `0x3f7fd70a`.
|
||||||
|
Это проверка CPU-проекции реальной таблицы, а не сравнение целой миссии с
|
||||||
|
оригиналом.
|
||||||
|
|
||||||
#### Активация и начальные часы
|
#### Активация и начальные часы
|
||||||
|
|
||||||
|
|||||||
@@ -1082,12 +1082,31 @@ Rust переносит этот порядок, сохраняет native на
|
|||||||
классы, которые исходный диспетчер направляет в фабрику по умолчанию. Текущая
|
классы, которые исходный диспетчер направляет в фабрику по умолчанию. Текущая
|
||||||
категория читается из descriptor: её native map подтверждён только для тегов
|
категория читается из descriptor: её native map подтверждён только для тегов
|
||||||
`0..=34`. Неподдержанный специализированный callback, недоступный provider или
|
`0..=34`. Неподдержанный специализированный callback, недоступный provider или
|
||||||
неподдержанное число останавливают шаг без частичных изменений. В
|
неподдержанное число останавливают шаг без частичных изменений.
|
||||||
лицензированном `min_l_n1.dat` предварительная проверка распознаёт текущие
|
|
||||||
категории и выявляет неподдержанный factory tag `26`; она отклоняет бюджетный
|
У фабричного tag `26` ставка складывается из базового значения устройства и
|
||||||
вызов до расчёта ставок и обновлений, сохраняя таблицу целиком. Полный
|
изменяемой добавки. Затем сумма умножается на прошедшее время:
|
||||||
бюджетный проход на реальной миссии и подключение к игровым часам пока не
|
|
||||||
подтверждены.
|
```text
|
||||||
|
rate = -((B + extra) * dt)
|
||||||
|
```
|
||||||
|
|
||||||
|
Например, при `B = 0.2`, `extra = 0.3` и `dt = 0.5` устройство запрашивает
|
||||||
|
`0.25`. Если доступный бюджет равен `0.125`, оно получает половину запроса;
|
||||||
|
устройство сохраняет долю распределения, а сама добавка переживает замену
|
||||||
|
descriptor через `INTO`. Так разные части Control могут менять базовую запись и
|
||||||
|
текущее состояние независимо.
|
||||||
|
|
||||||
|
Эту добавку записывает исходный selector `0x500`, когда текущий descriptor
|
||||||
|
тоже относится к tag `26`. Мы восстановили его точку вызова в Behavior, но
|
||||||
|
смысл передаваемого там значения пока не установлен, поэтому этот Behavior путь
|
||||||
|
ещё не исполняется. Подтверждённые callbacks tag `26`, их native адреса и
|
||||||
|
точные проверочные векторы приведены в [техническом приложении](../appendices/script-vm.md#фабричный-tag-26-изменяемая-добавка).
|
||||||
|
|
||||||
|
Полный явный CPU-проход по исходной таблице из одиннадцати устройств
|
||||||
|
`min_l_n1.dat` тоже завершился. Этот результат проверяет вычисление для одной
|
||||||
|
загруженной таблицы; обновление пока не подключено к игровым часам и не
|
||||||
|
заменяет полный Control tick.
|
||||||
|
|
||||||
Отдельный путь объекта класса `4` умножает интервал на контактный коэффициент и
|
Отдельный путь объекта класса `4` умножает интервал на контактный коэффициент и
|
||||||
передаёт изменение строке. В проверенном примере коэффициент `1`, интервал `2`,
|
передаёт изменение строке. В проверенном примере коэффициент `1`, интервал `2`,
|
||||||
|
|||||||
Reference in new issue
Block a user