## ADDED Requirements ### Requirement: Three-tier execution model WhiteMagic SHALL provide three execution strategies selected by payload safety: `RemoteThreadExecutor` (via `CreateRemoteThread`), `MainThreadPump` (work marshalled onto the target's own thread), and `InProcessInvoker` (direct native-delegate calls when injected in-process). #### Scenario: strategies are distinct and selectable - **WHEN** a caller chooses an execution strategy - **THEN** each of remote-thread, main-thread-pump, and in-process MUST be individually invokable #### Scenario: main-thread pump is the documented default for game state - **WHEN** documentation or API guidance describes calling functions that touch single-thread-affinity process state - **THEN** it MUST direct callers to the main-thread pump, not `CreateRemoteThread` ### Requirement: Remote-thread execution for thread-agnostic payloads `RemoteThreadExecutor` SHALL create a remote thread at a target address using a calling-convention-aware stub, wait for completion, and return the typed exit value. Its documentation MUST state that it is safe only for thread-agnostic payloads. #### Scenario: execute with parameters and convention - **WHEN** `Execute(addr, CallConvention.Cdecl, arg1, arg2)` is called on a safe self-contained function - **THEN** the target MUST be called with the arguments laid out per cdecl and the typed return value returned #### Scenario: parameters marshalled and freed - **WHEN** a `string` or struct parameter is passed to `Execute` - **THEN** it MUST be allocated in the remote process, passed by pointer, and freed after the call completes #### Scenario: no process open - **WHEN** `Execute` is called with no process open - **THEN** it MUST fail deterministically rather than crash ### Requirement: Crash-safe main-thread pump `MainThreadPump` SHALL install a hook on a per-frame function in the target and, each time that function runs, drain a thread-safe queue of work items, executing each on the target's own thread and returning its result or exception to the requesting caller. #### Scenario: work runs on the hooked thread - **WHEN** a work item is queued and the hooked per-frame function next executes - **THEN** the work item MUST run in the context of the thread that calls the per-frame function #### Scenario: result returned to caller - **WHEN** a caller queues a function returning a value and awaits its completion - **THEN** the caller MUST receive the returned value #### Scenario: exception propagated, pump survives - **WHEN** a queued work item throws - **THEN** the exception MUST be surfaced to the requesting caller AND subsequent queued items MUST still be processed #### Scenario: uninstall restores the frame function - **WHEN** the pump is disposed - **THEN** the hooked per-frame function MUST be restored to its original bytes ### Requirement: In-process delegate invocation `InProcessInvoker` SHALL convert a function address to a typed managed delegate and call it directly, without creating a thread or crossing a thread boundary. #### Scenario: call as delegate - **WHEN** `CreateFunction(addr)` is called in-process and the returned delegate is invoked - **THEN** the native function at `addr` MUST be called directly on the current thread with the delegate's marshalled arguments