port: a mistyped mod override was silent -- the liveness defect, in the product

Every checker fix this week was about a tool that could not tell 'I checked and it
was fine' from 'I checked nothing'. The port had the same defect facing the person
the asset tree exists for.

ExportTree.resolve announces every shadow as it happens, and its comment already
records why a startup summary was wrong. Nothing reported the opposite. Measured
with two planted overrides, one correct and one in a mistyped directory: the
correct one is announced and the typo produces NO OUTPUT AT ALL. The modder sees
the port load, run, and say nothing about the file that did nothing -- MODDING
rule 4's own failure mode, since base-and-overrides is only usable if an override
that misses says so.

ExportTree.unused_mods() and a report at run end now list them. Controlled both
directions: one inert file with the typo present, silent with it removed.

Getting the category right took three tries and that is the point. v1 'never used'
flagged data/mods/README.md on every run, and a report with a standing false
positive is one nobody reads -- precisely the failure it exists to fix. v2 'no such
path in the export' was correct and still flagged the README. v3 excludes by
extension with the rule checked rather than assumed: the export tree contains only
png, json, ogg, ogv and cmd, verified zero .md anywhere, so a .md in data/mods
could never be an override by construction.

The report also separates what v1 conflated: a file whose path exists in the export
but was not read this run is NOT listed. Every line printed is an override that
can never apply, whatever the run does.

boot.gd already had an _exit_tree and adding a second was a parse error -- the run
failed loudly instead of one hook silently replacing the other, the cheapest
possible failure mode and only because GDScript rejects it.

Their P3 delivery is taken at the strength given: Q6's count-match has disc
support for its structure -- every button record across all 16 GP_TITLE entries is
ptbtn00, ptbtn01-05, ptbtn11-13 -- but it does not show that event 3 is a
particular row, and they said not to author from it. flow.json already binds
buttons by measured screen rather than event index, so nothing changes. Their own
negative is narrower than 'not found': the DIFFICULTY search assumed four items
pair with f variants, so what is established is 'not an 8-record btn-named build
anywhere'.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N7FiFFFwbvG2uxdcEh8HyF
This commit is contained in:
Sylpheed port agent
2026-08-31 02:20:38 +00:00
parent b4cac06ac2
commit a3ec9d03fa
3 changed files with 161 additions and 1 deletions

View File

@@ -104,6 +104,7 @@ func _ready() -> void:
get_tree().quit(4)
return
var export_tree := ExportTree.locate()
_tree = export_tree
if export_tree.root == "":
push_error(export_tree.error)
get_tree().quit(2)
@@ -735,6 +736,33 @@ func _video_finished() -> void:
var _video_then: Dictionary = {}
## Say what the mods directory contributed, and what it did not.
##
## The shadow lines are printed as they happen; this is the other half -- files
## that matched nothing. Without it a mistyped override is indistinguishable
## from a working one to the person who wrote it, which makes the whole
## base-and-overrides arrangement (MODDING rule 4) unusable by its own audience.
## The export tree, kept so `_exit_tree` can report what the mods directory did
## NOT contribute. It was a local in `_ready`, which is why the unused-override
## report could not exist until now.
var _tree: ExportTree = null
func _report_unused_mods() -> void:
if _tree == null:
return
var unused := _tree.unused_mods()
if unused.is_empty():
return
print("mods: %d file(s) in data/mods can shadow NOTHING -- no such path in the"
% unused.size() + " export:")
for rel in unused:
print(" inert: %s" % rel)
print(" ⚠️ These are not \"not reached yet\": a file whose path exists in the")
print(" export but was not read this run is NOT listed. Every line above is")
print(" an override that can never apply, whatever the run does.")
func _unhandled_input(event: InputEvent) -> void:
# HANDOFF Q9, measured: one (A) press skips a movie -- the title was reached
# at 57 s against a 193 s baseline.
@@ -1204,6 +1232,10 @@ func _start_recording() -> void:
func _exit_tree() -> void:
# 🔴 boot.gd ALREADY HAD an `_exit_tree`, and adding a second was a parse
# error rather than a silent override -- the one failure mode that costs
# nothing. The mods report hangs off the existing hook.
_report_unused_mods()
if _record == null:
return
_record.set_recording_active(false)

View File

@@ -80,6 +80,70 @@ func resolve(rel: String) -> String:
return root.path_join(rel)
## Mod files that were never used, listed at the end of a run.
##
## 🔴 A MISTYPED OVERRIDE WAS SILENT. `resolve` announces every shadow as it
## happens -- that half was already right, and its comment records why a startup
## summary was wrong. What nothing reported was the opposite: a file sitting in
## `data/mods/` whose path matches no asset. Measured: `sprites/title/main_menu/`
## is announced, `sprites/title/TYPO_menu/` produces **no output at all**. The
## modder sees the port load, run, and say nothing about the file that did
## nothing.
##
## That is MODDING rule 4's own failure mode -- base-and-overrides is only usable
## if an override that misses says so -- and it is the same shape as the
## checkers that passed on an empty input: **agreeable rather than wrong.** A
## port that cannot tell "your override is in effect" from "your override was
## never looked at" is unusable for the person the asset tree exists for.
##
## ⚠️ Reported at the END of a run, not at startup: resolution is lazy, so before
## the assets are read there is nothing to compare against. A run that quits
## early will list files a longer run would have used, and the wording says so
## rather than calling them errors.
func unused_mods() -> PackedStringArray:
var out: PackedStringArray = []
if mods == "":
return out
var stack: PackedStringArray = [""]
while not stack.is_empty():
var rel := stack[stack.size() - 1]
stack.remove_at(stack.size() - 1)
var dir := DirAccess.open(mods.path_join(rel))
if dir == null:
continue
dir.list_dir_begin()
var name := dir.get_next()
while name != "":
var child := rel.path_join(name) if rel != "" else name
if dir.current_is_dir():
stack.append(child)
elif not shadowed.has(child):
# 🔴 TWO DIFFERENT THINGS, and reporting them as one produced a
# permanent false positive on the mods directory's own README.
# A file whose path exists in `export/` was simply not read this
# run -- a `--menu` run touches one screen. A file whose path
# exists NOWHERE in the export can never shadow anything: that
# is the mistyped override, and it is the only one that is a
# defect. A report with a standing false positive becomes
# scenery, which is the failure this whole report exists to fix.
# ⚠️ And a THIRD category, excluded by extension with the rule
# stated rather than assumed: the export tree contains only
# `png`, `json`, `ogg`, `ogv` and `cmd` files -- checked, no
# `.md` anywhere -- so a `.md` in `data/mods` cannot shadow
# anything BY CONSTRUCTION and is documentation, not a failed
# override. Flagging a class that could never be an override is
# noise, and a report with a permanent false positive is one
# nobody reads. `data/mods/README.md` is the standing case.
var ext := child.get_extension().to_lower()
if ext in ["png", "json", "ogg", "ogv", "cmd"] \
and not FileAccess.file_exists(root.path_join(child)):
out.append(child)
name = dir.get_next()
dir.list_dir_end()
out.sort()
return out
# `authored/` sits beside `export/`, never inside it: it is hand-written and
# committed, and a re-export must not be able to touch it.
func authored(name: String) -> Variant: