Files
musicmouse/python-backend/musicmouse/simulator/harness.py
Martin Bauer f7a5d24d8d Add backend support for the typing game: curriculum, progress, and reward unlocks
Moves the typing app's lesson plan and progress from client-side YAML/localStorage
into the backend, and adds a reward system that ties passing a lesson to unlocking
part of the music library. Lock state is always recomputed live from curriculum x
progress x the live library, never persisted separately.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-12 21:03:45 +02:00

97 lines
2.6 KiB
Python

"""Assemble the whole app against fake hardware.
This is the real bus, the real device, the real reactions - only the serial link and
VLC are substituted. That is what makes a scenario meaningful: everything between the
tag being read and the LED bytes being written is production code.
"""
from __future__ import annotations
from dataclasses import dataclass
from musicmouse.app import App
from musicmouse.bus import EventBus
from musicmouse.clock import Clock, FakeClock
from musicmouse.config import Config
from musicmouse.devices.mouse import MusicMouseDevice
from musicmouse.library import MusicLibrary
from musicmouse.reactions import register_all
from musicmouse.simulator.driver import SimulatorDriver
from musicmouse.simulator.fake_player import DEFAULT_TRACK_DURATION, FakePlayer
from musicmouse.simulator.fake_transport import FakeTransport
from musicmouse.tippen.runtime import TippenRuntime
__all__ = ["Simulation", "build_simulation"]
@dataclass
class Simulation:
app: App
driver: SimulatorDriver
transport: FakeTransport
player: FakePlayer
clock: Clock
bus: EventBus
async def aclose(self) -> None:
self.player.close()
await self.bus.stop()
async def build_simulation(
config: Config,
*,
clock: Clock | None = None,
track_duration: float = DEFAULT_TRACK_DURATION,
library: MusicLibrary | None = None,
tippen: TippenRuntime | None = None,
) -> Simulation:
bus = EventBus()
await bus.start()
if clock is None:
clock = FakeClock(idle=bus.drain)
transport = FakeTransport()
mouse = MusicMouseDevice(bus, transport, config.tag_map, port="simulated")
transport.attach(mouse.feed)
player = FakePlayer(
bus,
clock=clock,
track_duration=track_duration,
**FakePlayer.volume_kwargs(config.general),
)
if library is None:
library_config = config.general.library
library = await MusicLibrary.build(
library_config.root,
library_config.cache,
frozenset(config.general.audio_extensions),
figure_kinds=config.figure_kinds,
)
app = App(
config=config,
bus=bus,
mouse=mouse,
player=player,
library=library,
playlists=library.figure_playlists(),
clock=clock,
tippen=tippen,
)
register_all(bus, app)
mouse.on_connected()
await bus.drain()
return Simulation(
app=app,
driver=SimulatorDriver(app, transport, clock),
transport=transport,
player=player,
clock=clock,
bus=bus,
)