Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AUDIT.md
Original file line number Diff line number Diff line change
Expand Up @@ -699,7 +699,7 @@ The roadmap below integrates audit findings, EARS requirements, and TLA+ conform
|---|------|---------|-----|-----------------|--------|
| 8 | Extend `in_step` guard to all mutating APIs | M1 | REQ-EX-004 | ReentrancyMinimal | Medium |
| 9 | Sync framebuffer from engine (replace synthetic test pattern) | H8 | REQ-CP-001 | CaptureMinimal | Medium |
| 10 | Resolve dual runtime path divergence (`dosbox_step` vs `dosbox_lib_step_cycles`) | M10 | REQ-EX-001 | — | Medium |
| 10 | ~~Resolve dual runtime path divergence (`dosbox_step` vs `dosbox_lib_step_cycles`)~~ **RESOLVED** (REQ-LC-005) | M10 | REQ-EX-001 | — | Medium |
| 11 | Guard `dosbox_lib_get_context_ptr()` return in `legends_step_cycles` | M11 | REQ-ER-003 | ErrorModel | Small |
| 12 | Exception-safe callback invocation at C ABI boundary | M6 | REQ-ER-004 | — | Small |

Expand Down
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -696,6 +696,7 @@ if(LEGENDS_BUILD_TESTS)
tests/unit/test_fullscreen_toggle.cpp
tests/unit/test_joystick_mapper.cpp
tests/unit/test_shader_presets.cpp
tests/unit/test_shader_size_limit.cpp
tests/unit/test_ai_config.cpp
tests/unit/test_ai_screen_context.cpp
tests/unit/test_ai_http_client.cpp
Expand Down
2 changes: 1 addition & 1 deletion REQUIREMENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ The system shall route all step execution through the CPU bridge (`dosbox::execu
|---|---|
| Source | AUDIT M10 (dual runtime path), H3 (MachineContext.step stub) |
| Evidence | `dosbox_context.cpp:920` routes through stub; `dosbox_library.cpp:371` routes through bridge |
| Status | **GAP** — `dosbox_step()` routes through TODO stub, `dosbox_lib_step_cycles()` uses real bridge |
| Status | **RESOLVED** — `dosbox_step()`, `DOSBoxContext::step()`, `MachineContext::step()`, and `MachineContext::run()` removed (REQ-LC-005). Single execution path via `dosbox_lib_step_cycles()`. |

### REQ-LC-006 No Phantom Definitions [Ubiquitous]

Expand Down
70 changes: 4 additions & 66 deletions engine/include/aibox/machine_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,6 @@

namespace aibox {

// Forward declarations for subsystem types used in MachineContext interface.
// These are intentionally unimplemented — initialization delegates to DOSBox-X engine bridge.
class VgaContext;
class DosKernel;
class PicController;
class PitTimer;
class KeyboardController;
class MouseController;
class SoundSubsystem;

// ─────────────────────────────────────────────────────────────────────────────
// Machine State
// ─────────────────────────────────────────────────────────────────────────────
Expand Down Expand Up @@ -165,13 +155,13 @@ struct MachineConfig {
* ## Lifecycle
* 1. Create: `MachineContext ctx(config)`
* 2. Initialize: `ctx.initialize()`
* 3. Run: `ctx.step(ms)` or `ctx.run()`
* 3. Run: via `dosbox_lib_step_cycles()` (production path)
* 4. Shutdown: `ctx.shutdown()`
*
* ## State Machine
* @verbatim
* Created -> [initialize] -> Initialized
* Initialized -> [step/run] -> Running
* Initialized -> [dosbox_lib_step_cycles] -> Running
* Running -> [pause] -> Paused
* Running -> [stop] -> Stopped
* Paused -> [resume] -> Running
Expand All @@ -194,9 +184,7 @@ struct MachineConfig {
* return;
* }
*
* while (ctx.state() == MachineState::Running) {
* ctx.step(10); // Run 10ms
* }
* // Execution is driven by dosbox_lib_step_cycles() (production path)
*
* ctx.shutdown();
* @endcode
Expand Down Expand Up @@ -255,37 +243,10 @@ class MachineContext {
*/
Result<void> initialize();

/**
* @brief Execute emulation for specified duration.
*
* Executes the specified number of milliseconds of emulated
* time. The actual execution time depends on host performance.
*
* @param ms Milliseconds of emulated time to execute
* @return Result with actual steps executed or error
*
* @pre state() == Initialized, Running, or Paused
* @post state() == Running (or Stopped if stop requested)
*/
Result<uint32_t> step(uint32_t ms);

/**
* @brief Run until stop requested.
*
* Blocks until request_stop() is called or error occurs.
* Suitable for simple use cases without external event loop.
*
* @return Result indicating completion reason
*
* @pre state() == Initialized
* @post state() == Stopped
*/
Result<void> run();

/**
* @brief Request stop from another thread.
*
* Thread-safe. Sets flag checked by run/step loop.
* Thread-safe. Sets flag checked by the execution loop.
* The emulator will stop at the next safe point.
*
* @note This is the ONLY thread-safe method.
Expand Down Expand Up @@ -351,15 +312,6 @@ class MachineContext {
/** @brief DMA subsystem (nullptr until initialized) */
std::unique_ptr<DmaSubsystem> dma;

// Future subsystems (nullptr until implemented)
// std::unique_ptr<VgaContext> vga;
// std::unique_ptr<DosKernel> dos;
// std::unique_ptr<PicController> pic;
// std::unique_ptr<PitTimer> pit;
// std::unique_ptr<KeyboardController> keyboard;
// std::unique_ptr<MouseController> mouse;
// std::unique_ptr<SoundSubsystem> sound;

// ─────────────────────────────────────────────────────────────────────────
// State Queries
// ─────────────────────────────────────────────────────────────────────────
Expand Down Expand Up @@ -453,14 +405,7 @@ class MachineContext {
None,
Memory,
Cpu,
Pic,
Pit,
Dma,
Vga,
Input,
Sound,
Dos,
Bios,
Complete
};

Expand All @@ -472,14 +417,7 @@ class MachineContext {

Result<void> init_memory();
Result<void> init_cpu();
Result<void> init_pic();
Result<void> init_pit();
Result<void> init_dma();
Result<void> init_vga();
Result<void> init_input();
Result<void> init_sound();
Result<void> init_dos();
Result<void> init_bios();

/**
* @brief Cleanup subsystems down to specified stage.
Expand Down
28 changes: 13 additions & 15 deletions engine/include/dosbox/dosbox_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@

#include <cstdint>
#include <memory>
#include <mutex>

#ifdef __cplusplus
extern "C" {
Expand Down Expand Up @@ -82,22 +83,13 @@ dosbox_handle_t dosbox_create(const dosbox_config* config);
/**
* @brief Initialize a created instance.
*
* Must be called before step/run.
* Must be called before run.
*
* @param handle Instance handle
* @return DOSBOX_OK on success, error code on failure
*/
int dosbox_init(dosbox_handle_t handle);

/**
* @brief Execute emulation for specified duration.
*
* @param handle Instance handle
* @param ms Milliseconds of emulated time
* @return DOSBOX_OK on success, error code on failure
*/
int dosbox_step(dosbox_handle_t handle, uint32_t ms);

/**
* @brief Pause execution.
*
Expand Down Expand Up @@ -397,6 +389,7 @@ struct MixerState {
MixerState& operator=(const MixerState&) = delete;

MixerState(MixerState&& o) noexcept
// mutex intentionally not moved — destination gets a fresh default-constructed mutex
: freq(o.freq), blocksize(o.blocksize)
, mastervol{o.mastervol[0], o.mastervol[1]}
, recordvol{o.recordvol[0], o.recordvol[1]}
Expand All @@ -415,6 +408,7 @@ struct MixerState {
{}

MixerState& operator=(MixerState&& o) noexcept {
// mutex intentionally not assigned — each instance owns its own mutex
freq = o.freq; blocksize = o.blocksize;
mastervol[0] = o.mastervol[0]; mastervol[1] = o.mastervol[1];
recordvol[0] = o.recordvol[0]; recordvol[1] = o.recordvol[1];
Expand All @@ -433,6 +427,14 @@ struct MixerState {
return *this;
}

// ─────────────────────────────────────────────────────────────────────────
// Synchronization
// Acquired before any other lock in the system (mutex ordering rule).
// mutable so const methods (hash_into) can lock it.
// ─────────────────────────────────────────────────────────────────────────

mutable std::mutex mutex;

// ─────────────────────────────────────────────────────────────────────────
// Core Configuration (from mixer struct, lines 94-95)
// Set during init, read-only during execution
Expand Down Expand Up @@ -497,6 +499,7 @@ struct MixerState {
* @brief Reset to initial state.
*/
void reset() noexcept {
std::lock_guard<std::mutex> lock(mutex);
freq = 44100;
blocksize = 1024;
mastervol[0] = mastervol[1] = 1.0f;
Expand Down Expand Up @@ -1592,11 +1595,6 @@ class DOSBoxContext {
*/
[[nodiscard]] Result<void> initialize();

/**
* @brief Execute emulation for specified duration.
*/
[[nodiscard]] Result<uint32_t> step(uint32_t ms);

/**
* @brief Pause execution.
*/
Expand Down
35 changes: 28 additions & 7 deletions engine/include/dosbox/engine_state.h
Original file line number Diff line number Diff line change
Expand Up @@ -246,9 +246,30 @@ struct EngineStateCpu {
uint8_t nmi_active; ///< CPU_NMI_active
uint8_t nmi_pending; ///< CPU_NMI_pending
uint8_t halted; ///< CPU in HLT state
uint8_t _pad[6];

// CPU General Purpose Registers (REQ-SR-002)
uint32_t reg_eax = 0;
uint32_t reg_ecx = 0;
uint32_t reg_edx = 0;
uint32_t reg_ebx = 0;
uint32_t reg_esp = 0;
uint32_t reg_ebp = 0;
uint32_t reg_esi = 0;
uint32_t reg_edi = 0;
uint32_t reg_eip = 0;
uint32_t reg_eflags = 0;

// Segment Registers
uint16_t seg_cs = 0;
uint16_t seg_ds = 0;
uint16_t seg_es = 0;
uint16_t seg_fs = 0;
uint16_t seg_gs = 0;
uint16_t seg_ss = 0;

uint8_t _pad[10];
};
static_assert(sizeof(EngineStateCpu) == 96, "EngineStateCpu must be 96 bytes");
static_assert(sizeof(EngineStateCpu) == 152, "EngineStateCpu must be 152 bytes");

// ═══════════════════════════════════════════════════════════════════════════════
// Memory State Section [V2]
Expand Down Expand Up @@ -372,7 +393,7 @@ static_assert(sizeof(EngineStateDos) == 20, "EngineStateDos must be 20 bytes");
* @brief V5 extension header, appended after V4 data at offset ENGINE_STATE_SIZE_V4.
*
* V4 loaders reject V5 data via version check. V5 loaders read V4 data
* normally, then read this extension header at offset 680.
* normally, then read this extension header at offset 736.
*/
struct EngineStateV5ExtHeader {
uint32_t ext_magic; ///< ENGINE_STATE_V5_EXT_MAGIC
Expand Down Expand Up @@ -418,7 +439,7 @@ constexpr uint16_t V5_SUBTAG_VRAM = 4; ///< VGA VRAM contents (zero-RLE comp
constexpr uint16_t V5_BLOCK_FLAG_COMPRESSED = 0x0001; ///< Block is zero-RLE compressed

/**
* @brief V5 sub-block directory header, placed at offset 792 (after CpuGpr).
* @brief V5 sub-block directory header, placed at offset 848 (after CpuGpr).
*
* Contains magic and entry count. Unknown tags are skipped via offset+size.
*/
Expand Down Expand Up @@ -614,7 +635,7 @@ constexpr size_t ENGINE_STATE_SIZE_V3 =
sizeof(EngineStateCpu) +
sizeof(EngineStateMemory);

static_assert(ENGINE_STATE_SIZE_V3 == 544, "V3 size must be 544 bytes");
static_assert(ENGINE_STATE_SIZE_V3 == 600, "V3 size must be 600 bytes");

/**
* @brief Total size for V4 engine state (backward compat baseline).
Expand All @@ -630,7 +651,7 @@ constexpr size_t ENGINE_STATE_SIZE_V4 =
sizeof(EngineStateVga) +
sizeof(EngineStateDos);

static_assert(ENGINE_STATE_SIZE_V4 == 680, "ENGINE_STATE_SIZE_V4 must be 680 bytes");
static_assert(ENGINE_STATE_SIZE_V4 == 736, "ENGINE_STATE_SIZE_V4 must be 736 bytes");

/**
* @brief Minimum V5 state size (V4 + GPR extension, no sub-block blobs).
Expand All @@ -643,7 +664,7 @@ constexpr size_t ENGINE_STATE_SIZE_V5_BASE =
sizeof(EngineStateV5ExtHeader) +
sizeof(EngineStateCpuGpr);

static_assert(ENGINE_STATE_SIZE_V5_BASE == 792, "ENGINE_STATE_SIZE_V5_BASE must be 792 bytes");
static_assert(ENGINE_STATE_SIZE_V5_BASE == 848, "ENGINE_STATE_SIZE_V5_BASE must be 848 bytes");

/// @deprecated Use ENGINE_STATE_SIZE_V5_BASE for compile-time minimum.
/// Actual V5 size is dynamic — query via dosbox_lib_save_state(nullptr).
Expand Down
Loading
Loading