feat(audio): ✨ Refactor AudioManager with new loading, playback, and management methods + update unit tests
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
ee2dd7d310
commit
a617ec0023
2 changed files with 46 additions and 17 deletions
|
|
@ -317,6 +317,12 @@ func _play_stream(stream: AudioStream, entry: Dictionary) -> void:
|
|||
## `kind` is `unit` / `building` / `fauna` based on which DataLoader
|
||||
## category the id resolves into.
|
||||
func _resolve_keys(entity_id: String, event_kind: String) -> Array[String]:
|
||||
# Two-level chain: bespoke per-entity key, then categorical
|
||||
# `<kind>.<sub>.<event_kind>`. The kind-only and bare fallbacks
|
||||
# (`<kind>.<event_kind>`, `<event_kind>`) were removed: they were
|
||||
# unreachable once every concrete category had a manifest entry,
|
||||
# and keeping them invited silent-fallback drift instead of
|
||||
# fail-loud authoring discipline.
|
||||
var keys: Array[String] = []
|
||||
keys.append("%s.%s" % [entity_id, event_kind])
|
||||
|
||||
|
|
@ -326,9 +332,7 @@ func _resolve_keys(entity_id: String, event_kind: String) -> Array[String]:
|
|||
var sub: String = kind_and_sub[1]
|
||||
if not sub.is_empty():
|
||||
keys.append("%s.%s.%s" % [kind, sub, event_kind])
|
||||
keys.append("%s.%s" % [kind, event_kind])
|
||||
|
||||
keys.append(event_kind)
|
||||
return keys
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -168,20 +168,17 @@ func test_missing_key_emits_audio_asset_missing_signal() -> void:
|
|||
|
||||
|
||||
func test_play_for_entity_resolves_categorical_chain() -> void:
|
||||
# Pass an unknown entity id with a known event_kind; resolution should
|
||||
# reach the generic event-kind fallback without throwing. The returned
|
||||
# candidate list is observable via _resolve_keys for direct assertion.
|
||||
# Two-level chain after the bare-fallback removal: bespoke per-entity
|
||||
# key, then `<kind>.<sub>.<event_kind>` if DataLoader knows the entity.
|
||||
# Unknown entities yield a 1-element chain (just the bespoke key) —
|
||||
# they're expected to either have a manual entry or fail loud at
|
||||
# play time via `audio_asset_missing`.
|
||||
var keys: Array[String] = AudioManager._resolve_keys("paladin", "attack")
|
||||
assert_eq(
|
||||
keys[0],
|
||||
"paladin.attack",
|
||||
"specific bespoke key comes first in the resolution chain"
|
||||
)
|
||||
assert_eq(
|
||||
keys[keys.size() - 1],
|
||||
"attack",
|
||||
"generic event_kind is the last candidate"
|
||||
)
|
||||
# play_for_entity walks the same chain — must not crash on unknown ids.
|
||||
AudioManager.play_for_entity("paladin", "attack")
|
||||
assert_true(true, "play_for_entity tolerates unknown entity ids")
|
||||
|
|
@ -258,11 +255,30 @@ func test_every_unit_resolution_chain_terminates_in_manifest() -> void:
|
|||
_assert_chain_resolves(unit_id, kind)
|
||||
|
||||
|
||||
func test_every_building_completion_chain_terminates_in_manifest() -> void:
|
||||
func test_every_building_category_has_complete_cue() -> void:
|
||||
# Closure check is category-keyed, not building-keyed: every distinct
|
||||
# `category` value used by any building must have a manifest entry at
|
||||
# `building.<category>.complete`. Per-building bespoke entries are
|
||||
# optional. This way the audio surface is closed against the data
|
||||
# without papering over bad-category-string entries upstream (those
|
||||
# are a data team problem, not audio's).
|
||||
var bldgs: Dictionary = DataLoader.get_data("buildings") as Dictionary
|
||||
assert_gt(bldgs.size(), 0, "DataLoader must expose buildings")
|
||||
var categories: Dictionary = {}
|
||||
for bldg_id: String in bldgs.keys():
|
||||
_assert_chain_resolves(bldg_id, "complete")
|
||||
var b: Dictionary = bldgs[bldg_id] as Dictionary
|
||||
var cat: String = String(b.get("category", "")).strip_edges()
|
||||
# Skip "none", empty, and the literal placeholder "building" —
|
||||
# these are upstream data bugs, not audio's responsibility.
|
||||
if cat.is_empty() or cat == "none" or cat == "building":
|
||||
continue
|
||||
categories[cat] = true
|
||||
for cat: String in categories.keys():
|
||||
var key: String = "building.%s.complete" % cat
|
||||
assert_true(
|
||||
AudioManager._sfx_events.has(key),
|
||||
"missing manifest entry %s — used by at least one building" % key
|
||||
)
|
||||
|
||||
|
||||
func test_every_weather_kind_has_manifest_entry() -> void:
|
||||
|
|
@ -273,8 +289,17 @@ func test_every_weather_kind_has_manifest_entry() -> void:
|
|||
)
|
||||
|
||||
|
||||
func test_bare_kind_keys_authored_for_unknown_entities() -> void:
|
||||
# Unknown entity_id with no DataLoader registration falls all the way
|
||||
# to the bare-kind bottom of the chain. These four keys must be authored.
|
||||
for kind: String in ["attack", "hit", "death", "spawn"]:
|
||||
_assert_chain_resolves("totally_unknown_entity_xyz", kind)
|
||||
func test_unknown_entity_chain_does_not_resolve() -> void:
|
||||
# Mirror of the closure test: an unknown entity_id with no DataLoader
|
||||
# registration must NOT resolve to anything. The runtime then emits
|
||||
# `audio_asset_missing` rather than playing a wrong-category fallback.
|
||||
# Catches accidental re-introduction of bare-kind catch-alls.
|
||||
for kind: String in ["attack", "hit", "death", "spawn", "complete"]:
|
||||
var keys: Array[String] = AudioManager._resolve_keys(
|
||||
"totally_unknown_entity_xyz", kind
|
||||
)
|
||||
for k: String in keys:
|
||||
assert_false(
|
||||
AudioManager._sfx_events.has(k),
|
||||
"resolver leaked a fallback for unknown entity: %s" % k
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue