feat(runtime): publish provider values into model node state
Docs Deploy / Build and Deploy MkDocs (push) Successful in 36s
Docs Deploy / Build and Deploy MkDocs (push) Successful in 36s
This commit is contained in:
6 files changed
+156
-22
No files matched your search
@@ -222,6 +222,15 @@ pub struct MissionControlNodeInitialMeshState {
|
||||
pub stage_count: u32,
|
||||
}
|
||||
|
||||
/// Mutable projection of the provider row's matching AniMesh node fields.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub struct MissionControlNodeMeshState {
|
||||
/// Node value at AniMesh `+0x124`, written by selector `0x200`.
|
||||
pub type1_value_bits: u32,
|
||||
/// Visible MSH stage at AniMesh `+0x1c`; distinct from Control's exhaustion sentinel.
|
||||
pub visible_stage: u32,
|
||||
}
|
||||
|
||||
/// Source and current state of one loaded global Control node-data provider row.
|
||||
///
|
||||
/// The native table is indexed independently of MSH model nodes. Its rows are
|
||||
@@ -256,12 +265,13 @@ pub struct MissionControlNodeProviderRow {
|
||||
pub current_flags: Option<u32>,
|
||||
/// MSH state exposed at the same global ordinal, if the mesh projection exists.
|
||||
pub initial_mesh_state: Option<MissionControlNodeInitialMeshState>,
|
||||
/// Current model-node fields, when its global ordinal was resolved.
|
||||
pub mesh_state: Option<MissionControlNodeMeshState>,
|
||||
}
|
||||
|
||||
/// Node-property publication required after a provider amount changes.
|
||||
/// Node-property publication applied to CPU model state after an amount change.
|
||||
///
|
||||
/// The model-node callback is native selector `0x200`; the caller must publish
|
||||
/// this value separately from the later stage and visibility update.
|
||||
/// Records native selector `0x200`; stage and visibility updates are separate.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
#[must_use]
|
||||
pub struct ControlNodeProviderPublication {
|
||||
@@ -2157,15 +2167,16 @@ pub fn set_loaded_mission_control_multiplier(
|
||||
|
||||
/// Applies the confirmed non-animation-delta path of native Control `0x10f30`.
|
||||
///
|
||||
/// Updates amount, ratio, aggregate, exhaustion flag, and dirty state, returning
|
||||
/// the required selector-`0x200` node publication. It does not run stage propagation,
|
||||
/// Updates amount, ratio, aggregate, exhaustion flag, dirty state, and the CPU
|
||||
/// model node's selector-`0x200` value, returning the applied publication.
|
||||
/// It does not run stage propagation,
|
||||
/// visibility, or the separate Control clock; the processed amount stays unchanged.
|
||||
/// The loaded Unit DAT path has unity maximum multipliers.
|
||||
///
|
||||
/// # Errors
|
||||
/// Rejects invalid handles, unavailable state, unsupported numeric ranges, or
|
||||
/// depletion requiring the object's unimplemented lifecycle. No state is changed
|
||||
/// on error. The returned node publication must be applied by the caller.
|
||||
/// on error. This updates CPU model state, not GPU uploads or window rendering.
|
||||
pub fn apply_loaded_mission_control_node_delta(
|
||||
engine: &mut Engine,
|
||||
handle: ObjectHandle,
|
||||
@@ -2262,8 +2273,16 @@ fn apply_control_node_delta(
|
||||
let change = checked_control_sum(0, "provider amount change", amount, -previous)?;
|
||||
let total = checked_control_sum(0, "provider aggregate", total, change)?.max(0.0);
|
||||
let ratio = checked_control_ratio("provider ratio", amount, maximum)?;
|
||||
let mesh = row
|
||||
.mesh_state
|
||||
.as_mut()
|
||||
.ok_or(ControlResourceAccessError::UnsupportedField {
|
||||
factory_tag: 0,
|
||||
field: "model node state",
|
||||
})?;
|
||||
row.current_amount_bits = amount.to_bits();
|
||||
row.current_type1_value_bits = ratio.to_bits();
|
||||
mesh.type1_value_bits = ratio.to_bits();
|
||||
row.current_flags = Some(if amount == 0.0 {
|
||||
flags | 0x10
|
||||
} else {
|
||||
@@ -3755,6 +3774,7 @@ fn append_control_node_provider_rows(
|
||||
current_type1_value_bits: 0x3f80_0000,
|
||||
current_flags: None,
|
||||
initial_mesh_state: None,
|
||||
mesh_state: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -3820,6 +3840,10 @@ fn attach_initial_mesh_states_by_global_ordinal(
|
||||
for (ordinal, row) in provider_rows.iter_mut().enumerate() {
|
||||
row.initial_mesh_state = global_mesh_states.get(ordinal).copied().flatten();
|
||||
row.current_flags = row.initial_mesh_state.map(|state| state.flags);
|
||||
row.mesh_state = row.initial_mesh_state.map(|_| MissionControlNodeMeshState {
|
||||
type1_value_bits: row.initial_type1_value_bits,
|
||||
visible_stage: 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4321,6 +4345,7 @@ mod tests {
|
||||
current_type1_value_bits: initial_type1_value_bits,
|
||||
current_flags: None,
|
||||
initial_mesh_state: None,
|
||||
mesh_state: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5157,6 +5182,10 @@ mod tests {
|
||||
row.current_amount_bits = 40.0_f32.to_bits();
|
||||
row.processed_amount_bits = 40.0_f32.to_bits();
|
||||
row.current_flags = Some(0);
|
||||
row.mesh_state = Some(MissionControlNodeMeshState {
|
||||
type1_value_bits: 1.0_f32.to_bits(),
|
||||
visible_stage: 77,
|
||||
});
|
||||
let mut device = synthetic_effective_control_device(
|
||||
26,
|
||||
26,
|
||||
@@ -5198,6 +5227,13 @@ mod tests {
|
||||
};
|
||||
assert_eq!(rows[1].current_amount_bits, amount.to_bits());
|
||||
assert_eq!(rows[1].processed_amount_bits, 40.0_f32.to_bits());
|
||||
assert_eq!(
|
||||
rows[1].mesh_state,
|
||||
Some(MissionControlNodeMeshState {
|
||||
type1_value_bits: ratio.to_bits(),
|
||||
visible_stage: 77
|
||||
})
|
||||
);
|
||||
assert_eq!(rows[1].initial_amount_bits, 100.0_f32.to_bits());
|
||||
assert_eq!(rows[1].current_flags.unwrap() & 0x10 != 0, exhausted);
|
||||
let resource = (ratio * 2.0) * 3.0;
|
||||
@@ -5260,6 +5296,27 @@ mod tests {
|
||||
));
|
||||
assert_eq!(table, before);
|
||||
table.node_provider_amount_bits = total;
|
||||
let MissionControlNodeProviderTableStatus::Available(rows) =
|
||||
&mut table.node_provider_status
|
||||
else {
|
||||
panic!()
|
||||
};
|
||||
let mesh = rows[1].mesh_state.take();
|
||||
let before = table.clone();
|
||||
assert!(matches!(
|
||||
apply_control_node_delta(&mut table, 1, 1.0),
|
||||
Err(ControlResourceAccessError::UnsupportedField {
|
||||
field: "model node state",
|
||||
..
|
||||
})
|
||||
));
|
||||
assert_eq!(table, before);
|
||||
let MissionControlNodeProviderTableStatus::Available(rows) =
|
||||
&mut table.node_provider_status
|
||||
else {
|
||||
panic!()
|
||||
};
|
||||
rows[1].mesh_state = mesh;
|
||||
let before = table.clone();
|
||||
assert_eq!(
|
||||
apply_control_node_delta(&mut table, 1, -100.0),
|
||||
@@ -6794,6 +6851,13 @@ mod tests {
|
||||
};
|
||||
assert_eq!(mine_rows[key as usize].initial_amount_bits, initial);
|
||||
assert_eq!(mine_rows[key as usize].processed_amount_bits, initial);
|
||||
assert_eq!(
|
||||
mine_rows[key as usize].mesh_state,
|
||||
Some(MissionControlNodeMeshState {
|
||||
type1_value_bits: 0.5_f32.to_bits(),
|
||||
visible_stage: 0,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -125,6 +125,7 @@ internal static class BehaviorDistributorTickOracle {
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)] private delegate IntPtr NativeControlResourceComponentOverride(IntPtr self, int deviceIndex, IntPtr typedRecord);
|
||||
[UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate int NativeControlAggregateQuery(IntPtr self, uint selector, IntPtr result);
|
||||
[UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate int NativeControlAggregateSet(IntPtr self, uint selector, IntPtr value);
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)] private delegate void NativeAniMeshNodeConstructor(IntPtr self, IntPtr model, IntPtr source, uint component, IntPtr meshData);
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)] private delegate void NativeControlProviderAmountUpdate(IntPtr self, int nodeIndex, float delta, uint animationFlag);
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)] private delegate void NativeControlProviderAmountDistribute(IntPtr self, float delta, uint mode, uint flags);
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)] private delegate void NativeControlContactRateUpdate(IntPtr self, float deltaSeconds);
|
||||
@@ -696,7 +697,7 @@ internal static class BehaviorDistributorTickOracle {
|
||||
RunNativeControlProviderInitialStateCase(controlModule, aniMeshModule);
|
||||
RunNativeControlIntoOverrideCase(controlModule);
|
||||
RunNativeControlResourceDeviceCase(controlModule);
|
||||
RunNativeControlProviderLiveStateCase(controlModule);
|
||||
RunNativeControlProviderLiveStateCase(controlModule, aniMeshModule);
|
||||
RunNativeControlProviderStageArithmeticCase(controlModule, aniMeshModule);
|
||||
RunNativeControlProviderHierarchyPropagationCase(controlModule, aniMeshModule);
|
||||
RunNativeControlProviderVisibilityCase(controlModule, aniMeshModule);
|
||||
@@ -861,7 +862,7 @@ internal static class BehaviorDistributorTickOracle {
|
||||
}
|
||||
}
|
||||
|
||||
private static void RunNativeControlProviderLiveStateCase(IntPtr module) {
|
||||
private static void RunNativeControlProviderLiveStateCase(IntPtr module, IntPtr aniMeshModule) {
|
||||
NativeControlProviderRowConstructor constructProviderRow = (NativeControlProviderRowConstructor)
|
||||
Marshal.GetDelegateForFunctionPointer(IntPtr.Add(module, ControlProviderRowConstructorRva),
|
||||
typeof(NativeControlProviderRowConstructor));
|
||||
@@ -890,7 +891,29 @@ internal static class BehaviorDistributorTickOracle {
|
||||
|
||||
IntPtr providerInterface = IntPtr.Add(owner, 4);
|
||||
IntPtr providerVtable = IntPtr.Add(module, ControlProviderInterfaceVtableRva);
|
||||
IntPtr callbackObject = fixture.Allocate(IntPtr.Size);
|
||||
IntPtr callbackObject = fixture.Allocate(0x1b0);
|
||||
IntPtr meshRows = fixture.Allocate(2 * 0x130);
|
||||
NativeAniMeshNodeConstructor constructNode = (NativeAniMeshNodeConstructor)Marshal.GetDelegateForFunctionPointer(
|
||||
IntPtr.Add(aniMeshModule, 0x123e0), typeof(NativeAniMeshNodeConstructor));
|
||||
for (int node = 0; node < 2; node++) {
|
||||
constructNode(IntPtr.Add(meshRows, node * 0x130), IntPtr.Zero, IntPtr.Zero, 0, IntPtr.Zero);
|
||||
Expect(Marshal.ReadInt32(meshRows, node * 0x130 + 0x124) == FloatBits(1.0f) &&
|
||||
Marshal.ReadInt32(meshRows, node * 0x130 + 0x1c) == 0,
|
||||
"original AniMesh node constructor initializes provider value and visible stage");
|
||||
}
|
||||
Marshal.WriteIntPtr(callbackObject, 0x1a8, meshRows);
|
||||
Marshal.WriteInt32(callbackObject, 0x1ac, 2);
|
||||
Marshal.WriteInt32(meshRows, 0x124, FloatBits(-7.0f));
|
||||
Marshal.WriteInt32(meshRows, 0x130 + 0x1c, 77);
|
||||
NativeControlNodeUpdateCallback setNodeValue = (NativeControlNodeUpdateCallback)Marshal.GetDelegateForFunctionPointer(
|
||||
IntPtr.Add(aniMeshModule, 0x5750), typeof(NativeControlNodeUpdateCallback));
|
||||
NativeControlNodeUpdateCallback forwardNodeValue = delegate(IntPtr self, uint node, uint selector, uint bits) {
|
||||
OnControlNodeUpdate(self, node, selector, bits);
|
||||
setNodeValue(self, node, selector, bits);
|
||||
};
|
||||
setNodeValue(callbackObject, 1, 0x201, unchecked((uint)FloatBits(99.0f)));
|
||||
Expect(ReadFloat(meshRows, 0x130 + 0x124) == 1.0f,
|
||||
"AniMesh node-value setter ignores selectors other than 0x200");
|
||||
IntPtr callbackVtable = fixture.Allocate(0x38);
|
||||
IntPtr device = fixture.Allocate(0x94);
|
||||
IntPtr row = IntPtr.Add(rows, 0x2c); // Keep the root row untouched.
|
||||
@@ -909,7 +932,7 @@ internal static class BehaviorDistributorTickOracle {
|
||||
Marshal.WriteInt32(row, 0x14, FloatBits(0.4f));
|
||||
|
||||
Marshal.WriteIntPtr(callbackObject, 0, callbackVtable);
|
||||
Marshal.WriteIntPtr(callbackVtable, 0x34, Marshal.GetFunctionPointerForDelegate(ControlNodeUpdateCallback));
|
||||
Marshal.WriteIntPtr(callbackVtable, 0x34, Marshal.GetFunctionPointerForDelegate(forwardNodeValue));
|
||||
Marshal.WriteIntPtr(owner, 0x20, callbackObject);
|
||||
|
||||
Marshal.WriteInt32(device, 0x04, 1);
|
||||
@@ -922,6 +945,9 @@ internal static class BehaviorDistributorTickOracle {
|
||||
Expect(ReadFloat(row, 0x08) == 60.0f && ReadFloat(row, 0x14) == 0.6f &&
|
||||
ReadFloat(owner, 0x590) == 60.0f,
|
||||
"Control native amount update clamps the node amount, updates the normalized provider value, and keeps the aggregate amount in sync");
|
||||
Expect(ReadFloat(meshRows, 0x130 + 0x124) == 0.6f && ReadFloat(meshRows, 0x124) == -7.0f &&
|
||||
Marshal.ReadInt32(meshRows, 0x130 + 0x1c) == 77,
|
||||
"Control amount update reaches original AniMesh setter and changes only its target node value");
|
||||
Expect(controlNodeUpdateCount == 1 && controlNodeUpdateIndex == 1 &&
|
||||
controlNodeUpdateSelector == 0x200 && controlNodeUpdateValueBits == unchecked((uint)FloatBits(0.6f)),
|
||||
"Control native amount update publishes the normalized provider state through the existing node callback");
|
||||
@@ -960,11 +986,17 @@ internal static class BehaviorDistributorTickOracle {
|
||||
update(owner, 1, -1.0f, 0);
|
||||
Expect(Marshal.ReadInt32(row, 8) == 0 && ReadFloat(row, 0x14) == 0.25f &&
|
||||
ReadFloat(row, 0x0c) == 40.0f && ReadFloat(owner, 0x590) == 20.0f &&
|
||||
controlNodeUpdateCount == 5 && (Marshal.ReadInt32(row, 0x28) & 0x10) == 0,
|
||||
controlNodeUpdateCount == 5 && (Marshal.ReadInt32(row, 0x28) & 0x10) == 0 &&
|
||||
ReadFloat(meshRows, 0x130 + 0x124) == 0.1f,
|
||||
"clamping signed zero writes amount bits but leaves ratio, aggregate, flags and publication unchanged");
|
||||
|
||||
Console.WriteLine("native-control-live-ndp-provider: controlHash=" + ExpectedControlSha256 +
|
||||
" rowCtorRva=0xb240 initialType1=1 rowStride=0x2c node=1 max=100 amount=0..100 tag26Selector300=live");
|
||||
Console.WriteLine("native-control-to-animesh-node-value: AniMeshCtor=0x123e0 setter=0x5750 rowStride=0x130 value=+0x124" +
|
||||
" selector=0x200 other-node/stage=unchanged signed-zero=no-publication");
|
||||
GC.KeepAlive(constructNode);
|
||||
GC.KeepAlive(setNodeValue);
|
||||
GC.KeepAlive(forwardNodeValue);
|
||||
GC.KeepAlive(constructProviderRow);
|
||||
GC.KeepAlive(update);
|
||||
GC.KeepAlive(getter);
|
||||
@@ -1046,6 +1078,8 @@ internal static class BehaviorDistributorTickOracle {
|
||||
unchecked((uint)Marshal.ReadInt32(rows, 0x2c + 0x14)) == 0x3e800000 &&
|
||||
unchecked((uint)Marshal.ReadInt32(rows, 3 * 0x2c + 0x14)) == 0x3e800000,
|
||||
"Control flag 4 copies the parent type-1 ratio before stage calculation");
|
||||
Expect(ReadFloat(meshRows, 0x130 + 0x124) == 0.0f && ReadFloat(rows, 0x2c + 0x14) == 0.25f,
|
||||
"inheriting a parent provider ratio does not rewrite the separate AniMesh node value");
|
||||
Expect(String.Join(",", aniMeshStageTransitions.ToArray()) == "0:3,1:3,2:1,3:3,4:2,5:1,6:1,7:0,8:2" &&
|
||||
String.Join(",", aniMeshNodeLifecycleCallbacks.ToArray()) == "7:101:4" && !aniMeshCallbackOwnerMismatch &&
|
||||
!controlNodeUpdateOwnerMismatch && !nativeAniMeshStageCallbackMissing,
|
||||
|
||||
@@ -343,9 +343,11 @@ MSH-часть проверки подтверждает начальный сн
|
||||
`Control.dll+0xf940` кладёт результат selector `3` в текущее и обработанное
|
||||
количество; при подтверждённых единичных множителях это raw `max_volume` NDP.
|
||||
Прямой API `apply_loaded_mission_control_node_delta` меняет amount, долю, флаг
|
||||
истощения, aggregate и dirty, возвращая обязательную публикацию `0x200` для
|
||||
узла. Он пока не доставляет её в AniMesh и не выполняет пересчёт стадий,
|
||||
видимости, FX или время Control. Поэтому обработанное количество остаётся
|
||||
истощения, aggregate и dirty, применяя публикацию `0x200` к CPU-проекции
|
||||
соответствующего узла модели. Исходный `AniMesh.dll+0x5750` подтверждён в
|
||||
связке с `Control+0x10f30`: меняется только поле узла `+0x124`, а стадия и
|
||||
соседние узлы сохраняются. Эта проекция ещё не подключена к GPU/window path
|
||||
и не выполняет пересчёт стадий, видимости, FX или время Control. Поэтому обработанное количество остаётся
|
||||
прежним. Гибель root/critical узла и неизвестные flags/aggregate дают явную
|
||||
ошибку до записи; неподдержанные численные диапазоны тоже отклоняются.
|
||||
|
||||
|
||||
@@ -672,8 +672,28 @@ native `+0x590` получает `max(0, total + (new - old))`, `+0x14` — до
|
||||
это начальное значение.
|
||||
|
||||
Rust сохраняет начальные и текущие поля отдельно. Возвращаемая
|
||||
`ControlNodeProviderPublication` содержит ключ и значение для обязательного
|
||||
callback узла с selector `0x200`; автоматической доставки в AniMesh пока нет.
|
||||
`ControlNodeProviderPublication` содержит ключ и значение уже применённого
|
||||
callback selector `0x200` в CPU-проекции узла. У `AniMesh.dll` с SHA-256
|
||||
`65659bf67f20e5be77d67a5b2f59968f61b94293143807fa59ca451c560c4f7e`
|
||||
primary vtable RVA `0x2057c`, slot `+0x34`, указывает на `0x5750`.
|
||||
Этот stdcall-обработчик принимает четыре DWORD: owner, ordinal, selector,
|
||||
raw float value. Только selector `0x200` записывает float в
|
||||
`owner[+0x1a8] + ordinal * 0x130 + 0x124`. Видимая стадия `+0x1c` при этом не
|
||||
меняется. Конструктор строки RVA `0x123e0` задаёт начальные `+0x124 = 1.0`
|
||||
и `+0x1c = 0`.
|
||||
|
||||
Native live-provider oracle использует оригинальный конструктор этих строк и
|
||||
передаёт callback `Control+0x10f30` в исходный `AniMesh+0x5750` через
|
||||
прозрачную записывающую трассу обёртку. Проверены значения `0.6` и `0.1`,
|
||||
отсутствие записи у соседнего узла и прежняя стадия `77` у целевого узла;
|
||||
selector `0x201` ничего не меняет. При signed-zero no-publication случае
|
||||
provider остаётся с заданным тестом значением `0.25`, а модель сохраняет
|
||||
последнее опубликованное `0.1`. Ещё один native вектор показывает, что
|
||||
стадийное наследование доли родителя тоже не переписывает `+0x124` модели.
|
||||
Поэтому Rust хранит `mesh_state.type1_value_bits` отдельно от
|
||||
`current_type1_value_bits` provider, а `mesh_state.visible_stage` — отдельно от
|
||||
будущего Control stage/sentinel. Неизвестный узел даёт ошибку без частичной
|
||||
записи; callback отражён в CPU-проекции, но GPU/window path ещё не подключён.
|
||||
Полный пересчёт стадии `0x11130`/`0x11220`, видимости и FX также не вызывается.
|
||||
Истощение корня или критического узла возвращает `UnsupportedNodeLifecycle`
|
||||
до записи: состояние владельца `+0x550`, отложенная гибель и сопутствующие
|
||||
@@ -1049,7 +1069,8 @@ Licensed full-load regression `full_load_builds_effective_control_device_table_f
|
||||
Проверка использует исходные CTL/NDP/MSH, затем явно уменьшает количество
|
||||
строки tag-26 устройства вдвое. Rust `0xe00` и `0x1002` дают `0.5`, и обычный
|
||||
шаг runtime сохраняет эту долю. Это вызов прямого API, а не полный живой
|
||||
Control tick или автоматическая публикация изменения в AniMesh.
|
||||
Control tick. Проекция поля модели `+0x124` также получает `0.5`, а её видимая
|
||||
стадия остаётся `0`; GPU/window path этим тестом не исполняется.
|
||||
|
||||
Native-часть этих ABI проверок воспроизводит
|
||||
`behavior_distributor_tick_native_oracle.cs` на синтетически выделенной памяти
|
||||
|
||||
@@ -362,8 +362,8 @@ CollObject, Control и PhysicalModel, пропуская отсутствующ
|
||||
[техническом разборе](../appendices/script-vm.md#доставка-игровых-часов-в-control).
|
||||
Статическая цепочка ещё не доказывает исполнение активного Control update на
|
||||
миссии. Rust сохраняет исходные и текущие provider-поля отдельно. Прямой delta
|
||||
API меняет строку и возвращает публикацию узла, но описанный clock/budget-путь
|
||||
пока не подключён.
|
||||
API меняет строку и значение соответствующего CPU-узла модели, но описанный
|
||||
clock/budget-путь и вывод этого состояния в окно пока не подключены.
|
||||
|
||||
В текущем runtime `step_headless` и `frame` получают время очереди явным
|
||||
аргументом `u32`. По нему запускается представленный ресурсный планировщик
|
||||
|
||||
@@ -945,9 +945,22 @@ let ratio = amount / maximum;
|
||||
Это три отдельные операции над `f32`, с проверкой поддерживаемого численного
|
||||
диапазона до записи состояния. Исходные `initial_*` поля сохраняются;
|
||||
`processed_amount_bits` тоже не меняется, поскольку его обновляет другой
|
||||
нативный проход — пересчёт стадий. API возвращает обязательную публикацию
|
||||
`ControlNodeProviderPublication` для узла и selector `0x200`; сам callback
|
||||
AniMesh и пересчёт стадий этим API не исполняются. Истощение корня либо узла
|
||||
нативный проход — пересчёт стадий. Публикация selector `0x200` теперь применяется
|
||||
к CPU-состоянию соответствующего узла модели. Результат
|
||||
`ControlNodeProviderPublication` описывает уже выполненную запись. Пересчёт
|
||||
стадий и вывод изменённой модели в окно этим API ещё не запускаются.
|
||||
|
||||
У AniMesh есть собственное поле значения узла, отдельное от доли Control.
|
||||
Конструктор `AniMesh.dll+0x123e0` начинает его с `1.0`, а видимую стадию — с
|
||||
`0`. Callback `+0x5750` при selector `0x200` записывает число в поле `+0x124`
|
||||
строки модели с шагом `0x130`, не меняя её стадию `+0x1c` и соседние узлы.
|
||||
Native oracle соединяет исходные `Control+0x10f30` и `AniMesh+0x5750`; между
|
||||
ними стоит только запись диагностической трассы. Прямая добавка `20` к
|
||||
количеству `40` из максимума `100` записывает `0.6` и в provider, и в узел
|
||||
модели. Эти поля нельзя объединять: наследование родительской доли в отдельном
|
||||
стадийном проходе меняет provider, не повторяя callback `0x200`.
|
||||
В Rust `mesh_state` хранит это отдельное значение и видимую стадию;
|
||||
неразрешённый узел прерывает изменение до записи остальных полей. Истощение корня либо узла
|
||||
с критическим флагом `1` пока возвращает ошибку без изменения состояния:
|
||||
эта ветвь требует полного lifecycle объекта, а не только нового количества.
|
||||
|
||||
|
||||
Reference in new issue
Block a user