From 5181c3e74a643c1805c95bfc1b919cc29ba8c2eb Mon Sep 17 00:00:00 2001 From: Phillip Tarrant Date: Fri, 10 Jul 2026 12:51:13 -0500 Subject: [PATCH] =?UTF-8?q?feat(client):=20authored=20NPC=20fallback=20lin?= =?UTF-8?q?e=20(=C2=A713)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- client/content/fallback/npc.json | 5 ++++ client/scripts/net/fallback_library.gd | 29 +++++++++++++--------- client/tests/unit/test_fallback_library.gd | 12 +++++++++ 3 files changed, 34 insertions(+), 12 deletions(-) create mode 100644 client/content/fallback/npc.json diff --git a/client/content/fallback/npc.json b/client/content/fallback/npc.json new file mode 100644 index 0000000..0520bed --- /dev/null +++ b/client/content/fallback/npc.json @@ -0,0 +1,5 @@ +{ + "npc": [ + "The figure looks you over, says nothing you can use, and turns back to their work." + ] +} diff --git a/client/scripts/net/fallback_library.gd b/client/scripts/net/fallback_library.gd index 408a7be..73a15b5 100644 --- a/client/scripts/net/fallback_library.gd +++ b/client/scripts/net/fallback_library.gd @@ -1,37 +1,42 @@ class_name FallbackLibrary extends RefCounted -## Authored degraded-DM fallback text (charter §13). The client shows one of -## these lines on ANY failure, in the Narrator's voice — never an error. If the -## content file is missing or malformed the library returns a hardcoded -## constant, so the fallback itself cannot fail. +## Authored degraded-DM fallback text (charter §13). On ANY failure the client +## shows one of these lines in-voice — never an error. Missing/malformed content +## yields a hardcoded constant, so the fallback itself cannot fail. const PATH := "res://content/fallback/narrator.json" +const NPC_PATH := "res://content/fallback/npc.json" const LAST_RESORT := "The chamber is cold. Something waits in the dark." +const NPC_LAST_RESORT := "The figure regards you in silence, and the moment passes." var _lines: Array = [] +var _npc_lines: Array = [] -func _init(path := PATH) -> void: - _lines = _load(path) +func _init(path := PATH, npc_path := NPC_PATH) -> void: + _lines = _load(path, "narrator") + _npc_lines = _load(npc_path, "npc") func narrator_line() -> String: return str(_lines[0]) if not _lines.is_empty() else LAST_RESORT -static func _load(path: String) -> Array: +func npc_line() -> String: + return str(_npc_lines[0]) if not _npc_lines.is_empty() else NPC_LAST_RESORT + + +static func _load(path: String, key: String) -> Array: if not FileAccess.file_exists(path): return [] var text := FileAccess.get_file_as_string(path) - # Use the instance API, not JSON.parse_string(): the static convenience - # method pushes an engine-level error on malformed input, which is noisy - # and (in this project's test config) turns a handled failure into a - # reported test error. The instance parse() just returns an Error code. + # Instance parse() (not JSON.parse_string) so malformed input returns an + # Error code instead of pushing an engine error the test config promotes. var json := JSON.new() if json.parse(text) != OK: return [] var data = json.get_data() if typeof(data) != TYPE_DICTIONARY: return [] - var lines = data.get("narrator", []) + var lines = data.get(key, []) return lines if typeof(lines) == TYPE_ARRAY else [] diff --git a/client/tests/unit/test_fallback_library.gd b/client/tests/unit/test_fallback_library.gd index e517993..5b3d4f6 100644 --- a/client/tests/unit/test_fallback_library.gd +++ b/client/tests/unit/test_fallback_library.gd @@ -31,3 +31,15 @@ func test_empty_list_uses_last_resort(): f.close() var lib = FallbackLibrary.new(path) assert_eq(lib.narrator_line(), FallbackLibrary.LAST_RESORT) + + +func test_npc_line_falls_back_to_constant_when_missing(): + var lib = FallbackLibrary.new( + "res://content/fallback/does_not_exist.json", + "res://content/fallback/does_not_exist.json") + assert_eq(lib.npc_line(), FallbackLibrary.NPC_LAST_RESORT) + + +func test_npc_line_reads_authored_file(): + var lib = FallbackLibrary.new() # default paths — real content + assert_ne(lib.npc_line(), "")