sonartk 0.1__tar.gz

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 (67) hide show
  1. sonartk-0.1/LICENSE +21 -0
  2. sonartk-0.1/PKG-INFO +426 -0
  3. sonartk-0.1/pyproject.toml +79 -0
  4. sonartk-0.1/readme.md +388 -0
  5. sonartk-0.1/setup.cfg +4 -0
  6. sonartk-0.1/src/sonartk/__init__.py +34 -0
  7. sonartk-0.1/src/sonartk/map_builder/__init__.py +14 -0
  8. sonartk-0.1/src/sonartk/map_builder/map_2d/__init__.py +25 -0
  9. sonartk-0.1/src/sonartk/map_builder/map_2d/direction_tools.py +29 -0
  10. sonartk-0.1/src/sonartk/map_builder/map_2d/map_2d.py +257 -0
  11. sonartk-0.1/src/sonartk/map_builder/map_2d/map_loader.py +33 -0
  12. sonartk-0.1/src/sonartk/map_builder/map_2d/map_object/__init__.py +4 -0
  13. sonartk-0.1/src/sonartk/map_builder/map_2d/map_object/character.py +15 -0
  14. sonartk-0.1/src/sonartk/map_builder/map_2d/map_object/map_object.py +11 -0
  15. sonartk-0.1/src/sonartk/map_builder/map_2d/map_queries.py +1330 -0
  16. sonartk-0.1/src/sonartk/map_builder/map_2d/map_tile.py +37 -0
  17. sonartk-0.1/src/sonartk/map_builder/map_2d/map_tools.py +56 -0
  18. sonartk-0.1/src/sonartk/map_builder/map_2d/parser/__init__.py +5 -0
  19. sonartk-0.1/src/sonartk/map_builder/map_2d/parser/csv_parser.py +38 -0
  20. sonartk-0.1/src/sonartk/map_builder/map_2d/parser/json_parser.py +36 -0
  21. sonartk-0.1/src/sonartk/map_builder/map_2d/parser/map_parser.py +33 -0
  22. sonartk-0.1/src/sonartk/map_builder/map_2d/parser/stop_parsing_exception.py +2 -0
  23. sonartk-0.1/src/sonartk/orchestration/__init__.py +76 -0
  24. sonartk-0.1/src/sonartk/orchestration/audio_controls.py +66 -0
  25. sonartk-0.1/src/sonartk/orchestration/audio_lifecycle.py +24 -0
  26. sonartk-0.1/src/sonartk/orchestration/game_builder.py +169 -0
  27. sonartk-0.1/src/sonartk/orchestration/input_gate.py +17 -0
  28. sonartk-0.1/src/sonartk/orchestration/map_object_collection.py +183 -0
  29. sonartk-0.1/src/sonartk/orchestration/map_sound_navigation.py +384 -0
  30. sonartk-0.1/src/sonartk/orchestration/message_action_state.py +91 -0
  31. sonartk-0.1/src/sonartk/orchestration/proximity_audio.py +134 -0
  32. sonartk-0.1/src/sonartk/orchestration/scene_audio_state.py +100 -0
  33. sonartk-0.1/src/sonartk/py.typed +0 -0
  34. sonartk-0.1/src/sonartk/sound/__init__.py +10 -0
  35. sonartk-0.1/src/sonartk/sound/openal_lite/__init__.py +39 -0
  36. sonartk-0.1/src/sonartk/sound/openal_lite/openal.py +5402 -0
  37. sonartk-0.1/src/sonartk/sound/player_pool.py +93 -0
  38. sonartk-0.1/src/sonartk/sound/sound_manager.py +473 -0
  39. sonartk-0.1/src/sonartk/sound/sound_pool.py +158 -0
  40. sonartk-0.1/src/sonartk/ui/__init__.py +22 -0
  41. sonartk-0.1/src/sonartk/ui/element/__init__.py +21 -0
  42. sonartk-0.1/src/sonartk/ui/element/button.py +36 -0
  43. sonartk-0.1/src/sonartk/ui/element/checkbox.py +58 -0
  44. sonartk-0.1/src/sonartk/ui/element/element.py +114 -0
  45. sonartk-0.1/src/sonartk/ui/element/grid.py +274 -0
  46. sonartk-0.1/src/sonartk/ui/element/menu.py +239 -0
  47. sonartk-0.1/src/sonartk/ui/element/text_box.py +742 -0
  48. sonartk-0.1/src/sonartk/ui/element/text_label.py +33 -0
  49. sonartk-0.1/src/sonartk/ui/element/toggle_button.py +64 -0
  50. sonartk-0.1/src/sonartk/ui/focusable_container.py +26 -0
  51. sonartk-0.1/src/sonartk/ui/screen/__init__.py +5 -0
  52. sonartk-0.1/src/sonartk/ui/screen/container_screen.py +65 -0
  53. sonartk-0.1/src/sonartk/ui/screen/dialog.py +68 -0
  54. sonartk-0.1/src/sonartk/ui/screen/screen.py +116 -0
  55. sonartk-0.1/src/sonartk/ui/ui_component.py +80 -0
  56. sonartk-0.1/src/sonartk/ui/window.py +411 -0
  57. sonartk-0.1/src/sonartk/util/__init__.py +32 -0
  58. sonartk-0.1/src/sonartk/util/key_handler.py +429 -0
  59. sonartk-0.1/src/sonartk/util/speech_manager.py +234 -0
  60. sonartk-0.1/src/sonartk/util/state.py +107 -0
  61. sonartk-0.1/src/sonartk/util/state_machine.py +139 -0
  62. sonartk-0.1/src/sonartk.egg-info/PKG-INFO +426 -0
  63. sonartk-0.1/src/sonartk.egg-info/SOURCES.txt +66 -0
  64. sonartk-0.1/src/sonartk.egg-info/dependency_links.txt +1 -0
  65. sonartk-0.1/src/sonartk.egg-info/requires.txt +24 -0
  66. sonartk-0.1/src/sonartk.egg-info/top_level.txt +1 -0
  67. sonartk-0.1/test/test_package_boundaries.py +119 -0
sonartk-0.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020 Timothy Breitenfeldt
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
sonartk-0.1/PKG-INFO ADDED
@@ -0,0 +1,426 @@
1
+ Metadata-Version: 2.4
2
+ Name: sonartk
3
+ Version: 0.1
4
+ Summary: A library used to create audio games built on top of Pyglet.
5
+ Author-email: Timothy Breitenfeldt <timothyjb310@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: repository, https://github.com/tbreitenfeldt/sonar-tk
8
+ Project-URL: bugtracker, https://github.com/tbreitenfeldt/sonar-tk/issues
9
+ Keywords: development,accessibility,screen reader,sonartk,sonar tk
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.13
12
+ Classifier: Operating System :: OS Independent
13
+ Requires-Python: >=3.13
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENSE
16
+ Requires-Dist: accessible_output2
17
+ Requires-Dist: pyglet
18
+ Requires-Dist: pyperclip
19
+ Requires-Dist: ijson
20
+ Requires-Dist: pyogg
21
+ Provides-Extra: test
22
+ Requires-Dist: build; extra == "test"
23
+ Requires-Dist: pytest; extra == "test"
24
+ Requires-Dist: pytest-mock; extra == "test"
25
+ Requires-Dist: pytest-cov; extra == "test"
26
+ Provides-Extra: dev
27
+ Requires-Dist: mypy; extra == "dev"
28
+ Requires-Dist: flake8; extra == "dev"
29
+ Requires-Dist: black; extra == "dev"
30
+ Requires-Dist: pre-commit; extra == "dev"
31
+ Requires-Dist: twine; extra == "dev"
32
+ Provides-Extra: docs
33
+ Requires-Dist: mkdocs==1.6.1; extra == "docs"
34
+ Requires-Dist: mkdocs-material==9.5.47; extra == "docs"
35
+ Requires-Dist: mkdocstrings==1.0.4; extra == "docs"
36
+ Requires-Dist: mkdocstrings-python==2.0.3; extra == "docs"
37
+ Dynamic: license-file
38
+
39
+ # Sonar TK
40
+
41
+ Sonar TK is a Python library for creating audio-first games and accessible,
42
+ screen-reader-oriented interactive applications.
43
+
44
+ The project is organized into focused packages:
45
+
46
+ - ui: windowing, focus, screens, and interactive UI elements.
47
+ - map_builder: 2D map loading, tiles, map objects, and parsers.
48
+ - sound: sound playback and OpenAL integration helpers.
49
+ - util: speech, key handling, shared state/state machine primitives, and
50
+ core utility types.
51
+ - orchestration: optional higher-level coordination helpers for composing
52
+ ui + map_builder + sound into game flows.
53
+
54
+ For common game composition, Sonar TK also provides a top-level builder API.
55
+
56
+ If you only need core primitives (for example, UI-only or UI+sound projects),
57
+ you can ignore `orchestration` entirely.
58
+
59
+ ## Installation
60
+
61
+ Install from PyPI:
62
+
63
+ ```bash
64
+ pip install sonartk
65
+ ```
66
+
67
+ ## Supported Python Versions
68
+
69
+ Sonar TK currently requires Python 3.13 or newer.
70
+
71
+ ## Top-Level API
72
+
73
+ The top-level package exposes high-level entry points:
74
+
75
+ ```python
76
+ from sonartk import (
77
+ Window,
78
+ MapGridGameBuilder,
79
+ BuiltMapGridGame,
80
+ MapSoundNavigationController,
81
+ )
82
+ ```
83
+
84
+ - Window: primary application window.
85
+ - MapGridGameBuilder: composition helper for map + grid game wiring.
86
+ - BuiltMapGridGame: typed return object from the builder.
87
+ - MapSoundNavigationController: reusable map navigation + sound integration helper.
88
+
89
+ ## Package-Level APIs
90
+
91
+ Use package imports when you want lower-level control:
92
+
93
+ ```python
94
+ from sonartk.ui import Window
95
+ from sonartk.map_builder import Map2d, MapTile, load_2d_map
96
+ from sonartk.orchestration import (
97
+ MapGridGameBuilder,
98
+ MapSoundNavigationController,
99
+ )
100
+ from sonartk.sound import sound_manager
101
+ from sonartk.util import Direction, Coordinates, KeyHandler
102
+ ```
103
+
104
+ UI remains focused on UI primitives. Cross-domain composition helpers belong at
105
+ top level (for example, MapGridGameBuilder).
106
+
107
+ The explicit orchestration package path is also supported:
108
+
109
+ ```python
110
+ from sonartk.orchestration import MapGridGameBuilder
111
+ from sonartk.orchestration import MapSoundNavigationController
112
+ ```
113
+
114
+ ## Public API Reference
115
+
116
+ The following exports are the intended stable import surface:
117
+
118
+ - sonartk: Window, MapGridGameBuilder, BuiltMapGridGame, MapSoundNavigationController
119
+ - sonartk.ui: UIComponent, FocusableContainer, Window
120
+ - sonartk.map_builder: Map2d, MapTile, load_2d_map
121
+ - sonartk.map_builder.map_2d: Map2d, Map2dQueries, MapQueryResult, PathfindingResult, PathfindingState, MapTile, load_2d_map
122
+ - sonartk.map_builder.map_2d.map_object: MapObject, Character
123
+ - sonartk.map_builder.map_2d.parser: MapParser, CSVParser, JSONParser
124
+ - sonartk.orchestration: AmbientTileSoundConfig, IntroGameAudioLifecycle, TerrainAudioProfile, BuiltMapGridGame, MapGridGameBuilder, MapSoundNavigationController
125
+ - sonartk.sound: sound_manager
126
+ - sonartk.util: Callback, Coordinates, Direction, EmptyState, Key,
127
+ KeyHandler, State, StateMachine, speech_manager
128
+
129
+ ## Audio Preloading APIs
130
+
131
+ When using the sound package, you can preload assets at startup to reduce
132
+ runtime latency and fail early on missing/invalid files.
133
+
134
+ - `sound_manager.preload_sounds(paths)`: preload through the shared default
135
+ sound manager.
136
+ - `sound_manager.allocate_players_by_role(role_names)`: allocate named players
137
+ for gameplay roles without positional tuple wiring.
138
+ - `SoundPool.load_many(paths)`: lower-level preload API when managing your own
139
+ `SoundPool` instance.
140
+
141
+ ## New Orchestration Audio APIs
142
+
143
+ - `TerrainAudioProfile`: unify passability + movement + ambient terrain audio.
144
+ - `MapSoundNavigationController.from_terrain_audio_profiles(...)`: build
145
+ movement + ambient controller directly from terrain profiles.
146
+ - `MapSoundNavigationController.validate_sound_map_for_map()`: fail fast on
147
+ missing movement/collision mappings.
148
+ - `IntroGameAudioLifecycle`: reusable intro-to-game audio transition helper.
149
+
150
+ ## Map Terrain Utility
151
+
152
+ - `sonartk.map_builder.map_2d.map_tools.mark_adjacent_tiles(...)`: generic
153
+ terrain post-processing helper (for example, mark mud banks around rivers).
154
+
155
+ If you need symbols outside this list, import from the concrete module path
156
+ and treat those imports as lower-level/internal APIs.
157
+
158
+ ## Map Builder Behavior Notes
159
+
160
+ - Map2d.add_row prepends rows. The latest inserted row is y=0.
161
+ - Map2d register/move methods now return displaced occupants when collision policy is set to replace:
162
+ - Map2d.register_character(...) -> Character | None
163
+ - Map2d.move_character(...) -> Character | None
164
+ - Map2d.register_map_object(...) -> MapObject | None
165
+ - Map2d.move_map_object(...) -> MapObject | None
166
+ - Map2dQueries.get_coordinate_hit(...) with tile_names returns None for in-range coordinates when:
167
+ - no tile name matches, and
168
+ - no character/map object occupies that coordinate.
169
+ - Map2dQueries.find_path(...) accepts optional PathfindingState for dynamic traversal rules:
170
+ - blocked_coordinates: explicit blocked coordinates for this search.
171
+ - block_characters: treat character-occupied coordinates as blocked.
172
+ - block_map_objects: treat map-object-occupied coordinates as blocked.
173
+ - actor_capabilities: capability profile used for tile capability checks.
174
+ - allow_end_occupied: allow/disallow a blocked end coordinate.
175
+ - allow_start_blocked: allow/disallow starting from a blocked coordinate.
176
+ - allow_diagonal_movement: enable 8-direction path traversal.
177
+ - allow_corner_cutting: allow/disallow diagonal corner cuts around blockers.
178
+ - tile_cost_by_name: add non-negative movement costs by tile name.
179
+ - tile_cost_resolver: callback for dynamic non-negative movement costs.
180
+ - dynamic_cost_layers: stackable callbacks for additive contextual costs.
181
+ - turn_penalty: non-negative cost applied when the route changes direction.
182
+ - blocker_proximity_cost: non-negative cost scaled by nearby blocker count.
183
+ - tie_breaker: equal-cost ordering policy (fifo, lower-heuristic, lower-cost).
184
+ - max_expanded_nodes: optional hard cap for search expansion.
185
+ - max_total_cost: optional hard cap for route total cost.
186
+ - smoothing_mode: post-process output path (none or collinear).
187
+ - enable_result_cache/cache_namespace/map_state_token: result-cache controls for repeated planning.
188
+ - Map2dQueries.find_path_result(...) returns metadata (found, path, total_cost,
189
+ visited_nodes, expanded_nodes, reason, from_cache) for AI/debug tooling.
190
+ - MapTile.allowed_entry_directions can constrain which movement directions may enter a tile.
191
+ - MapTile.required_capabilities can require actor capabilities for traversal.
192
+ - PathfindingState also includes preset constructors for common movement profiles:
193
+ - PathfindingState.for_player(tier="relaxed"|"standard"|"strict")
194
+ - PathfindingState.for_flying_enemy(tier="relaxed"|"standard"|"strict")
195
+ - PathfindingState.for_heavy_unit(tier="relaxed"|"standard"|"strict")
196
+
197
+ Example of handling displaced occupants in replace mode:
198
+
199
+ ```python
200
+ from sonartk.map_builder import Map2d
201
+ from sonartk.map_builder.map_2d.map_object import Character
202
+ from sonartk.util import Direction
203
+
204
+ hero = Character("hero", (1, 1), Direction.UP)
205
+ map2d = Map2d(
206
+ "arena",
207
+ hero,
208
+ character_collision_policy="replace",
209
+ )
210
+
211
+ ally = Character("ally", (0, 0), Direction.RIGHT)
212
+ blocker = Character("blocker", (1, 0), Direction.LEFT)
213
+ map2d.register_character(ally)
214
+ map2d.register_character(blocker)
215
+
216
+ displaced = map2d.move_character(ally, (1, 0))
217
+ if displaced is not None:
218
+ print(f"Displaced character: {displaced.name}")
219
+ ```
220
+
221
+ Example of state-aware pathfinding:
222
+
223
+ ```python
224
+ from sonartk.map_builder.map_2d import PathfindingState
225
+
226
+ path = map2d.queries.find_path(
227
+ start=(0, 0),
228
+ end=(3, 0),
229
+ state=PathfindingState(
230
+ blocked_coordinates={(1, 0)},
231
+ block_characters=True,
232
+ block_map_objects=True,
233
+ allow_end_occupied=False,
234
+ allow_start_blocked=False,
235
+ allow_diagonal_movement=True,
236
+ allow_corner_cutting=False,
237
+ tile_cost_by_name={"mud": 4.0},
238
+ turn_penalty=0.25,
239
+ max_expanded_nodes=2000,
240
+ smoothing_mode="collinear",
241
+ ),
242
+ )
243
+ ```
244
+
245
+ Example of metadata and caching:
246
+
247
+ ```python
248
+ state = PathfindingState(
249
+ enable_result_cache=True,
250
+ cache_namespace="enemy-ai",
251
+ map_state_token="wave-2",
252
+ )
253
+
254
+ result = map2d.queries.find_path_result((0, 0), (4, 2), state=state)
255
+ if result.found:
256
+ print(result.path, result.total_cost, result.from_cache)
257
+ else:
258
+ print(result.reason)
259
+
260
+ # Invalidate when map blockers/state changes.
261
+ map2d.queries.invalidate_path_cache()
262
+ ```
263
+
264
+ Example of preset profiles:
265
+
266
+ ```python
267
+ player_state = PathfindingState.for_player("standard")
268
+ flying_state = PathfindingState.for_flying_enemy("relaxed")
269
+ heavy_state = PathfindingState.for_heavy_unit("strict")
270
+
271
+ player_path = map2d.queries.find_path((0, 0), (5, 2), state=player_state)
272
+ enemy_path = map2d.queries.find_path((0, 0), (5, 2), state=flying_state)
273
+ tank_path = map2d.queries.find_path((0, 0), (5, 2), state=heavy_state)
274
+ ```
275
+
276
+ Example of preset + overrides:
277
+
278
+ ```python
279
+ state = PathfindingState.for_player().with_overrides(
280
+ max_expanded_nodes=500,
281
+ allow_diagonal_movement=True,
282
+ )
283
+
284
+ path = map2d.queries.find_path((0, 0), (5, 2), state=state)
285
+ ```
286
+
287
+ Example of custom profile registry:
288
+
289
+ ```python
290
+ PathfindingState.register_profile(
291
+ "scout",
292
+ PathfindingState.for_player("relaxed").with_overrides(
293
+ allow_diagonal_movement=True,
294
+ turn_penalty=0.1,
295
+ ),
296
+ )
297
+
298
+ scout_state = PathfindingState.from_profile("scout")
299
+ path = map2d.queries.find_path((0, 0), (5, 2), state=scout_state)
300
+
301
+ print(PathfindingState.list_profiles())
302
+ PathfindingState.unregister_profile("scout")
303
+ ```
304
+
305
+ Example of profile persistence data:
306
+
307
+ ```python
308
+ PathfindingState.register_profile("scout", PathfindingState.for_player("relaxed"))
309
+ data = PathfindingState.export_profiles()
310
+
311
+ # Save/load this JSON-safe dict with your own file I/O.
312
+ PathfindingState.import_profiles(data, overwrite=True)
313
+
314
+ # Strict mode rejects unknown profile fields.
315
+ PathfindingState.import_profiles(data, overwrite=True, strict=True)
316
+
317
+ # Strict mode also validates field types and value ranges.
318
+ # Examples rejected in strict mode:
319
+ # - negative max_total_cost or max_expanded_nodes
320
+ # - non-finite numbers (nan/inf) for numeric fields
321
+ # - negative tile_cost_by_name values
322
+ ```
323
+
324
+ Example of profile JSON file helpers:
325
+
326
+ ```python
327
+ PathfindingState.save_profiles("profiles.json")
328
+ PathfindingState.load_profiles("profiles.json", overwrite=True)
329
+
330
+ # Permissive mode ignores unknown fields when loading.
331
+ PathfindingState.load_profiles("profiles.json", overwrite=True, strict=False)
332
+
333
+ # strict=False only relaxes unknown-field handling.
334
+ # Invalid types and values still raise clear errors at runtime when used.
335
+
336
+ # Optional output style controls.
337
+ PathfindingState.save_profiles(
338
+ "profiles.compact.json",
339
+ pretty=False,
340
+ sort_keys=False,
341
+ )
342
+ ```
343
+
344
+ ## Dependencies
345
+
346
+ Core runtime dependencies include:
347
+
348
+ - pyglet
349
+ - accessible_output2
350
+ - pyperclip
351
+ - ijson
352
+ - pyogg
353
+
354
+ ## Source Setup
355
+
356
+ Clone the repository:
357
+
358
+ https://github.com/tbreitenfeldt/sonar-tk
359
+
360
+ Install dependencies:
361
+
362
+ ```bash
363
+ pip install -r requirements.txt
364
+ ```
365
+
366
+ ## Running Tests
367
+
368
+ Run the test suite with pytest:
369
+
370
+ ```bash
371
+ pytest
372
+ ```
373
+
374
+ Run coverage locally:
375
+
376
+ ```bash
377
+ pytest --cov=src --cov-report=term-missing
378
+ ```
379
+
380
+ ## Window Debugging
381
+
382
+ Window includes optional runtime diagnostics for handler-stack issues.
383
+
384
+ Enable debug mode when creating the window:
385
+
386
+ ```python
387
+ from sonartk import Window
388
+
389
+ window = Window(caption="My Game", debug_mode=True)
390
+ ```
391
+
392
+ When debug mode is enabled, you'll see startup message and validation output on stderr:
393
+
394
+ ```
395
+ [Window debug] Debug mode enabled - handler stack validation active
396
+ [Window debug] after change('menu_screen'): Handler stack valid (expected=3, actual=3)
397
+ [Window debug] after set_state('button_0'): Handler stack valid (expected=4, actual=4)
398
+ ```
399
+
400
+ Or toggle it at runtime:
401
+
402
+ ```python
403
+ window.set_debug_mode(True) # Logs: Debug mode enabled - handler stack validation active
404
+ window.set_debug_mode(False) # Logs: Debug mode disabled
405
+ ```
406
+
407
+ When debug mode is enabled, window automatically logs handler-stack validation
408
+ after:
409
+
410
+ - Window.change(...)
411
+ - Window.set_state(...)
412
+
413
+ This shows you the handler stack state (expected vs actual count) at each transition,
414
+ making it easy to spot when a state's exit() or setup() is not managing handlers
415
+ correctly.
416
+
417
+ You can run checks manually at any time:
418
+
419
+ ```python
420
+ is_valid, message = window.validate_handler_stack()
421
+ if not is_valid:
422
+ print(message)
423
+
424
+ # Optional leak check helper (defaults to window baseline handlers)
425
+ window = Window(debug_mode=True, debug_stream=debug_stream)
426
+ ```
@@ -0,0 +1,79 @@
1
+ [build-system]
2
+ requires = ["setuptools"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "sonartk"
7
+ authors = [
8
+ {name = "Timothy Breitenfeldt", email = "timothyjb310@gmail.com"},
9
+ ]
10
+ readme = "readme.md"
11
+ version = "0.1"
12
+ description = "A library used to create audio games built on top of Pyglet."
13
+ requires-python = ">=3.13"
14
+ keywords = ["development", "accessibility", "screen reader", "sonartk", "sonar tk"]
15
+ license = "MIT"
16
+ license-files = ["LICENSE"]
17
+ classifiers = [
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.13",
20
+ "Operating System :: OS Independent",
21
+ ]
22
+ dependencies = [
23
+ "accessible_output2",
24
+ "pyglet",
25
+ "pyperclip",
26
+ "ijson",
27
+ "pyogg",
28
+ ]
29
+
30
+ [project.optional-dependencies]
31
+ test = [
32
+ "build",
33
+ "pytest",
34
+ "pytest-mock",
35
+ "pytest-cov",
36
+ ]
37
+ dev = [
38
+ "mypy",
39
+ "flake8",
40
+ "black",
41
+ "pre-commit",
42
+ "twine",
43
+ ]
44
+ docs = [
45
+ "mkdocs==1.6.1",
46
+ "mkdocs-material==9.5.47",
47
+ "mkdocstrings==1.0.4",
48
+ "mkdocstrings-python==2.0.3",
49
+ ]
50
+
51
+ [project.urls]
52
+ repository = "https://github.com/tbreitenfeldt/sonar-tk"
53
+ bugtracker = "https://github.com/tbreitenfeldt/sonar-tk/issues"
54
+
55
+ [tool.black]
56
+ target-version = ['py313']
57
+ line-length = 79
58
+ include = "\\.pyi?$"
59
+ exclude = """
60
+ /(
61
+ /.git
62
+ | /.hg
63
+ | /.mypy_cache
64
+ | /.tox
65
+ | /.venv
66
+ | _build
67
+ | buck-out
68
+ | build
69
+ | dist
70
+ )/
71
+ """
72
+
73
+ [tool.flake8]
74
+ max-line-length = 79
75
+ max-complexity = 18
76
+ select = "B,C,E,F,W,T4,B9"
77
+ ignore = "E203,E266,E501,E704,E711,E712,E722,W503,F403,F405,F811"
78
+ exclude = [".git", "examples", "test", ".venv", "src/sonartk/sound/openal_lite"]
79
+