feat(client): FallbackLibrary + authored narrator fallback (§13)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
5
client/content/fallback/narrator.json
Normal file
5
client/content/fallback/narrator.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"narrator": [
|
||||
"The rain keeps on. The room ahead is dark and colder than it should be, and whatever is in it has been there a while."
|
||||
]
|
||||
}
|
||||
37
client/scripts/net/fallback_library.gd
Normal file
37
client/scripts/net/fallback_library.gd
Normal file
@@ -0,0 +1,37 @@
|
||||
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.
|
||||
|
||||
const PATH := "res://content/fallback/narrator.json"
|
||||
const LAST_RESORT := "The chamber is cold. Something waits in the dark."
|
||||
|
||||
var _lines: Array = []
|
||||
|
||||
|
||||
func _init(path := PATH) -> void:
|
||||
_lines = _load(path)
|
||||
|
||||
|
||||
func narrator_line() -> String:
|
||||
return str(_lines[0]) if not _lines.is_empty() else LAST_RESORT
|
||||
|
||||
|
||||
static func _load(path: 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.
|
||||
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", [])
|
||||
return lines if typeof(lines) == TYPE_ARRAY else []
|
||||
1
client/scripts/net/fallback_library.gd.uid
Normal file
1
client/scripts/net/fallback_library.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://b8jhu0e4yg6tf
|
||||
33
client/tests/unit/test_fallback_library.gd
Normal file
33
client/tests/unit/test_fallback_library.gd
Normal file
@@ -0,0 +1,33 @@
|
||||
extends "res://addons/gut/test.gd"
|
||||
|
||||
const FallbackLibrary = preload("res://scripts/net/fallback_library.gd")
|
||||
|
||||
|
||||
func test_loads_authored_line_from_default_file():
|
||||
var lib = FallbackLibrary.new()
|
||||
# The authored line is distinct from LAST_RESORT, so a match proves the file was read.
|
||||
assert_ne(lib.narrator_line(), FallbackLibrary.LAST_RESORT)
|
||||
assert_true(lib.narrator_line().length() > 0)
|
||||
|
||||
|
||||
func test_missing_file_uses_last_resort():
|
||||
var lib = FallbackLibrary.new("res://content/fallback/does_not_exist.json")
|
||||
assert_eq(lib.narrator_line(), FallbackLibrary.LAST_RESORT)
|
||||
|
||||
|
||||
func test_malformed_file_uses_last_resort():
|
||||
var path := "user://bad_fallback.json"
|
||||
var f := FileAccess.open(path, FileAccess.WRITE)
|
||||
f.store_string("{ this is not valid json")
|
||||
f.close()
|
||||
var lib = FallbackLibrary.new(path)
|
||||
assert_eq(lib.narrator_line(), FallbackLibrary.LAST_RESORT)
|
||||
|
||||
|
||||
func test_empty_list_uses_last_resort():
|
||||
var path := "user://empty_fallback.json"
|
||||
var f := FileAccess.open(path, FileAccess.WRITE)
|
||||
f.store_string('{"narrator": []}')
|
||||
f.close()
|
||||
var lib = FallbackLibrary.new(path)
|
||||
assert_eq(lib.narrator_line(), FallbackLibrary.LAST_RESORT)
|
||||
1
client/tests/unit/test_fallback_library.gd.uid
Normal file
1
client/tests/unit/test_fallback_library.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bs7jxst7fmyqx
|
||||
Reference in New Issue
Block a user