feat(runtime): advance loaded mission resources
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:
5 files changed
+1369
-45
No files matched your search
File diff suppressed because it is too large.
Load diff
@@ -18,6 +18,7 @@ internal static class BehaviorDistributorTickOracle {
|
|||||||
private const string ExpectedControlSha256 = "bcb86318089028548a178b03a21accdce15df0cc85ddd91674cf3c2f55012c6e";
|
private const string ExpectedControlSha256 = "bcb86318089028548a178b03a21accdce15df0cc85ddd91674cf3c2f55012c6e";
|
||||||
private const string ExpectedArealMapSha256 = "a25f9254ebb339d70e2e07c8121950191e9584ca0858d8e9c2e2b6c671edf6ea";
|
private const string ExpectedArealMapSha256 = "a25f9254ebb339d70e2e07c8121950191e9584ca0858d8e9c2e2b6c671edf6ea";
|
||||||
private const int TickRva = 0x19e80;
|
private const int TickRva = 0x19e80;
|
||||||
|
private const int DistributorScheduleRva = 0x1adb0;
|
||||||
private const int AddObjectRva = 0x1aac0;
|
private const int AddObjectRva = 0x1aac0;
|
||||||
private const int NormalizeRva = 0x1ac90;
|
private const int NormalizeRva = 0x1ac90;
|
||||||
private const int IndexedPropertySetRva = 0x0b470;
|
private const int IndexedPropertySetRva = 0x0b470;
|
||||||
@@ -27,6 +28,7 @@ internal static class BehaviorDistributorTickOracle {
|
|||||||
private const int BehaviorVarBlockConstructorRva = 0x22d90;
|
private const int BehaviorVarBlockConstructorRva = 0x22d90;
|
||||||
private const int BehaviorVarFieldRegisterRva = 0x22e20;
|
private const int BehaviorVarFieldRegisterRva = 0x22e20;
|
||||||
private const int GetSystemArealMapIatRva = 0x59008;
|
private const int GetSystemArealMapIatRva = 0x59008;
|
||||||
|
private const int GetQueueIatRva = 0x59110;
|
||||||
private const int ControlResourceDeviceGetterRva = 0x21d00;
|
private const int ControlResourceDeviceGetterRva = 0x21d00;
|
||||||
private const int ControlResourceAggregateQueryRva = 0x2b410;
|
private const int ControlResourceAggregateQueryRva = 0x2b410;
|
||||||
private const int ControlResourceAggregateSetRva = 0x2ba30;
|
private const int ControlResourceAggregateSetRva = 0x2ba30;
|
||||||
@@ -66,6 +68,9 @@ internal static class BehaviorDistributorTickOracle {
|
|||||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void X87ControlWordReader();
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void X87ControlWordReader();
|
||||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void X87ControlWordWriter();
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void X87ControlWordWriter();
|
||||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)] private delegate void TickDelegate(IntPtr self, float deltaSeconds);
|
[UnmanagedFunctionPointer(CallingConvention.ThisCall)] private delegate void TickDelegate(IntPtr self, float deltaSeconds);
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate void NativeBehaviorSchedule(IntPtr self);
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate IntPtr QueueGetter();
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate uint QueueClockGetter(IntPtr self);
|
||||||
[UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate void NativeAddObject(IntPtr self, uint key);
|
[UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate void NativeAddObject(IntPtr self, uint key);
|
||||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)] private delegate void NativeNormalize(IntPtr self, uint key);
|
[UnmanagedFunctionPointer(CallingConvention.ThisCall)] private delegate void NativeNormalize(IntPtr self, uint key);
|
||||||
[UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate void VectorInsert(IntPtr self, IntPtr source, uint count, uint position);
|
[UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate void VectorInsert(IntPtr self, IntPtr source, uint count, uint position);
|
||||||
@@ -100,6 +105,8 @@ internal static class BehaviorDistributorTickOracle {
|
|||||||
[UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate void NativeControlNodeUpdateCallback(IntPtr self, uint nodeIndex, uint selector, uint valueBits);
|
[UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate void NativeControlNodeUpdateCallback(IntPtr self, uint nodeIndex, uint selector, uint valueBits);
|
||||||
|
|
||||||
private static readonly AreaMapGetter AreaMapGetterCallback = GetAreaMap;
|
private static readonly AreaMapGetter AreaMapGetterCallback = GetAreaMap;
|
||||||
|
private static readonly QueueGetter QueueGetterCallback = GetQueue;
|
||||||
|
private static readonly QueueClockGetter QueueClockGetterCallback = GetQueueTime;
|
||||||
private static readonly VectorInsert VectorInsertCallback = InsertVector;
|
private static readonly VectorInsert VectorInsertCallback = InsertVector;
|
||||||
private static readonly MapLookup MapLookupCallback = Lookup;
|
private static readonly MapLookup MapLookupCallback = Lookup;
|
||||||
private static readonly ObjectState ObjectStateCallback = GetObjectState;
|
private static readonly ObjectState ObjectStateCallback = GetObjectState;
|
||||||
@@ -127,6 +134,7 @@ internal static class BehaviorDistributorTickOracle {
|
|||||||
private static X87ControlWordWriter x87ControlWordWriter;
|
private static X87ControlWordWriter x87ControlWordWriter;
|
||||||
private static readonly NativeBehaviorProfileLoadCallback BehaviorProfileLoadCallback = CaptureBehaviorProfileLoad;
|
private static readonly NativeBehaviorProfileLoadCallback BehaviorProfileLoadCallback = CaptureBehaviorProfileLoad;
|
||||||
private static readonly List<BehaviorProfileSelection> behaviorProfileSelections = new List<BehaviorProfileSelection>();
|
private static readonly List<BehaviorProfileSelection> behaviorProfileSelections = new List<BehaviorProfileSelection>();
|
||||||
|
private static Fixture activeQueueFixture;
|
||||||
|
|
||||||
private sealed class BehaviorProfileSelection {
|
private sealed class BehaviorProfileSelection {
|
||||||
public IntPtr Owner;
|
public IntPtr Owner;
|
||||||
@@ -168,6 +176,9 @@ internal static class BehaviorDistributorTickOracle {
|
|||||||
public IntPtr VectorVtable;
|
public IntPtr VectorVtable;
|
||||||
public IntPtr Manager;
|
public IntPtr Manager;
|
||||||
public IntPtr ManagerVtable;
|
public IntPtr ManagerVtable;
|
||||||
|
public IntPtr Queue;
|
||||||
|
public IntPtr QueueVtable;
|
||||||
|
public uint QueueTime;
|
||||||
public readonly List<IntPtr> Allocations = new List<IntPtr>();
|
public readonly List<IntPtr> Allocations = new List<IntPtr>();
|
||||||
public readonly Dictionary<uint, NativeObject> ByKey = new Dictionary<uint, NativeObject>();
|
public readonly Dictionary<uint, NativeObject> ByKey = new Dictionary<uint, NativeObject>();
|
||||||
public readonly List<string> Calls = new List<string>();
|
public readonly List<string> Calls = new List<string>();
|
||||||
@@ -221,6 +232,8 @@ internal static class BehaviorDistributorTickOracle {
|
|||||||
Marshal.WriteInt32(Distributor, 0x20, keys.Length);
|
Marshal.WriteInt32(Distributor, 0x20, keys.Length);
|
||||||
Marshal.WriteInt32(Distributor, 0x24, capacity);
|
Marshal.WriteInt32(Distributor, 0x24, capacity);
|
||||||
Marshal.WriteIntPtr(Distributor, 0x28, Entries);
|
Marshal.WriteIntPtr(Distributor, 0x28, Entries);
|
||||||
|
Marshal.WriteInt16(Distributor, 0x18, 3);
|
||||||
|
Marshal.WriteInt16(Distributor, 0x1a, 1);
|
||||||
for (int i = 0; i < keys.Length; i++) {
|
for (int i = 0; i < keys.Length; i++) {
|
||||||
IntPtr entry = IntPtr.Add(Entries, i * 0x1c);
|
IntPtr entry = IntPtr.Add(Entries, i * 0x1c);
|
||||||
Marshal.WriteInt32(entry, 0x00, unchecked((int)keys[i]));
|
Marshal.WriteInt32(entry, 0x00, unchecked((int)keys[i]));
|
||||||
@@ -233,8 +246,16 @@ internal static class BehaviorDistributorTickOracle {
|
|||||||
Allocations.Add(value);
|
Allocations.Add(value);
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
public void MakeQueue(uint time) {
|
||||||
|
QueueTime = time;
|
||||||
|
QueueVtable = Allocate(0x40);
|
||||||
|
Queue = Allocate(0x20);
|
||||||
|
Marshal.WriteIntPtr(Queue, 0, QueueVtable);
|
||||||
|
Marshal.WriteIntPtr(QueueVtable, 0x38, Marshal.GetFunctionPointerForDelegate(QueueClockGetterCallback));
|
||||||
|
}
|
||||||
public void Dispose() {
|
public void Dispose() {
|
||||||
active = null;
|
active = null;
|
||||||
|
if (activeQueueFixture == this) activeQueueFixture = null;
|
||||||
for (int i = Allocations.Count - 1; i >= 0; i--) Marshal.FreeHGlobal(Allocations[i]);
|
for (int i = Allocations.Count - 1; i >= 0; i--) Marshal.FreeHGlobal(Allocations[i]);
|
||||||
Allocations.Clear();
|
Allocations.Clear();
|
||||||
}
|
}
|
||||||
@@ -287,6 +308,16 @@ internal static class BehaviorDistributorTickOracle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static IntPtr GetAreaMap() { return active.Manager; }
|
private static IntPtr GetAreaMap() { return active.Manager; }
|
||||||
|
private static IntPtr GetQueue() {
|
||||||
|
if (activeQueueFixture == null || activeQueueFixture.Queue == IntPtr.Zero)
|
||||||
|
throw new InvalidOperationException("native scheduler requested a queue without a fixture");
|
||||||
|
return activeQueueFixture.Queue;
|
||||||
|
}
|
||||||
|
private static uint GetQueueTime(IntPtr self) {
|
||||||
|
if (activeQueueFixture == null || self != activeQueueFixture.Queue)
|
||||||
|
throw new InvalidOperationException("native scheduler queried an unexpected queue");
|
||||||
|
return activeQueueFixture.QueueTime;
|
||||||
|
}
|
||||||
private static int OnArealMapEvent(IntPtr owner, uint message, uint subcode, IntPtr descriptor) {
|
private static int OnArealMapEvent(IntPtr owner, uint message, uint subcode, IntPtr descriptor) {
|
||||||
ArealMapFixture fixture = activeArealMap;
|
ArealMapFixture fixture = activeArealMap;
|
||||||
if (fixture == null) return 0;
|
if (fixture == null) return 0;
|
||||||
@@ -493,7 +524,9 @@ internal static class BehaviorDistributorTickOracle {
|
|||||||
FreeLibrary(module); FreeLibrary(controlModule); return 12;
|
FreeLibrary(module); FreeLibrary(controlModule); return 12;
|
||||||
}
|
}
|
||||||
IntPtr import = IntPtr.Add(module, GetSystemArealMapIatRva);
|
IntPtr import = IntPtr.Add(module, GetSystemArealMapIatRva);
|
||||||
|
IntPtr queueImport = IntPtr.Add(module, GetQueueIatRva);
|
||||||
IntPtr originalImport = Marshal.ReadIntPtr(import);
|
IntPtr originalImport = Marshal.ReadIntPtr(import);
|
||||||
|
IntPtr originalQueueImport = Marshal.ReadIntPtr(queueImport);
|
||||||
uint oldProtection = 0;
|
uint oldProtection = 0;
|
||||||
bool protectionChanged = false;
|
bool protectionChanged = false;
|
||||||
bool thunksAllocated = false;
|
bool thunksAllocated = false;
|
||||||
@@ -513,8 +546,9 @@ internal static class BehaviorDistributorTickOracle {
|
|||||||
throw new InvalidOperationException("VirtualProtect(GetSystemArealMap IAT) failed: " + Marshal.GetLastWin32Error());
|
throw new InvalidOperationException("VirtualProtect(GetSystemArealMap IAT) failed: " + Marshal.GetLastWin32Error());
|
||||||
protectionChanged = true;
|
protectionChanged = true;
|
||||||
Marshal.WriteIntPtr(import, Marshal.GetFunctionPointerForDelegate(AreaMapGetterCallback));
|
Marshal.WriteIntPtr(import, Marshal.GetFunctionPointerForDelegate(AreaMapGetterCallback));
|
||||||
|
Marshal.WriteIntPtr(queueImport, Marshal.GetFunctionPointerForDelegate(QueueGetterCallback));
|
||||||
RunSmokeCases(module, controlModule, arealMapModule);
|
RunSmokeCases(module, controlModule, arealMapModule);
|
||||||
Console.WriteLine("original native Behavior/ArealMap/Control oracle passed (Behavior Add 0x1aac0, normalize 0x1ac90, tick 0x19e80; ArealMap UnitDAT projection 0x14b90; Control INTO override 0x2d890; only synthetic host callbacks, GetSystemArealMap IAT, and bounded vector insertion are substituted; this does not compare Rust parity).");
|
Console.WriteLine("original native Behavior/ArealMap/Control oracle passed (Behavior Add 0x1aac0, normalize 0x1ac90, scheduler 0x1adb0, tick 0x19e80; ArealMap UnitDAT projection 0x14b90; Control INTO override 0x2d890; synthetic host callbacks, GetSystemArealMap/GetQueue IAT slots, and bounded vector insertion are substituted; scheduler and tick machine code are original; this does not compare Rust parity).");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
catch (Exception error) {
|
catch (Exception error) {
|
||||||
@@ -524,10 +558,12 @@ internal static class BehaviorDistributorTickOracle {
|
|||||||
finally {
|
finally {
|
||||||
if (protectionChanged) {
|
if (protectionChanged) {
|
||||||
Marshal.WriteIntPtr(import, originalImport);
|
Marshal.WriteIntPtr(import, originalImport);
|
||||||
|
Marshal.WriteIntPtr(queueImport, originalQueueImport);
|
||||||
uint ignored;
|
uint ignored;
|
||||||
VirtualProtect(import, new UIntPtr((uint)IntPtr.Size), oldProtection, out ignored);
|
VirtualProtect(import, new UIntPtr((uint)IntPtr.Size), oldProtection, out ignored);
|
||||||
}
|
}
|
||||||
GC.KeepAlive(AreaMapGetterCallback); GC.KeepAlive(VectorInsertCallback); GC.KeepAlive(x87ControlWordReader); GC.KeepAlive(x87ControlWordWriter); GC.KeepAlive(MapLookupCallback);
|
GC.KeepAlive(AreaMapGetterCallback); GC.KeepAlive(QueueGetterCallback); GC.KeepAlive(QueueClockGetterCallback);
|
||||||
|
GC.KeepAlive(VectorInsertCallback); GC.KeepAlive(x87ControlWordReader); GC.KeepAlive(x87ControlWordWriter); GC.KeepAlive(MapLookupCallback);
|
||||||
GC.KeepAlive(ObjectStateCallback); GC.KeepAlive(ObjectClassCallback);
|
GC.KeepAlive(ObjectStateCallback); GC.KeepAlive(ObjectClassCallback);
|
||||||
GC.KeepAlive(ObjectQueryCallback); GC.KeepAlive(ObjectRateCallback);
|
GC.KeepAlive(ObjectQueryCallback); GC.KeepAlive(ObjectRateCallback);
|
||||||
GC.KeepAlive(ObjectGetPropertyCallback); GC.KeepAlive(ObjectSetPropertyCallback);
|
GC.KeepAlive(ObjectGetPropertyCallback); GC.KeepAlive(ObjectSetPropertyCallback);
|
||||||
@@ -565,6 +601,19 @@ internal static class BehaviorDistributorTickOracle {
|
|||||||
}
|
}
|
||||||
RunNativeIndexedPropertyCases(module);
|
RunNativeIndexedPropertyCases(module);
|
||||||
RunNativeOreInitializationCases(module);
|
RunNativeOreInitializationCases(module);
|
||||||
|
ushort tickControlWord = CurrentX87ControlWord();
|
||||||
|
try {
|
||||||
|
SetX87ControlWord(0x007f);
|
||||||
|
Expect(CurrentX87ControlWord() == 0x007f, "Behavior resource-distributor tick uses the measured game x87 control word");
|
||||||
|
Console.WriteLine("native-behavior-tick-x87-control-word=0x" + CurrentX87ControlWord().ToString("x4"));
|
||||||
|
RunNativeBehaviorTickCases(module);
|
||||||
|
RunNativeBehaviorSchedulerCases(module);
|
||||||
|
} finally {
|
||||||
|
SetX87ControlWord(tickControlWord);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RunNativeBehaviorTickCases(IntPtr module) {
|
||||||
TickDelegate tick = (TickDelegate)Marshal.GetDelegateForFunctionPointer(IntPtr.Add(module, TickRva), typeof(TickDelegate));
|
TickDelegate tick = (TickDelegate)Marshal.GetDelegateForFunctionPointer(IntPtr.Add(module, TickRva), typeof(TickDelegate));
|
||||||
using (Fixture empty = NewFixture()) {
|
using (Fixture empty = NewFixture()) {
|
||||||
empty.MakeDistributor(); active = empty; tick(empty.Distributor, 0.0f);
|
empty.MakeDistributor(); active = empty; tick(empty.Distributor, 0.0f);
|
||||||
@@ -579,6 +628,10 @@ internal static class BehaviorDistributorTickOracle {
|
|||||||
RunObjectCase(tick, 0x80000008, 0.5f, 0.0f, 3.0f, 0, 2.0f, "ore-80000008");
|
RunObjectCase(tick, 0x80000008, 0.5f, 0.0f, 3.0f, 0, 2.0f, "ore-80000008");
|
||||||
RunMixedOreCase(tick, "mixed-ore-producer-recipient", 1.0f, 0.5f);
|
RunMixedOreCase(tick, "mixed-ore-producer-recipient", 1.0f, 0.5f);
|
||||||
RunMixedOreCase(tick, "bounded-ore-store", 0.25f, 0.5f);
|
RunMixedOreCase(tick, "bounded-ore-store", 0.25f, 0.5f);
|
||||||
|
RunOreAllocationBranch(tick, "ore-allocation-middle", 0.25f, 2.0f, 0.25f, 0.625f, 1.75f, 0.5f);
|
||||||
|
RunOreAllocationBranch(tick, "ore-allocation-surplus", 2.0f, 3.0f, 0.25f, 0.625f, 2.625f, 0.625f);
|
||||||
|
RunOreAllocationBranch(tick, "ore-allocation-equality", 0.375f, 2.0f, 0.25f, 0.625f, 1.625f, 0.625f);
|
||||||
|
RunNativeOreClampCases(tick);
|
||||||
RunWeightedOreCase(tick);
|
RunWeightedOreCase(tick);
|
||||||
RunMixedPowerCase(tick, "mixed-power-sufficient", 4.0f, 1.0f, 0.5f, 0.75f);
|
RunMixedPowerCase(tick, "mixed-power-sufficient", 4.0f, 1.0f, 0.5f, 0.75f);
|
||||||
RunMixedPowerCase(tick, "mixed-power-shortage", 0.125f, 1.0f, 0.5f, 0.75f);
|
RunMixedPowerCase(tick, "mixed-power-shortage", 0.125f, 1.0f, 0.5f, 0.75f);
|
||||||
@@ -587,6 +640,62 @@ internal static class BehaviorDistributorTickOracle {
|
|||||||
RunSkippedCase(tick);
|
RunSkippedCase(tick);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void RunNativeBehaviorSchedulerCases(IntPtr module) {
|
||||||
|
NativeBehaviorSchedule schedule = (NativeBehaviorSchedule)Marshal.GetDelegateForFunctionPointer(
|
||||||
|
IntPtr.Add(module, DistributorScheduleRva), typeof(NativeBehaviorSchedule));
|
||||||
|
uint[] elapsedVectors = { 1, 1001, 16777217, 16777219, 0xffffffff };
|
||||||
|
uint[] expectedBits = { 0x3a83126f, 0x3f8020c5, 0x46831270, 0x46831271, 0x4a83126f };
|
||||||
|
for (int i = 0; i < elapsedVectors.Length; i++) {
|
||||||
|
uint elapsed = elapsedVectors[i];
|
||||||
|
uint now = unchecked(1u + elapsed);
|
||||||
|
RunNativeSchedulerSample(schedule, now, 1, 0, expectedBits[i], "elapsed-" + elapsed.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
using (Fixture fixture = NewFixture()) {
|
||||||
|
NativeObject producer = fixture.AddObject(0x75, 0x80000002, 1.0f, 0.0f, 0.0f, 0);
|
||||||
|
fixture.MakeDistributor(producer.Key);
|
||||||
|
fixture.MakeQueue(10);
|
||||||
|
active = fixture;
|
||||||
|
activeQueueFixture = fixture;
|
||||||
|
Marshal.WriteInt32(fixture.Distributor, 0x10, 1);
|
||||||
|
Marshal.WriteInt32(fixture.Distributor, 0x14, 10);
|
||||||
|
Marshal.WriteInt32(fixture.Distributor, 0x30, unchecked((int)0x41200000));
|
||||||
|
schedule(fixture.Distributor);
|
||||||
|
Expect(fixture.LookupCount == 0 && unchecked((uint)Marshal.ReadInt32(fixture.Distributor, 0x30)) == 0x41200000,
|
||||||
|
"scheduler equality with the deadline performs no resource tick or totals write");
|
||||||
|
|
||||||
|
fixture.QueueTime = 11;
|
||||||
|
schedule(fixture.Distributor);
|
||||||
|
uint deadlineTotal = unchecked((uint)Marshal.ReadInt32(fixture.Distributor, 0x30));
|
||||||
|
Expect(fixture.LookupCount == 2 && deadlineTotal == 0x3c23d70b,
|
||||||
|
"scheduler ticks only after the deadline is strictly passed (lookups=" + fixture.LookupCount +
|
||||||
|
", total=" + Hex(deadlineTotal) + ")");
|
||||||
|
Console.WriteLine("scheduler-deadline: equality=skip; now=11 delta=0.01; lookups=" + fixture.LookupCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
RunNativeSchedulerSample(schedule, 0, 0xffffffff, 0, 0x3a83126f, "wrapped-elapsed-1");
|
||||||
|
GC.KeepAlive(schedule);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RunNativeSchedulerSample(
|
||||||
|
NativeBehaviorSchedule schedule, uint now, uint firstTime, uint deadline, uint expectedTotalBits, string label) {
|
||||||
|
using (Fixture fixture = NewFixture()) {
|
||||||
|
NativeObject producer = fixture.AddObject(0x74, 0x80000002, 1.0f, 0.0f, 0.0f, 0);
|
||||||
|
fixture.MakeDistributor(producer.Key);
|
||||||
|
fixture.MakeQueue(now);
|
||||||
|
active = fixture;
|
||||||
|
activeQueueFixture = fixture;
|
||||||
|
Marshal.WriteInt32(fixture.Distributor, 0x10, unchecked((int)firstTime));
|
||||||
|
Marshal.WriteInt32(fixture.Distributor, 0x14, unchecked((int)deadline));
|
||||||
|
schedule(fixture.Distributor);
|
||||||
|
uint actual = unchecked((uint)Marshal.ReadInt32(fixture.Distributor, 0x30));
|
||||||
|
Expect(actual == expectedTotalBits,
|
||||||
|
label + " native qword-to-f32 scheduler delta expected " + Hex(expectedTotalBits) + " got " + Hex(actual));
|
||||||
|
Console.WriteLine("native-scheduler-" + label + ": now=" + now.ToString("x8") + " first=" + firstTime.ToString("x8") +
|
||||||
|
" total=" + Hex(actual) + " lookups=" + fixture.LookupCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void RunNativeControlResourceDeviceCase(IntPtr module) {
|
private static void RunNativeControlResourceDeviceCase(IntPtr module) {
|
||||||
Expect(CurrentX87ControlWord() == 0x007f, "Control selector 0x300 uses x87 control word 0x007f");
|
Expect(CurrentX87ControlWord() == 0x007f, "Control selector 0x300 uses x87 control word 0x007f");
|
||||||
const uint key = 2;
|
const uint key = 2;
|
||||||
@@ -1641,6 +1750,58 @@ internal static class BehaviorDistributorTickOracle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void RunOreAllocationBranch(
|
||||||
|
TickDelegate tick, string label, float rate, float stock, float recipientCurrent, float recipientMaximum,
|
||||||
|
float expectedProducerRemaining, float expectedRecipientResult) {
|
||||||
|
using (Fixture fixture = NewFixture()) {
|
||||||
|
NativeObject producer = fixture.AddObject(0x46, 0x80000004, 0.0f, rate, stock, 0);
|
||||||
|
producer.PropertyMetadata = FloatBits(10.0f);
|
||||||
|
NativeObject recipient = fixture.AddObject(0x47, 0x80000001, 0.0f, 0.0f, recipientCurrent, 0);
|
||||||
|
recipient.PropertyMetadata = FloatBits(recipientMaximum);
|
||||||
|
recipient.InterfaceSelector1 = 0.5f;
|
||||||
|
recipient.InterfaceSelector2 = 0.0f;
|
||||||
|
fixture.MakeDistributor(producer.Key, recipient.Key);
|
||||||
|
Marshal.WriteInt32(fixture.Entries, 0x04, FloatBits(1.0f));
|
||||||
|
Marshal.WriteInt32(fixture.Entries, 0x1c + 0x04, FloatBits(0.5f));
|
||||||
|
active = fixture;
|
||||||
|
tick(fixture.Distributor, 1.0f);
|
||||||
|
Console.WriteLine(label + ": totals=[" + Float(ReadFloat(fixture.Distributor, 0x30)) + "," +
|
||||||
|
Float(ReadFloat(fixture.Distributor, 0x34)) + "," + Float(ReadFloat(fixture.Distributor, 0x38)) + "," +
|
||||||
|
Float(ReadFloat(fixture.Distributor, 0x3c)) + "," + Float(ReadFloat(fixture.Distributor, 0x40)) +
|
||||||
|
"]; stored=[" + Float(producer.Property) + "," + Float(recipient.Property) + "] reads=" + producer.OreRateReads);
|
||||||
|
Expect(ReadFloat(fixture.Distributor, 0x34) == Math.Min(rate, stock) &&
|
||||||
|
ReadFloat(fixture.Distributor, 0x40) == recipientMaximum - recipientCurrent &&
|
||||||
|
producer.Property == expectedProducerRemaining && recipient.Property == expectedRecipientResult &&
|
||||||
|
producer.OreRateReads == 2 && producer.Writes == 1 && recipient.Writes == 1 && fixture.LookupCount == 4,
|
||||||
|
label + " native allocation branch totals and two-pass reread");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RunNativeOreClampCases(TickDelegate tick) {
|
||||||
|
float[][] states = {
|
||||||
|
new float[] { 0.995f, 1.0f, 1.0f },
|
||||||
|
new float[] { 1.25f, 1.0f, 1.0f },
|
||||||
|
new float[] { 0.0f, 0.01f, 0.0f }
|
||||||
|
};
|
||||||
|
foreach (float[] state in states) {
|
||||||
|
float current = state[0];
|
||||||
|
float maximum = state[1];
|
||||||
|
float expected = state[2];
|
||||||
|
using (Fixture fixture = NewFixture()) {
|
||||||
|
NativeObject item = fixture.AddObject(0x48, 0x80000001, 0.0f, 0.0f, current, 0);
|
||||||
|
item.PropertyMetadata = FloatBits(maximum);
|
||||||
|
item.InterfaceSelector2 = 0.0f;
|
||||||
|
fixture.MakeDistributor(item.Key);
|
||||||
|
active = fixture;
|
||||||
|
tick(fixture.Distributor, 1.0f);
|
||||||
|
Expect(item.Property == expected && item.Writes > 0 && fixture.LookupCount == 2,
|
||||||
|
"native first-pass ore clamp for CurrentOre=" + Float(current));
|
||||||
|
Console.WriteLine("native-ore-clamp: current=" + Float(current) + " maximum=" + Float(maximum) + " result=" +
|
||||||
|
Float(item.Property) + " writes=" + item.Writes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void RunMixedPowerCase(TickDelegate tick, string label, float powerRate, float recipientProperty, float selector1, float selector2) {
|
private static void RunMixedPowerCase(TickDelegate tick, string label, float powerRate, float recipientProperty, float selector1, float selector2) {
|
||||||
using (Fixture fixture = NewFixture()) {
|
using (Fixture fixture = NewFixture()) {
|
||||||
NativeObject producer = fixture.AddObject(0x61, 0x80000002, powerRate, 0.0f, 1.0f, 0);
|
NativeObject producer = fixture.AddObject(0x61, 0x80000002, powerRate, 0.0f, 1.0f, 0);
|
||||||
|
|||||||
@@ -325,17 +325,28 @@ sidecar недоступен, запрос `0xe00` сообщает это от
|
|||||||
|
|
||||||
Rust query обращается к загруженному объекту и поддерживает `0xe00` только для
|
Rust query обращается к загруженному объекту и поддерживает `0xe00` только для
|
||||||
текущего descriptor tag `26` с factory tag `26`; другие активные typed tags и
|
текущего descriptor tag `26` с factory tag `26`; другие активные typed tags и
|
||||||
factory paths остаются явными границами. Snapshot показывает состояние сразу
|
factory paths остаются явными границами. Этот запрос возвращает начальный
|
||||||
после native-конструкторов, но он не обновляется при Control tick-е. Native
|
снимок provider amount: Native `FUN_10010f30` (RVA `0x10f30`) изменяет и
|
||||||
`FUN_10010f30` (RVA `0x10f30`) изменяет и ограничивает provider amount, а
|
ограничивает количество, а связанные callbacks переносят его между строками;
|
||||||
связанные callbacks переносят его между строками; их общий переход в Rust ещё
|
этот переход в Rust ещё не реализован.
|
||||||
не реализован. Поэтому вызов нельзя считать живым ресурсным симулятором.
|
|
||||||
|
Отдельный API `advance_loaded_mission_resources` принимает явное время очереди
|
||||||
|
и обновляет представленные поля `CurrentOre`, строк распределителя и Control
|
||||||
|
selector `1`. Его граница — статические снимки профиля и provider-значений:
|
||||||
|
динамическое распространение Control amount, смена профилей, special
|
||||||
|
object-state gate и жизненный цикл созданных/удалённых объектов здесь не
|
||||||
|
моделируются. Если обязательный запрос позднего объекта не поддержан,
|
||||||
|
транзакция возвращает ошибку и не применяет подготовленные изменения. API не
|
||||||
|
подключён к игровому циклу; AutoDemo-тест подтверждает связь настоящих профилей
|
||||||
|
с этим шагом, но не полный паритет миссии. Отдельный aggregate getter Behavior
|
||||||
|
RVA `0xa490` в oracle не исполнен.
|
||||||
|
|
||||||
Behavior selectors `0x1002` и `0x1005` используют значения выбранного
|
Behavior selectors `0x1002` и `0x1005` используют значения выбранного
|
||||||
профиля из `behpsp.res`, разобранного до создания нового мира. `0x1002` складывает
|
профиля из `behpsp.res`, разобранного до создания нового мира. `0x1002` складывает
|
||||||
только текущие descriptor tag `26` запросы `0xe00`, затем применяет
|
только текущие descriptor tag `26` запросы `0xe00`, затем применяет
|
||||||
`Transfer_Ore_OffBoard`; `0x1005` возвращает `Transfer_Power_Out`. Это начальный
|
`Transfer_Ore_OffBoard`; `0x1005` возвращает `Transfer_Power_Out`. Это начальный
|
||||||
снимок: он не отражает последующие изменения provider-строк, Control tick-а или
|
снимок профиля и provider-строк. Полная загрузка сохраняет результат у
|
||||||
профиля. Полная загрузка сохраняет результат у `ObjectHandle` и возвращает
|
`ObjectHandle` и возвращает
|
||||||
ошибку, если известный код класса не имеет корректной записи; класс,
|
ошибку, если известный код класса не имеет корректной записи; класс,
|
||||||
отсутствующий в таблице выбора, сохраняет начальные значения конструктора.
|
отсутствующий в таблице выбора, сохраняет начальные значения конструктора.
|
||||||
Preview и объекты без полного Unit DAT не получают профильное состояние.
|
Preview и объекты без полного Unit DAT не получают профильное состояние.
|
||||||
|
|||||||
@@ -373,10 +373,17 @@ nearest-even `FISTP qword`, младший DWORD, преобразование D
|
|||||||
deadline `+0x14` по формуле `время + 3*64 + jitter`, где `jitter` лежит в
|
deadline `+0x14` по формуле `время + 3*64 + jitter`, где `jitter` лежит в
|
||||||
`0..63` и получен одним шагом глобального MSVCRT LCG. В этой ветви она также
|
`0..63` и получен одним шагом глобального MSVCRT LCG. В этой ветви она также
|
||||||
обнуляет пять слов `+0x30..+0x40`. При повторном вызове до deadline ненулевой
|
обнуляет пять слов `+0x30..+0x40`. При повторном вызове до deadline ненулевой
|
||||||
`+0x10` оставляет deadline прежним, не расходует новое случайное число и не
|
`+0x10` оставляет срок прежним, не расходует новое случайное число и не
|
||||||
очищает эти слова. Поведение после истечения deadline этим опытом не проверено.
|
очищает эти слова. Если само время очереди равно нулю, `+0x10` остаётся
|
||||||
Если само время очереди равно нулю, `+0x10` остаётся нулевым, и повторный вызов
|
нулевым, и повторный вызов снова проходит ветвь начальной подготовки. После
|
||||||
снова проходит ветвь начальной подготовки.
|
срока scheduler продолжает при нулевом deadline или при беззнаковом
|
||||||
|
`now > deadline`; равенство ненулевому deadline ещё ждёт. Он считает
|
||||||
|
`u32(now - first_time)` с переполнением по модулю `2^32`, переводит миллисекунды в секунды через x87
|
||||||
|
`FILD`/`FMUL`/`FSTP` и планирует следующий срок от текущего времени. Шаг
|
||||||
|
интервала равен `3 * 64 + jitter`: один шаг LCG `state = state * 214013 +
|
||||||
|
2531011` выдаёт значение `random = (state >> 16) & 0x7fff`, а
|
||||||
|
`jitter = ((random & 0xff) * 64) >> 8`. Native oracle проверяет
|
||||||
|
равенство и проход срока, обёртку и большие интервалы с округлением времени.
|
||||||
|
|
||||||
Путь host callback в проверенной паре DLL теперь статически известен. В
|
Путь host callback в проверенной паре DLL теперь статически известен. В
|
||||||
`iron3d.dll` функция `FUN_1005ff40` (RVA `0x5ff40`) сначала вызывает
|
`iron3d.dll` функция `FUN_1005ff40` (RVA `0x5ff40`) сначала вызывает
|
||||||
@@ -400,9 +407,11 @@ callback-точку входа и порядок внутри неё, но не
|
|||||||
bundle, но остальные потребители исходного глобального RNG не моделируются.
|
bundle, но остальные потребители исходного глобального RNG не моделируются.
|
||||||
В Rust также нет значения аргумента в `AI+0x3fc`, recursion guard `AI+0x78`
|
В Rust также нет значения аргумента в `AI+0x3fc`, recursion guard `AI+0x78`
|
||||||
или одноразового latch `AI+0x8a8`. Поэтому это связанный начальный поднабор, а
|
или одноразового latch `AI+0x8a8`. Поэтому это связанный начальный поднабор, а
|
||||||
не полная активация SuperAI. Восстановленный ниже distributor tick проверен на
|
не полная активация SuperAI. После полной загрузки узкая Rust-реализация
|
||||||
исходной DLL, но пока не подключён к Rust игровому циклу: у runtime нет
|
вызывается явно через `advance_loaded_mission_resources(engine, queue_time_ms)`;
|
||||||
ArealMap-объектов и интерфейсов, которые нужны этому вызову. При
|
runtime не читает системную очередь, а приложение пока не вызывает API из
|
||||||
|
игрового цикла. Шаг использует разрешённые handles и представленные snapshots
|
||||||
|
профилей и Control, поэтому не воспроизводит отсутствующие в них callbacks. При
|
||||||
ошибке ограниченного `Init` Rust отклоняет полную загрузку и сохраняет прежний
|
ошибке ограниченного `Init` Rust отклоняет полную загрузку и сохраняет прежний
|
||||||
мир и состояние генератора; такая политика отката принадлежит Rust loader-у.
|
мир и состояние генератора; такая политика отката принадлежит Rust loader-у.
|
||||||
|
|
||||||
@@ -428,9 +437,33 @@ DWORD по смещению `+0x14` как `f32`; если выходной ук
|
|||||||
пока не получило доменного имени. Идентификаторы и границы вызовов не
|
пока не получило доменного имени. Идентификаторы и границы вызовов не
|
||||||
задают полную семантику свойств.
|
задают полную семантику свойств.
|
||||||
|
|
||||||
Изолированный x86 oracle вызывает исходный tick с подменой только IAT-слота
|
Раздача считает доступный за этот шаг объём `A`, полный спрос `D` и взвешенный
|
||||||
`GetSystemArealMap`; синтетические vtables записывают последовательность
|
спрос `W = Σ(demand × share)`. Из них выводятся коэффициенты приоритетной доли
|
||||||
вызовов и ответы. Для смешанного примера два прохода нашли объекты в порядке
|
`P`, остатка `R` и расхода `C`. В таблице ниже показан исходный порядок
|
||||||
|
проверок: он важен, когда нестандартные доли дают `W > D`.
|
||||||
|
|
||||||
|
| Первое совпавшее условие (при `A > 0`, `D > 0`) | `P` | `R` | `C` |
|
||||||
|
| --- | ---: | ---: | ---: |
|
||||||
|
| `A ≤ W` | `A / W` | `0` | `1` |
|
||||||
|
| `W < A < D` | `1` | `(A − W) / (D − W)` | `1` |
|
||||||
|
| `A ≥ D` | `1` | `1` | `D / A` |
|
||||||
|
|
||||||
|
При нулевом доступном количестве или спросе native path не выделяет
|
||||||
|
положительное значение. Строка с долей `share` получает коэффициент
|
||||||
|
`(1 − share) × R + share × P`, который сочетается с её спросом или доступной
|
||||||
|
вместимостью. `C` задаёт долю отбора руды у источников; для источников энергии
|
||||||
|
этот коэффициент вычисляется, но native-код не применяет его к производству.
|
||||||
|
У объектов в ветви приёма руды (не источников руды) native-код ограничивает `CurrentOre`:
|
||||||
|
если `MaximumOre − CurrentOre < 0.01`, значение становится `MaximumOre`.
|
||||||
|
Сравнение строгое: при разнице ровно `0.01` значение не меняется.
|
||||||
|
В x86-коде это константа `0x3c23d70a`; сравнение и округление проверены при
|
||||||
|
игровом x87 control word `0x007f`.
|
||||||
|
|
||||||
|
Изолированный x86 oracle вызывает исходный scheduler RVA `0x1adb0`, который
|
||||||
|
сам вызывает исходный tick RVA `0x19e80`. Для синтетического времени он
|
||||||
|
подменяет IAT-слот `GetQueue`; tick подменяет `GetSystemArealMap`, а синтетические
|
||||||
|
vtables записывают последовательность вызовов и ответы. Для смешанного примера
|
||||||
|
два прохода нашли объекты в порядке
|
||||||
`41,42,41,42`. Производитель руды с начальным значением `1` и скоростью `4`
|
`41,42,41,42`. Производитель руды с начальным значением `1` и скоростью `4`
|
||||||
вернул при следующем чтении скорость `1`; получатель с начальным значением `6`
|
вернул при следующем чтении скорость `1`; получатель с начальным значением `6`
|
||||||
получил `7`, а агрегаты distributor равны `[0,1,1,0.375,6]`. При запасе
|
получил `7`, а агрегаты distributor равны `[0,1,1,0.375,6]`. При запасе
|
||||||
@@ -441,11 +474,13 @@ selector `1` на интерфейсе получателя становится
|
|||||||
`0x3f2aaaab`). Эти векторы проверяют арифметику исходной функции, но не
|
`0x3f2aaaab`). Эти векторы проверяют арифметику исходной функции, но не
|
||||||
распределение реальной карты.
|
распределение реальной карты.
|
||||||
|
|
||||||
Полная загрузка Rust теперь собирает этот список из runtime `ObjectHandle` и
|
Полная загрузка Rust собирает этот список из runtime `ObjectHandle` и сохраняет
|
||||||
сохраняет веса вместе с пятью исходными DWORD хвоста каждой записи. Членство
|
веса и поля строк, а также начальные ore-свойства объектов. Членство строится
|
||||||
строится только для полного режима; preview остаётся статическим. Это пока не
|
только для полного режима; preview остаётся статическим. Явный API
|
||||||
даёт ресурсных полей и интерфейсов ArealMap для `FUN_10019e80`, а
|
`advance_loaded_mission_resources` использует очередь времени, расписание,
|
||||||
`step_headless` по-прежнему шагает только `World`.
|
профили и представленные Control-таблицы, чтобы исполнить этот ограниченный
|
||||||
|
шаг над загруженным состоянием. `step_headless` по-прежнему шагает только
|
||||||
|
`World`.
|
||||||
|
|
||||||
### Добавление и нормализация долей
|
### Добавление и нормализация долей
|
||||||
|
|
||||||
@@ -520,17 +555,21 @@ exit $LASTEXITCODE
|
|||||||
|
|
||||||
Oracle проверяет SHA-256 оригинального `Behavior.dll`:
|
Oracle проверяет SHA-256 оригинального `Behavior.dll`:
|
||||||
`ddfbdbde07832358fe6e35e01c8dba9b58e7313789498b6049ed51d4ed9baf99`. Harness
|
`ddfbdbde07832358fe6e35e01c8dba9b58e7313789498b6049ed51d4ed9baf99`. Harness
|
||||||
явно устанавливает x87 CW `0x007f` для вызовов Add (`0x1aac0`) и normalize
|
явно устанавливает x87 CW `0x007f` для Add (`0x1aac0`), normalize (`0x1ac90`)
|
||||||
(`0x1ac90`), проверяет CW до и после каждого вызова и восстанавливает прежнее
|
и scheduler/tick (`0x1adb0`/`0x19e80`), проверяя слово перед набором вызовов
|
||||||
значение перед tick suite. Для этих двух методов подменяется только bounded
|
и восстанавливая прежнее значение после каждого набора. Для Add и normalize
|
||||||
вставка синтетического vector; их машинный код не патчится. Для tick helper
|
подменяется только bounded вставка синтетического vector; их машинный код не
|
||||||
подменяет только IAT-слот `GetSystemArealMap` и вызывает RVA `0x19e80`.
|
патчится. Scheduler и tick тоже исполняются оригинальными: подменяются лишь
|
||||||
Проверяются нулевой
|
IAT-слоты `GetQueue` и `GetSystemArealMap`, а synthetic vtables задают часы и
|
||||||
шаг, обе ветви селекторов скорости, пропуск состояния `0xfffe`, missing lookup,
|
ответы объектов. Проверяются равенство и проход срока, переполнение времени
|
||||||
динамическое повторное чтение `0x1002`, ограниченный запас руды, разные веса,
|
и точные `f32`-биты elapsed seconds (`1 → 0x3a83126f`, `1001 → 0x3f8020c5`,
|
||||||
достаточное и недостаточное значение мощности. Это синтетические ArealMap
|
`16777217 → 0x46831270`, `16777219 → 0x46831271`, `u32::MAX → 0x4a83126f`).
|
||||||
объекты; исполнение оригинальной функции проверяет векторы oracle, но не
|
Tick-векторы проверяют нулевой шаг, обе ветви селекторов скорости, пропуск
|
||||||
подменяет сравнение Rust и интеграционную проверку миссии в игре.
|
состояния `0xfffe`, отсутствующий объект, повторное чтение `0x1002`, ограниченный
|
||||||
|
запас руды, порог `0.01`, ветви дефицита/частичного/избыточного предложения и
|
||||||
|
равенство спросу, разные веса и достаточную/недостаточную мощность. Это
|
||||||
|
синтетические ArealMap объекты: oracle исполняет оригинальные scheduler и tick,
|
||||||
|
но не сравнивает паритет Rust и не заменяет проверку полной миссии в игре.
|
||||||
|
|
||||||
Caller oracle проверяет AutoDemo пару `(500.2857, 751.8217)`, четыре режима
|
Caller oracle проверяет AutoDemo пару `(500.2857, 751.8217)`, четыре режима
|
||||||
x87 на tie-векторах, границу `10000` и младший DWORD. AI oracle отдельно
|
x87 на tie-векторах, границу `10000` и младший DWORD. AI oracle отдельно
|
||||||
|
|||||||
@@ -326,8 +326,48 @@ Resource Distributor работает со списком зарегистрир
|
|||||||
записях при игровом x87 control word `0x007f`: проверены добавление, повторный
|
записях при игровом x87 control word `0x007f`: проверены добавление, повторный
|
||||||
ключ, закреплённая запись, нулевая сумма и округление. Это позволяет Rust
|
ключ, закреплённая запись, нулевая сумма и округление. Это позволяет Rust
|
||||||
сохранять начальный список, но пока не подтверждает веса реальных объектов в
|
сохранять начальный список, но пока не подтверждает веса реальных объектов в
|
||||||
живой миссии. Для регулярного шага по-прежнему не хватает интерфейсов ArealMap.
|
живой миссии.
|
||||||
Остальные ограничения собраны в [границах знания](../appendices/knowledge-boundaries.md).
|
|
||||||
|
Узкая часть регулярного шага уже связана с загруженным состоянием движка.
|
||||||
|
`advance_loaded_mission_resources(engine, queue_time_ms)` принимает явную
|
||||||
|
метку времени общей 32-битной очереди. Для каждого видимого Behavior clan он
|
||||||
|
проверяет срок запуска, выполняет два прохода по списку и сохраняет пять
|
||||||
|
итогов: доступную энергию и руду, буфер руды, потребление энергии и потребление
|
||||||
|
руды. В обоих проходах объекты разрешаются заново по исходному TMA ID. Первый
|
||||||
|
проход измеряет поставки и потребность, второй повторно читает представленные
|
||||||
|
профильные и Control-значения перед применением выделения. Нативный код также
|
||||||
|
запрашивает интерфейсы во втором проходе, но динамические getter-callbacks в
|
||||||
|
этом снимке Rust не воспроизводятся.
|
||||||
|
|
||||||
|
Планировщик использует беззнаковое сравнение времени: равенство ненулевому
|
||||||
|
сроку ещё не запускает шаг, а нулевой срок не задерживает его. Переполнение
|
||||||
|
вычитания даёт 32-битную длительность. Затем
|
||||||
|
длительность переводится в секунды с округлением, подтверждённым x87-путём
|
||||||
|
оригинальной DLL. Нативный oracle проверяет его на длинных интервалах, включая
|
||||||
|
значения около границы точного целого `f32`, а также на равенстве и строгом
|
||||||
|
проходе срока. Формула запуска, native vectors и команда воспроизведения
|
||||||
|
описаны в технической главе [script VM](../appendices/script-vm.md).
|
||||||
|
|
||||||
|
Шаг работает только с уже созданными полными объектами миссии и снимками тех
|
||||||
|
профилей и Control-таблиц, чьи запросы и setters представлены в runtime. Он
|
||||||
|
обновляет загруженные `CurrentOre`, поля строк распределителя и поддержанный
|
||||||
|
Control selector `1`; неподдержанное свойство, запрос Control или невозможное
|
||||||
|
арифметическое значение возвращает ошибку. Обновления нескольких кланов, их
|
||||||
|
расписания и RNG фиксируются одной транзакцией: если обязательный запрос
|
||||||
|
позднего клана не разрешён, ранее подготовленные изменения остаются
|
||||||
|
неприменёнными. Приложение пока не вызывает
|
||||||
|
этот API из игрового цикла, а динамические callbacks, которые переносят
|
||||||
|
количество между Control provider-строками, ещё не моделируются. Поэтому это
|
||||||
|
исполняемый загруженный resource step, а не полный симулятор миссии.
|
||||||
|
|
||||||
|
Для настоящих ресурсов AutoDemo тест проходит несколько временных точек:
|
||||||
|
генераторы `ObjectId 6` и `ObjectId 7` читают `Transfer_Power_Out = 10` из
|
||||||
|
`prof_generator.var`; после шага в 500 мс каждый сообщает 5 единиц доступной
|
||||||
|
энергии. Это подтверждает связь загруженного профиля с runtime-расчётом, но не
|
||||||
|
сравнение полной миссии с оригинальной игрой. Native oracle сверяет исходные
|
||||||
|
планировщик и распределитель GOG `Behavior.dll`; техническая процедура и
|
||||||
|
проверяемые векторы описаны в [script VM](../appendices/script-vm.md). Остальные ограничения собраны в
|
||||||
|
[границах знания](../appendices/knowledge-boundaries.md).
|
||||||
|
|
||||||
Первый часто встречающийся table entry с side effect — `Handler(2)` (176
|
Первый часто встречающийся table entry с side effect — `Handler(2)` (176
|
||||||
records в corpus). Он берёт active instruction, разрешает семь slots через
|
records в corpus). Он берёт active instruction, разрешает семь slots через
|
||||||
|
|||||||
Reference in new issue
Block a user