mini-arcade-core 0.10.0__py3-none-any.whl → 1.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. mini_arcade_core/__init__.py +51 -63
  2. mini_arcade_core/backend/__init__.py +2 -6
  3. mini_arcade_core/backend/backend.py +148 -8
  4. mini_arcade_core/backend/events.py +1 -1
  5. mini_arcade_core/{keymaps/sdl.py → backend/sdl_map.py} +1 -1
  6. mini_arcade_core/engine/__init__.py +0 -0
  7. mini_arcade_core/engine/commands.py +169 -0
  8. mini_arcade_core/engine/game.py +369 -0
  9. mini_arcade_core/engine/render/__init__.py +0 -0
  10. mini_arcade_core/engine/render/packet.py +56 -0
  11. mini_arcade_core/engine/render/pipeline.py +63 -0
  12. mini_arcade_core/engine/render/viewport.py +203 -0
  13. mini_arcade_core/managers/__init__.py +0 -22
  14. mini_arcade_core/managers/cheats.py +71 -240
  15. mini_arcade_core/managers/inputs.py +5 -3
  16. mini_arcade_core/runtime/__init__.py +0 -0
  17. mini_arcade_core/runtime/audio/__init__.py +0 -0
  18. mini_arcade_core/runtime/audio/audio_adapter.py +20 -0
  19. mini_arcade_core/runtime/audio/audio_port.py +36 -0
  20. mini_arcade_core/runtime/capture/__init__.py +0 -0
  21. mini_arcade_core/runtime/capture/capture_adapter.py +143 -0
  22. mini_arcade_core/runtime/capture/capture_port.py +51 -0
  23. mini_arcade_core/runtime/context.py +53 -0
  24. mini_arcade_core/runtime/file/__init__.py +0 -0
  25. mini_arcade_core/runtime/file/file_adapter.py +20 -0
  26. mini_arcade_core/runtime/file/file_port.py +31 -0
  27. mini_arcade_core/runtime/input/__init__.py +0 -0
  28. mini_arcade_core/runtime/input/input_adapter.py +49 -0
  29. mini_arcade_core/runtime/input/input_port.py +31 -0
  30. mini_arcade_core/runtime/input_frame.py +71 -0
  31. mini_arcade_core/runtime/scene/__init__.py +0 -0
  32. mini_arcade_core/runtime/scene/scene_adapter.py +97 -0
  33. mini_arcade_core/runtime/scene/scene_port.py +149 -0
  34. mini_arcade_core/runtime/services.py +35 -0
  35. mini_arcade_core/runtime/window/__init__.py +0 -0
  36. mini_arcade_core/runtime/window/window_adapter.py +90 -0
  37. mini_arcade_core/runtime/window/window_port.py +109 -0
  38. mini_arcade_core/scenes/__init__.py +0 -22
  39. mini_arcade_core/scenes/autoreg.py +1 -1
  40. mini_arcade_core/scenes/registry.py +21 -19
  41. mini_arcade_core/scenes/sim_scene.py +41 -0
  42. mini_arcade_core/scenes/systems/__init__.py +0 -0
  43. mini_arcade_core/scenes/systems/base_system.py +40 -0
  44. mini_arcade_core/scenes/systems/system_pipeline.py +57 -0
  45. mini_arcade_core/sim/__init__.py +0 -0
  46. mini_arcade_core/sim/protocols.py +41 -0
  47. mini_arcade_core/sim/runner.py +222 -0
  48. mini_arcade_core/spaces/__init__.py +0 -12
  49. mini_arcade_core/spaces/d2/__init__.py +0 -30
  50. mini_arcade_core/spaces/d2/boundaries2d.py +10 -1
  51. mini_arcade_core/spaces/d2/collision2d.py +25 -28
  52. mini_arcade_core/spaces/d2/geometry2d.py +18 -0
  53. mini_arcade_core/spaces/d2/kinematics2d.py +2 -8
  54. mini_arcade_core/spaces/d2/physics2d.py +9 -0
  55. mini_arcade_core/ui/__init__.py +0 -26
  56. mini_arcade_core/ui/menu.py +271 -85
  57. mini_arcade_core/utils/__init__.py +10 -0
  58. mini_arcade_core/utils/deprecated_decorator.py +45 -0
  59. mini_arcade_core/utils/logging.py +168 -0
  60. {mini_arcade_core-0.10.0.dist-info → mini_arcade_core-1.0.1.dist-info}/METADATA +1 -1
  61. mini_arcade_core-1.0.1.dist-info/RECORD +66 -0
  62. {mini_arcade_core-0.10.0.dist-info → mini_arcade_core-1.0.1.dist-info}/WHEEL +1 -1
  63. mini_arcade_core/commands.py +0 -84
  64. mini_arcade_core/entity.py +0 -72
  65. mini_arcade_core/game.py +0 -287
  66. mini_arcade_core/keymaps/__init__.py +0 -15
  67. mini_arcade_core/managers/base.py +0 -132
  68. mini_arcade_core/managers/entities.py +0 -38
  69. mini_arcade_core/managers/overlays.py +0 -53
  70. mini_arcade_core/managers/system.py +0 -26
  71. mini_arcade_core/scenes/model.py +0 -34
  72. mini_arcade_core/scenes/runtime.py +0 -29
  73. mini_arcade_core/scenes/scene.py +0 -109
  74. mini_arcade_core/scenes/system.py +0 -69
  75. mini_arcade_core/ui/overlays.py +0 -41
  76. mini_arcade_core-0.10.0.dist-info/RECORD +0 -40
  77. /mini_arcade_core/{keymaps → backend}/keys.py +0 -0
  78. {mini_arcade_core-0.10.0.dist-info → mini_arcade_core-1.0.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,109 @@
1
+ """
2
+ Service interfaces for runtime components.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ from mini_arcade_core.backend import Backend, WindowSettings
8
+ from mini_arcade_core.engine.render.viewport import ViewportMode, ViewportState
9
+
10
+
11
+ class WindowPort:
12
+ """Interface for window-related operations."""
13
+
14
+ backend: Backend
15
+ size: tuple[int, int]
16
+ window_settings_cls: WindowSettings
17
+
18
+ def set_window_size(self, width: int, height: int):
19
+ """
20
+ Set the size of the window.
21
+
22
+ :param width: Width in pixels.
23
+ :type width: int
24
+
25
+ :param height: Height in pixels.
26
+ :type height: int
27
+ """
28
+
29
+ def set_viewport_mode(self, mode: ViewportMode):
30
+ """
31
+ Set the viewport mode for rendering.
32
+
33
+ :param mode: The viewport mode to set.
34
+ :type mode: ViewportMode
35
+ """
36
+
37
+ def get_viewport(self) -> ViewportState:
38
+ """
39
+ Get the current viewport state.
40
+
41
+ :return: The current ViewportState.
42
+ :rtype: ViewportState
43
+ """
44
+
45
+ def screen_to_virtual(self, x: float, y: float) -> tuple[float, float]:
46
+ """
47
+ Convert screen coordinates to virtual coordinates.
48
+
49
+ :param x: X coordinate on the screen.
50
+ :type x: float
51
+
52
+ :param y: Y coordinate on the screen.
53
+ :type y: float
54
+
55
+ :return: Corresponding virtual coordinates (x, y).
56
+ :rtype: tuple[float, float]
57
+ """
58
+
59
+ def set_virtual_resolution(self, width: int, height: int):
60
+ """
61
+ Set the virtual resolution for rendering.
62
+
63
+ :param width: Virtual width in pixels.
64
+ :type width: int
65
+
66
+ :param height: Virtual height in pixels.
67
+ :type height: int
68
+ """
69
+
70
+ def set_title(self, title: str):
71
+ """
72
+ Set the window title.
73
+
74
+ :param title: The new title for the window.
75
+ :type title: str
76
+ """
77
+
78
+ def set_clear_color(self, r: int, g: int, b: int):
79
+ """
80
+ Set the clear color for the window.
81
+
82
+ :param r: Red component (0-255).
83
+ :type r: int
84
+
85
+ :param g: Green component (0-255).
86
+ :type g: int
87
+
88
+ :param b: Blue component (0-255).
89
+ :type b: int
90
+ """
91
+
92
+ def on_window_resized(self, width: int, height: int):
93
+ """
94
+ Handle window resized event.
95
+
96
+ :param width: New width of the window.
97
+ :type width: int
98
+
99
+ :param height: New height of the window.
100
+ :type height: int
101
+ """
102
+
103
+ def get_virtual_size(self) -> tuple[int, int]:
104
+ """
105
+ Get the current virtual resolution size.
106
+
107
+ :return: Tuple of (virtual_width, virtual_height).
108
+ :rtype: tuple[int, int]
109
+ """
@@ -1,22 +0,0 @@
1
- """
2
- Scenes module for Mini Arcade Core.
3
- Provides the base Scene class and related functionality.
4
- """
5
-
6
- from __future__ import annotations
7
-
8
- from .autoreg import register_scene
9
- from .model import SceneModel
10
- from .registry import SceneRegistry
11
- from .runtime import SceneRuntime
12
- from .scene import Scene
13
- from .system import BaseSceneSystem
14
-
15
- __all__ = [
16
- "Scene",
17
- "register_scene",
18
- "SceneRegistry",
19
- "SceneRuntime",
20
- "SceneModel",
21
- "BaseSceneSystem",
22
- ]
@@ -8,7 +8,7 @@ from __future__ import annotations
8
8
  from typing import TYPE_CHECKING, Dict, Type
9
9
 
10
10
  if TYPE_CHECKING:
11
- from . import Scene
11
+ from mini_arcade_core.scenes.scene import Scene
12
12
 
13
13
  _AUTO: Dict[str, Type["Scene"]] = {}
14
14
 
@@ -1,5 +1,5 @@
1
1
  """
2
- Scene registry for mini arcade core.
2
+ SimScene registry for mini arcade core.
3
3
  Allows registering and creating scenes by string IDs.
4
4
  """
5
5
 
@@ -10,11 +10,13 @@ import pkgutil
10
10
  from dataclasses import dataclass
11
11
  from typing import TYPE_CHECKING, Dict, Protocol
12
12
 
13
+ from mini_arcade_core.runtime.context import RuntimeContext
14
+
13
15
  from .autoreg import snapshot
14
16
 
15
17
  if TYPE_CHECKING:
16
- from mini_arcade_core.game import Game
17
- from mini_arcade_core.scenes import Scene
18
+ from mini_arcade_core.engine.commands import CommandQueue
19
+ from mini_arcade_core.sim import SimScene
18
20
 
19
21
 
20
22
  class SceneFactory(Protocol):
@@ -22,7 +24,7 @@ class SceneFactory(Protocol):
22
24
  Protocol for scene factory callables.
23
25
  """
24
26
 
25
- def __call__(self, game: "Game") -> "Scene": ...
27
+ def __call__(self, context: RuntimeContext) -> "SimScene": ...
26
28
 
27
29
 
28
30
  @dataclass
@@ -40,28 +42,28 @@ class SceneRegistry:
40
42
  :param scene_id: The string ID for the scene.
41
43
  :type scene_id: str
42
44
 
43
- :param factory: A callable that creates a Scene instance.
45
+ :param factory: A callable that creates a SimScene instance.
44
46
  :type factory: SceneFactory
45
47
  """
46
48
  self._factories[scene_id] = factory
47
49
 
48
- def register_cls(self, scene_id: str, scene_cls: type["Scene"]):
50
+ def register_cls(self, scene_id: str, scene_cls: type["SimScene"]):
49
51
  """
50
- Register a Scene class under a given scene ID.
52
+ Register a SimScene class under a given scene ID.
51
53
 
52
54
  :param scene_id: The string ID for the scene.
53
55
  :type scene_id: str
54
56
 
55
- :param scene_cls: The Scene class to register.
56
- :type scene_cls: type["Scene"]
57
+ :param scene_cls: The SimScene class to register.
58
+ :type scene_cls: type["SimScene"]
57
59
  """
58
60
 
59
- def return_factory(game: "Game") -> "Scene":
60
- return scene_cls(game)
61
+ def return_factory(context: RuntimeContext) -> "SimScene":
62
+ return scene_cls(context)
61
63
 
62
64
  self.register(scene_id, return_factory)
63
65
 
64
- def create(self, scene_id: str, game: "Game") -> "Scene":
66
+ def create(self, scene_id: str, context: RuntimeContext) -> "SimScene":
65
67
  """
66
68
  Create a scene instance using the registered factory for the given scene ID.
67
69
 
@@ -71,22 +73,22 @@ class SceneRegistry:
71
73
  :param game: The Game instance to pass to the scene factory.
72
74
  :type game: Game
73
75
 
74
- :return: A new Scene instance.
75
- :rtype: Scene
76
+ :return: A new SimScene instance.
77
+ :rtype: SimScene
76
78
 
77
79
  :raises KeyError: If no factory is registered for the given scene ID.
78
80
  """
79
81
  try:
80
- return self._factories[scene_id](game)
82
+ return self._factories[scene_id](context)
81
83
  except KeyError as e:
82
84
  raise KeyError(f"Unknown scene_id={scene_id!r}") from e
83
85
 
84
- def load_catalog(self, catalog: Dict[str, type["Scene"]]):
86
+ def load_catalog(self, catalog: Dict[str, type["SimScene"]]):
85
87
  """
86
- Load a catalog of Scene classes into the registry.
88
+ Load a catalog of SimScene classes into the registry.
87
89
 
88
- :param catalog: A dictionary mapping scene IDs to Scene classes.
89
- :type catalog: Dict[str, type["Scene"]]
90
+ :param catalog: A dictionary mapping scene IDs to SimScene classes.
91
+ :type catalog: Dict[str, type["SimScene"]]
90
92
  """
91
93
  for scene_id, cls in catalog.items():
92
94
  self.register_cls(scene_id, cls)
@@ -0,0 +1,41 @@
1
+ """
2
+ Simulation scene protocol module.
3
+ Defines the SimScene protocol for simulation scenes.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from typing import Protocol, runtime_checkable
9
+
10
+ from mini_arcade_core.engine.render.packet import RenderPacket
11
+ from mini_arcade_core.runtime.input_frame import InputFrame
12
+
13
+
14
+ @runtime_checkable
15
+ class SimScene(Protocol):
16
+ """ "Protocol for a simulation scene in the mini arcade core."""
17
+
18
+ def on_enter(self):
19
+ """Called when the scene is entered."""
20
+
21
+ def on_exit(self):
22
+ """Called when the scene is exited."""
23
+
24
+ def tick(self, input_frame: InputFrame, dt: float):
25
+ """
26
+ Advance the simulation by one tick.
27
+
28
+ :param input_frame: Current input frame.
29
+ :type input_frame: InputFrame
30
+
31
+ :param dt: Delta time since last tick.
32
+ :type dt: float
33
+ """
34
+
35
+ def build_render_packet(self) -> RenderPacket:
36
+ """
37
+ Build the render packet for the current scene state.
38
+
39
+ :return: RenderPacket instance.
40
+ :rtype: RenderPacket
41
+ """
File without changes
@@ -0,0 +1,40 @@
1
+ """
2
+ Protocol for base systems in the mini arcade core.
3
+ Defines the BaseSystem protocol that all systems should implement.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from typing import Generic, Protocol, TypeVar, runtime_checkable
9
+
10
+ # Justification: Type variable name is conventional.
11
+ # pylint: disable=invalid-name
12
+ TSystemContext = TypeVar("TSystemContext")
13
+ # pylint: enable=invalid-name
14
+
15
+
16
+ @runtime_checkable
17
+ class BaseSystem(Protocol, Generic[TSystemContext]):
18
+ """Protocol for a system that operates within a given context."""
19
+
20
+ name: str
21
+ order: int
22
+
23
+ def enabled(self, ctx: TSystemContext) -> bool:
24
+ """
25
+ Determine if the system is enabled in the given context.
26
+
27
+ :param ctx: The system context.
28
+ :type ctx: TSystemContext
29
+
30
+ :return: True if the system is enabled, False otherwise.
31
+ :rtype: bool
32
+ """
33
+
34
+ def step(self, ctx: TSystemContext):
35
+ """
36
+ Perform a single step of the system within the given context.
37
+
38
+ :param ctx: The system context.
39
+ :type ctx: TSystemContext
40
+ """
@@ -0,0 +1,57 @@
1
+ """
2
+ Pipeline for managing and executing systems in order.
3
+ Defines the SystemPipeline dataclass that holds and runs systems.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from dataclasses import dataclass, field
9
+ from typing import Generic, Iterable, List
10
+
11
+ from mini_arcade_core.scenes.systems.base_system import (
12
+ BaseSystem,
13
+ TSystemContext,
14
+ )
15
+
16
+
17
+ @dataclass
18
+ class SystemPipeline(Generic[TSystemContext]):
19
+ """
20
+ Pipeline for managing and executing systems in order.
21
+
22
+ :ivar systems (List[BaseSystem[TSystemContext]]): List of systems in the pipeline.
23
+ """
24
+
25
+ systems: List[BaseSystem[TSystemContext]] = field(default_factory=list)
26
+
27
+ def add(self, system: BaseSystem[TSystemContext]):
28
+ """
29
+ Add a system to the pipeline and sort by order.
30
+
31
+ :param system: The system to add.
32
+ :type system: BaseSystem[TSystemContext]
33
+ """
34
+ self.systems.append(system)
35
+ self.systems.sort(key=lambda s: getattr(s, "order", 0))
36
+
37
+ def extend(self, systems: Iterable[BaseSystem[TSystemContext]]):
38
+ """
39
+ Extend the pipeline with multiple systems.
40
+
41
+ :param systems: An iterable of systems to add.
42
+ :type systems: Iterable[BaseSystem[TSystemContext]]
43
+ """
44
+ for s in systems:
45
+ self.add(s)
46
+
47
+ def step(self, ctx: TSystemContext):
48
+ """
49
+ Execute a step for each system in the pipeline.
50
+
51
+ :param ctx: The system context.
52
+ :type ctx: TSystemContext
53
+ """
54
+ for system in self.systems:
55
+ if hasattr(system, "enabled") and not system.enabled(ctx):
56
+ continue
57
+ system.step(ctx)
File without changes
@@ -0,0 +1,41 @@
1
+ """
2
+ Simulation scene protocol module.
3
+ Defines the SimScene protocol for simulation scenes.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from dataclasses import dataclass
9
+
10
+ from mini_arcade_core.engine.render.packet import RenderPacket
11
+ from mini_arcade_core.runtime.context import RuntimeContext
12
+ from mini_arcade_core.runtime.input_frame import InputFrame
13
+
14
+
15
+ @dataclass
16
+ class SimScene:
17
+ """
18
+ Simulation-first scene protocol.
19
+
20
+ tick() advances the simulation and returns a RenderPacket for this scene.
21
+ """
22
+
23
+ context: RuntimeContext
24
+
25
+ def on_enter(self):
26
+ """Called when the scene is entered."""
27
+
28
+ def on_exit(self):
29
+ """Called when the scene is exited."""
30
+
31
+ def tick(self, input_frame: InputFrame, dt: float) -> RenderPacket:
32
+ """
33
+ Advance the simulation by dt seconds, processing input_frame.
34
+
35
+ :param input_frame: InputFrame with input events for this frame.
36
+ :param dt: Time delta in seconds since the last tick.
37
+
38
+ :return: RenderPacket for this frame.
39
+ :rtype: RenderPacket
40
+ """
41
+ raise NotImplementedError()
@@ -0,0 +1,222 @@
1
+ """
2
+ Simulation runner module.
3
+ Defines the SimRunner class for running simulation scenes.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from dataclasses import dataclass
9
+ from time import perf_counter, sleep
10
+ from typing import Dict, Optional
11
+
12
+ from mini_arcade_core.backend import Backend
13
+ from mini_arcade_core.engine.render.packet import RenderPacket
14
+ from mini_arcade_core.engine.render.pipeline import RenderPipeline
15
+ from mini_arcade_core.runtime.input_frame import InputFrame
16
+ from mini_arcade_core.runtime.scene.scene_port import SceneEntry
17
+ from mini_arcade_core.runtime.services import RuntimeServices
18
+
19
+
20
+ def _neutral_input(frame_index: int, dt: float) -> InputFrame:
21
+ # InputFrame is frozen; create a clean snapshot for non-input scenes.
22
+ return InputFrame(frame_index=frame_index, dt=dt)
23
+
24
+
25
+ def _has_tick(scene: object) -> bool:
26
+ # Avoid isinstance(..., Protocol). Structural check.
27
+ return callable(getattr(scene, "tick", None))
28
+
29
+
30
+ def _has_draw(scene: object) -> bool:
31
+ return callable(getattr(scene, "draw", None))
32
+
33
+
34
+ def _has_update(scene: object) -> bool:
35
+ return callable(getattr(scene, "update", None))
36
+
37
+
38
+ def _has_handle_event(scene: object) -> bool:
39
+ return callable(getattr(scene, "handle_event", None))
40
+
41
+
42
+ @dataclass(frozen=True)
43
+ class SimRunnerConfig:
44
+ """
45
+ Config for sim runner.
46
+
47
+ - record: if True, capture a frame each tick using deterministic naming.
48
+ - run_id: required when record=True.
49
+ - max_frames: optional safety stop (useful for offline sims/tests).
50
+ """
51
+
52
+ fps: int = 60
53
+ record: bool = False
54
+ run_id: str = "run"
55
+ max_frames: Optional[int] = None
56
+ # If True, still forward raw events to the input scene's handle_event (legacy UI / text input).
57
+ forward_events_to_input_scene: bool = True
58
+
59
+
60
+ class SimRunner:
61
+ """
62
+ Simulation-first runner.
63
+
64
+ Uses:
65
+ - services.scenes.update_entries() for ticking (policy-aware)
66
+ - services.scenes.visible_entries() for rendering (opaque-aware)
67
+ - services.scenes.input_entry() for input focus
68
+ """
69
+
70
+ def __init__(
71
+ self,
72
+ backend: Backend,
73
+ services: RuntimeServices,
74
+ *,
75
+ render_pipeline: Optional[RenderPipeline] = None,
76
+ ):
77
+ if services.scenes is None:
78
+ raise ValueError("RuntimeServices.scenes must be set")
79
+ if services.input is None:
80
+ raise ValueError("RuntimeServices.input must be set")
81
+ if services.capture is None:
82
+ # recording is optional, but capture port should exist in v1
83
+ raise ValueError("RuntimeServices.capture must be set")
84
+
85
+ self.backend = backend
86
+ self.services = services
87
+ self.pipeline = render_pipeline or RenderPipeline()
88
+
89
+ # cache: scene object id -> last RenderPacket
90
+ self._packets: Dict[int, RenderPacket] = {}
91
+
92
+ self._running: bool = False
93
+
94
+ def stop(self):
95
+ """
96
+ Stop the simulation loop.
97
+ """
98
+ self._running = False
99
+
100
+ # TODO: Solve too-many-statements, too-many-branches and too-many-locals
101
+ # warning later
102
+ # Justification: The run method orchestrates multiple complex steps in the
103
+ # simulation loop.
104
+ # pylint: disable=too-many-statements,too-many-branches,too-many-locals
105
+ def run(
106
+ self, initial_scene_id: str, *, cfg: Optional[SimRunnerConfig] = None
107
+ ):
108
+ """
109
+ Run the simulation loop starting from the initial scene.
110
+
111
+ :param initial_scene_id: ID of the initial scene to load.
112
+ :type initial_scene_id: str
113
+
114
+ :param cfg: Optional SimRunnerConfig instance.
115
+ :type cfg: Optional[SimRunnerConfig]
116
+ """
117
+ cfg = cfg or SimRunnerConfig()
118
+
119
+ scenes = self.services.scenes
120
+ assert scenes is not None
121
+
122
+ # start at initial scene
123
+ scenes.change(initial_scene_id)
124
+
125
+ self._running = True
126
+ target_dt = 1.0 / cfg.fps if cfg.fps > 0 else 0.0
127
+
128
+ last_time = perf_counter()
129
+ frame_index = 0
130
+
131
+ while self._running:
132
+ if cfg.max_frames is not None and frame_index >= cfg.max_frames:
133
+ break
134
+
135
+ now = perf_counter()
136
+ dt = now - last_time
137
+ last_time = now
138
+
139
+ # 1) poll events -> build InputFrame
140
+ events = list(self.backend.poll_events())
141
+ input_frame = self.services.input.build(events, frame_index, dt)
142
+
143
+ # 2) OS quit request is a hard stop
144
+ if input_frame.quit:
145
+ # use ScenePort.quit so Game.quit can be centralized there
146
+ scenes.quit()
147
+ break
148
+
149
+ # 3) input focus scene (top of visible stack)
150
+ input_entry: Optional[SceneEntry] = scenes.input_entry()
151
+ if input_entry is None:
152
+ break
153
+
154
+ # Optional legacy: forward raw events to focused scene
155
+ if cfg.forward_events_to_input_scene and _has_handle_event(
156
+ input_entry.scene
157
+ ):
158
+ for ev in events:
159
+ input_entry.scene.handle_event(ev)
160
+
161
+ # 4) tick/update policy-aware scenes
162
+ for entry in scenes.update_entries():
163
+ scene_obj = entry.scene
164
+ scene_key = id(scene_obj)
165
+
166
+ # Only the input-focused scene receives the actual input_frame
167
+ effective_input = (
168
+ input_frame
169
+ if entry is input_entry
170
+ else _neutral_input(frame_index, dt)
171
+ )
172
+
173
+ if _has_tick(scene_obj):
174
+ packet = scene_obj.tick(effective_input, dt) # SimScene
175
+ if not isinstance(packet, RenderPacket):
176
+ raise TypeError(
177
+ f"{entry.scene_id}.tick() must "
178
+ f"return RenderPacket, got {type(packet)!r}"
179
+ )
180
+ self._packets[scene_key] = packet
181
+ elif _has_update(scene_obj):
182
+ # legacy scene; keep packet cache if any
183
+ scene_obj.update(dt)
184
+
185
+ # 5) render visible stack (policy-aware)
186
+ self.backend.begin_frame()
187
+
188
+ for entry in scenes.visible_entries():
189
+ scene_obj = entry.scene
190
+ scene_key = id(scene_obj)
191
+
192
+ if _has_tick(scene_obj):
193
+ packet = self._packets.get(scene_key)
194
+ # If first frame and no packet exists yet, do a dt=0 tick to bootstrap
195
+ if packet is None:
196
+ packet = scene_obj.tick(
197
+ _neutral_input(frame_index, 0.0), 0.0
198
+ )
199
+ self._packets[scene_key] = packet
200
+ self.pipeline.draw_packet(self.backend, packet)
201
+
202
+ elif _has_draw(scene_obj):
203
+ # legacy scene draw path
204
+ scene_obj.draw(self.backend)
205
+
206
+ self.backend.end_frame()
207
+
208
+ # 6) deterministic capture (optional)
209
+ if cfg.record:
210
+ # label could be "frame" or something semantic later
211
+ self.services.capture.screenshot_sim(
212
+ cfg.run_id, frame_index, label="frame"
213
+ )
214
+
215
+ # 7) frame pacing
216
+ if target_dt > 0 and dt < target_dt:
217
+ sleep(target_dt - dt)
218
+
219
+ frame_index += 1
220
+
221
+ # cleanup scenes
222
+ scenes.clean()
@@ -1,12 +0,0 @@
1
- """
2
- Mini Arcade Core Spaces Module
3
- This module contains different space definitions for the Mini Arcade Core framework.
4
- """
5
-
6
- from __future__ import annotations
7
-
8
- from . import d2
9
-
10
- __all__ = [
11
- "d2",
12
- ]
@@ -1,30 +0,0 @@
1
- """
2
- Two-dimensional utilities and components for Mini Arcade Core.
3
- Includes 2D entities, boundaries, and physics.
4
- """
5
-
6
- from __future__ import annotations
7
-
8
- from .boundaries2d import (
9
- RectKinematic,
10
- RectSprite,
11
- VerticalBounce,
12
- VerticalWrap,
13
- )
14
- from .collision2d import RectCollider
15
- from .geometry2d import Bounds2D, Position2D, Size2D
16
- from .kinematics2d import KinematicData
17
- from .physics2d import Velocity2D
18
-
19
- __all__ = [
20
- "Bounds2D",
21
- "Position2D",
22
- "Size2D",
23
- "KinematicData",
24
- "Velocity2D",
25
- "RectCollider",
26
- "RectKinematic",
27
- "RectSprite",
28
- "VerticalBounce",
29
- "VerticalWrap",
30
- ]