Skip to content

Emulator as a reconstructable set of identifiable devices #38

Description

@kosarev

The emulator is fundamentally a set of devices. Restoring it from a saved state should mean recreating the same set of devices and loading each device's state into it — constructing the emulator from the config, not loading state into a pre-existing instance. This is the foundation under #37 (settings are one slice of per-device state) and the long-term session vision. Full design notes are kept locally; summary below.

Container and boundary

  • A top-level Emulator owns the device set, its construction, the Dispatcher, and the run loop (lifted out of Spectrum, which shrinks to a pure core device).
  • Emulator and Dispatcher are distinct: devices are handed the Dispatcher (already the on_event argument), never the Emulator, so device independence is structural — a device cannot reach up, only notify/query peers.

Identity and registration

  • Device identity = type tag + a container-assigned ordinal / optional name; uniqueness is the container's responsibility. id(self) is never persisted.
  • DataRecord/Device auto-registration via __init_subclass__, keyed on the bare class name, uniqueness asserted at declaration (so plugins/custom code are restorable).

loading -> config -> run

  • The config is a Session DataRecord holding the device set as (type, name, state) plus rollback history.
  • File loaders (.z80/.rzx/.sna) become config transformers contributing device slices; a reset/model-switch is just amending the config; a rollback is loading a past config.
  • UnifiedSnapshot becomes SpectrumState, one device slice among many.

Settings and persistence

  • Settings are the first slice of per-device state: a GetSettings combining query + a SetOption apply, one address (device, option) for live read / live write / persisted key, each option carrying a default and a scope (content vs host).
  • Environment coupling (global prefs, session save/load, filesystem, raw sound output) becomes opt-in devices, so an API-built emulator is hermetic and reproducible.

Models

  • A uniform device set across all models; a present device need not actively participate (AY present-but-inactive on a 48K).
  • The model is broadcast as a descriptor (a DataRecord of facts/properties) via an extended EmulatorReset(model); each device self-configures by reading the properties it cares about, never the model name — so adding e.g. a 16K model touches no device.

Build order — demand-driven, demo-paired (each fundamental landed with the small use case that demonstrates it):

  • Arc 1: expose SoundDevice's produced samples -> a test that reads them (separate ticket).
  • Arc 2: GetSettings/SetOption (broadcast by option name) -> a Settings menu item for speed (feeds Settings UI: surface the speed, fps and latency knobs (GUI panel, CLI, persistence) #37).
  • Arc 3: a device serialises/restores its options DataRecord -> speed survives a restart (seed of session.zx).
  • Then identity, the registry, the Emulator/Dispatcher split, and the model descriptor arrive as concrete use cases pull them in.

Related: #19 (split into devices), #18 (public API), #37 (settings UI), #23 (playback as a device).

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions