Sonar TK
Sonar TK is a Python library for creating audio-first games and accessible, screen-reader-oriented interactive applications.
The project is organized into focused packages:
- ui: windowing, focus, screens, and interactive UI elements.
- map_builder: 2D map loading, tiles, map objects, and parsers.
- sound: sound playback and OpenAL integration helpers.
- util: speech, key handling, shared state/state machine primitives, and core utility types.
- orchestration: optional higher-level coordination helpers for composing ui + map_builder + sound into game flows.
For common game composition, Sonar TK also provides a top-level builder API.
If you only need core primitives (for example, UI-only or UI+sound projects),
you can ignore orchestration entirely.
Installation
Install from PyPI:
pip install sonartk
Supported Python Versions
Sonar TK currently requires Python 3.13 or newer.
Top-Level API
The top-level package exposes high-level entry points:
from sonartk import (
Window,
MapGridGameBuilder,
BuiltMapGridGame,
MapSoundNavigationController,
)
- Window: primary application window.
- MapGridGameBuilder: composition helper for map + grid game wiring.
- BuiltMapGridGame: typed return object from the builder.
- MapSoundNavigationController: reusable map navigation + sound integration helper.
Package-Level APIs
Use package imports when you want lower-level control:
from sonartk.ui import Window
from sonartk.map_builder import Map2d, MapTile, load_2d_map
from sonartk.orchestration import (
MapGridGameBuilder,
MapSoundNavigationController,
)
from sonartk.sound import sound_manager
from sonartk.util import Direction, Coordinates, KeyHandler
UI remains focused on UI primitives. Cross-domain composition helpers belong at top level (for example, MapGridGameBuilder).
The explicit orchestration package path is also supported:
from sonartk.orchestration import MapGridGameBuilder
from sonartk.orchestration import MapSoundNavigationController
Public API Reference
The following exports are the intended stable import surface:
- sonartk: Window, MapGridGameBuilder, BuiltMapGridGame, MapSoundNavigationController
- sonartk.ui: UIComponent, FocusableContainer, Window
- sonartk.map_builder: Map2d, MapTile, load_2d_map
- sonartk.map_builder.map_2d: Map2d, Map2dQueries, MapQueryResult, PathfindingResult, PathfindingState, MapTile, load_2d_map
- sonartk.map_builder.map_2d.map_object: MapObject, Character
- sonartk.map_builder.map_2d.parser: MapParser, CSVParser, JSONParser
- sonartk.orchestration: AmbientTileSoundConfig, IntroGameAudioLifecycle, TerrainAudioProfile, BuiltMapGridGame, MapGridGameBuilder, MapSoundNavigationController
- sonartk.sound: sound_manager
- sonartk.util: Callback, Coordinates, Direction, EmptyState, Key, KeyHandler, State, StateMachine, speech_manager
Audio Preloading APIs
When using the sound package, you can preload assets at startup to reduce runtime latency and fail early on missing/invalid files.
sound_manager.preload_sounds(paths): preload through the shared default sound manager.sound_manager.allocate_players_by_role(role_names): allocate named players for gameplay roles without positional tuple wiring.SoundPool.load_many(paths): lower-level preload API when managing your ownSoundPoolinstance.
New Orchestration Audio APIs
TerrainAudioProfile: unify passability + movement + ambient terrain audio.MapSoundNavigationController.from_terrain_audio_profiles(...): build movement + ambient controller directly from terrain profiles.MapSoundNavigationController.validate_sound_map_for_map(): fail fast on missing movement/collision mappings.IntroGameAudioLifecycle: reusable intro-to-game audio transition helper.
Map Terrain Utility
sonartk.map_builder.map_2d.map_tools.mark_adjacent_tiles(...): generic terrain post-processing helper (for example, mark mud banks around rivers).
If you need symbols outside this list, import from the concrete module path and treat those imports as lower-level/internal APIs.
Map Builder Behavior Notes
- Map2d.add_row prepends rows. The latest inserted row is y=0.
- Map2d register/move methods now return displaced occupants when collision policy is set to replace:
- Map2d.register_character(...) -> Character | None
- Map2d.move_character(...) -> Character | None
- Map2d.register_map_object(...) -> MapObject | None
- Map2d.move_map_object(...) -> MapObject | None
- Map2dQueries.get_coordinate_hit(...) with tile_names returns None for in-range coordinates when:
- no tile name matches, and
- no character/map object occupies that coordinate.
- Map2dQueries.find_path(...) accepts optional PathfindingState for dynamic traversal rules:
- blocked_coordinates: explicit blocked coordinates for this search.
- block_characters: treat character-occupied coordinates as blocked.
- block_map_objects: treat map-object-occupied coordinates as blocked.
- actor_capabilities: capability profile used for tile capability checks.
- allow_end_occupied: allow/disallow a blocked end coordinate.
- allow_start_blocked: allow/disallow starting from a blocked coordinate.
- allow_diagonal_movement: enable 8-direction path traversal.
- allow_corner_cutting: allow/disallow diagonal corner cuts around blockers.
- tile_cost_by_name: add non-negative movement costs by tile name.
- tile_cost_resolver: callback for dynamic non-negative movement costs.
- dynamic_cost_layers: stackable callbacks for additive contextual costs.
- turn_penalty: non-negative cost applied when the route changes direction.
- blocker_proximity_cost: non-negative cost scaled by nearby blocker count.
- tie_breaker: equal-cost ordering policy (fifo, lower-heuristic, lower-cost).
- max_expanded_nodes: optional hard cap for search expansion.
- max_total_cost: optional hard cap for route total cost.
- smoothing_mode: post-process output path (none or collinear).
- enable_result_cache/cache_namespace/map_state_token: result-cache controls for repeated planning.
- Map2dQueries.find_path_result(...) returns metadata (found, path, total_cost, visited_nodes, expanded_nodes, reason, from_cache) for AI/debug tooling.
- MapTile.allowed_entry_directions can constrain which movement directions may enter a tile.
- MapTile.required_capabilities can require actor capabilities for traversal.
- PathfindingState also includes preset constructors for common movement profiles:
- PathfindingState.for_player(tier="relaxed"|"standard"|"strict")
- PathfindingState.for_flying_enemy(tier="relaxed"|"standard"|"strict")
- PathfindingState.for_heavy_unit(tier="relaxed"|"standard"|"strict")
Example of handling displaced occupants in replace mode:
from sonartk.map_builder import Map2d
from sonartk.map_builder.map_2d.map_object import Character
from sonartk.util import Direction
hero = Character("hero", (1, 1), Direction.UP)
map2d = Map2d(
"arena",
hero,
character_collision_policy="replace",
)
ally = Character("ally", (0, 0), Direction.RIGHT)
blocker = Character("blocker", (1, 0), Direction.LEFT)
map2d.register_character(ally)
map2d.register_character(blocker)
displaced = map2d.move_character(ally, (1, 0))
if displaced is not None:
print(f"Displaced character: {displaced.name}")
Example of state-aware pathfinding:
from sonartk.map_builder.map_2d import PathfindingState
path = map2d.queries.find_path(
start=(0, 0),
end=(3, 0),
state=PathfindingState(
blocked_coordinates={(1, 0)},
block_characters=True,
block_map_objects=True,
allow_end_occupied=False,
allow_start_blocked=False,
allow_diagonal_movement=True,
allow_corner_cutting=False,
tile_cost_by_name={"mud": 4.0},
turn_penalty=0.25,
max_expanded_nodes=2000,
smoothing_mode="collinear",
),
)
Example of metadata and caching:
state = PathfindingState(
enable_result_cache=True,
cache_namespace="enemy-ai",
map_state_token="wave-2",
)
result = map2d.queries.find_path_result((0, 0), (4, 2), state=state)
if result.found:
print(result.path, result.total_cost, result.from_cache)
else:
print(result.reason)
# Invalidate when map blockers/state changes.
map2d.queries.invalidate_path_cache()
Example of preset profiles:
player_state = PathfindingState.for_player("standard")
flying_state = PathfindingState.for_flying_enemy("relaxed")
heavy_state = PathfindingState.for_heavy_unit("strict")
player_path = map2d.queries.find_path((0, 0), (5, 2), state=player_state)
enemy_path = map2d.queries.find_path((0, 0), (5, 2), state=flying_state)
tank_path = map2d.queries.find_path((0, 0), (5, 2), state=heavy_state)
Example of preset + overrides:
state = PathfindingState.for_player().with_overrides(
max_expanded_nodes=500,
allow_diagonal_movement=True,
)
path = map2d.queries.find_path((0, 0), (5, 2), state=state)
Example of custom profile registry:
PathfindingState.register_profile(
"scout",
PathfindingState.for_player("relaxed").with_overrides(
allow_diagonal_movement=True,
turn_penalty=0.1,
),
)
scout_state = PathfindingState.from_profile("scout")
path = map2d.queries.find_path((0, 0), (5, 2), state=scout_state)
print(PathfindingState.list_profiles())
PathfindingState.unregister_profile("scout")
Example of profile persistence data:
PathfindingState.register_profile("scout", PathfindingState.for_player("relaxed"))
data = PathfindingState.export_profiles()
# Save/load this JSON-safe dict with your own file I/O.
PathfindingState.import_profiles(data, overwrite=True)
# Strict mode rejects unknown profile fields.
PathfindingState.import_profiles(data, overwrite=True, strict=True)
# Strict mode also validates field types and value ranges.
# Examples rejected in strict mode:
# - negative max_total_cost or max_expanded_nodes
# - non-finite numbers (nan/inf) for numeric fields
# - negative tile_cost_by_name values
Example of profile JSON file helpers:
PathfindingState.save_profiles("profiles.json")
PathfindingState.load_profiles("profiles.json", overwrite=True)
# Permissive mode ignores unknown fields when loading.
PathfindingState.load_profiles("profiles.json", overwrite=True, strict=False)
# strict=False only relaxes unknown-field handling.
# Invalid types and values still raise clear errors at runtime when used.
# Optional output style controls.
PathfindingState.save_profiles(
"profiles.compact.json",
pretty=False,
sort_keys=False,
)
Dependencies
Core runtime dependencies include:
- pyglet
- accessible_output2
- pyperclip
- ijson
- pyogg
Source Setup
Clone the repository:
https://github.com/tbreitenfeldt/sonar-tk
Install dependencies:
pip install -r requirements.txt
Running Tests
Run the test suite with pytest:
pytest
Run coverage locally:
pytest --cov=src --cov-report=term-missing
Window Debugging
Window includes optional runtime diagnostics for handler-stack issues.
Enable debug mode when creating the window:
from sonartk import Window
window = Window(caption="My Game", debug_mode=True)
When debug mode is enabled, you'll see startup message and validation output on stderr:
[Window debug] Debug mode enabled - handler stack validation active
[Window debug] after transition_to('menu_screen'): Handler stack valid (expected=3, actual=3)
[Window debug] after transition_to('button_0'): Handler stack valid (expected=4, actual=4)
Or toggle it at runtime:
window.set_debug_mode(True) # Logs: Debug mode enabled - handler stack validation active
window.set_debug_mode(False) # Logs: Debug mode disabled
When debug mode is enabled, window automatically logs handler-stack validation after:
- Window.transition_to(...)
- Window.activate_current_state(...)
This shows you the handler stack state (expected vs actual count) at each transition, making it easy to spot when a state's exit() or setup() is not managing handlers correctly.
You can run checks manually at any time:
is_valid, message = window.validate_handler_stack()
if not is_valid:
print(message)
# Optional leak check helper (defaults to window baseline handlers)
window = Window(debug_mode=True, debug_stream=debug_stream)
Metadata
Release files for sonartk 0.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| sonartk-0.2.tar.gz | 103.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| sonartk-0.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 224.3 kB
Release files / sonartk-0.2.tar.gz
| Download URL | sonartk-0.2.tar.gz |
|---|---|
| Size | 103.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
162dd4a35bc043e2fa93fcde80a44bfb05154e70c4391a4b549b848d45a9d485
|
|
BLAKE2b-256 checksum How to use checksums |
bb837d4689bd8e16e74d91df9a24329940a1da581d9081665588548d115d077a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 20, 2026.
Transparency logRelease files / sonartk-0.2-py3-none-any.whl
| Download URL | sonartk-0.2-py3-none-any.whl |
|---|---|
| Size | 120.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
5b2da0d44dd0c430a94f4e7044415ae831c3020dbf81b365a8d6f759dc86bfc2
|
|
BLAKE2b-256 checksum How to use checksums |
b31e0c1f3c6e66a048fab1fcb3fd615540ad3f59f5b0332e3b8a0beaa1ce09f8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 20, 2026.
Transparency log