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:
@@ -9,7 +9,7 @@ dies, which is what this file is for.
|
|||||||
|
|
||||||
<!-- INDEX: generated by tools/port/index-decisions -- do not hand-edit -->
|
<!-- INDEX: generated by tools/port/index-decisions -- do not hand-edit -->
|
||||||
|
|
||||||
276 sections. Search this before re-deriving anything.
|
278 sections. Search this before re-deriving anything.
|
||||||
|
|
||||||
* [P0 — the exporter, 2026-08-28](#p0--the-exporter-2026-08-28)
|
* [P0 — the exporter, 2026-08-28](#p0--the-exporter-2026-08-28)
|
||||||
* [P1 — Godot draws the screen, 2026-08-28](#p1--godot-draws-the-screen-2026-08-28)
|
* [P1 — Godot draws the screen, 2026-08-28](#p1--godot-draws-the-screen-2026-08-28)
|
||||||
@@ -287,6 +287,8 @@ dies, which is what this file is for.
|
|||||||
* [Menu focus does not survive a reboot — and the reach matters more than the result](#menu-focus-does-not-survive-a-reboot--and-the-reach-matters-more-than-the-result)
|
* [Menu focus does not survive a reboot — and the reach matters more than the result](#menu-focus-does-not-survive-a-reboot--and-the-reach-matters-more-than-the-result)
|
||||||
* [Liveness: every one of my tools passed on an empty input](#liveness-every-one-of-my-tools-passed-on-an-empty-input)
|
* [Liveness: every one of my tools passed on an empty input](#liveness-every-one-of-my-tools-passed-on-an-empty-input)
|
||||||
* [Their `ring_row.py` defect, and why it did not reach me](#their-ring_rowpy-defect-and-why-it-did-not-reach-me)
|
* [Their `ring_row.py` defect, and why it did not reach me](#their-ring_rowpy-defect-and-why-it-did-not-reach-me)
|
||||||
|
* [The liveness lesson, applied to the product: a mistyped override was silent](#the-liveness-lesson-applied-to-the-product-a-mistyped-override-was-silent)
|
||||||
|
* [Their P3 delivery, taken at the strength they gave it](#their-p3-delivery-taken-at-the-strength-they-gave-it)
|
||||||
|
|
||||||
<!-- /INDEX -->
|
<!-- /INDEX -->
|
||||||
## P0 — the exporter, 2026-08-28
|
## P0 — the exporter, 2026-08-28
|
||||||
@@ -13966,3 +13968,65 @@ starts mattering.
|
|||||||
reason to. Several of my authored values rest on them; what I have instead is
|
reason to. Several of my authored values rest on them; what I have instead is
|
||||||
their statement of the exposure and the structural argument for why it did not
|
their statement of the exposure and the structural argument for why it did not
|
||||||
fire. Recorded as that, not as verification.
|
fire. Recorded as that, not as verification.
|
||||||
|
|
||||||
|
## The liveness lesson, applied to the product: a mistyped override was silent
|
||||||
|
|
||||||
|
Every checker fix this week has been about a tool that could not tell *"I checked
|
||||||
|
and it was fine"* from *"I checked nothing"*. **The port itself 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, one in a mistyped directory:
|
||||||
|
|
||||||
|
```
|
||||||
|
mod: sprites/title/main_menu/ptbase.png <- data/mods/... ← announced
|
||||||
|
sprites/title/TYPO_menu/pteff05.png ← 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.
|
||||||
|
|
||||||
|
`ExportTree.unused_mods()` + a report at run end now lists them. Controlled both
|
||||||
|
directions: **one inert file with the typo present, silent with it removed.**
|
||||||
|
|
||||||
|
### 🔴 Getting the report's *category* right took three tries, and that is the point
|
||||||
|
|
||||||
|
* **v1 — "never used".** Flagged `data/mods/README.md` on every run. **A report
|
||||||
|
with a standing false positive is one nobody reads**, which is precisely the
|
||||||
|
failure it exists to fix.
|
||||||
|
* **v2 — "no such path in the export".** Correct, and still flagged the README:
|
||||||
|
it genuinely cannot shadow anything.
|
||||||
|
* **v3 — excluded by extension, with the rule checked rather than assumed.** The
|
||||||
|
export tree contains only `png`, `json`, `ogg`, `ogv`, `cmd` — **verified, zero
|
||||||
|
`.md` anywhere** — so a `.md` in `data/mods` could never be an override *by
|
||||||
|
construction*. Flagging a class that can never be one is noise.
|
||||||
|
|
||||||
|
📌 And the report distinguishes two things v1 conflated: a file whose path exists
|
||||||
|
in the export but **was not read this run** (a `--menu` run touches one screen) is
|
||||||
|
**not listed**. Every line printed is an override that can never apply, whatever
|
||||||
|
the run does.
|
||||||
|
|
||||||
|
⚠️ One incident worth keeping: `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 happens to reject it.
|
||||||
|
|
||||||
|
## Their P3 delivery, taken at the strength they gave it
|
||||||
|
|
||||||
|
Q6's count-match now has **disc support for its structure**: every button record
|
||||||
|
across all 16 `GP_TITLE` entries is `ptbtn00`, `ptbtn01–05`, `ptbtn11–13` — three
|
||||||
|
button screens and no fourth, with the other four destinations in their own paks.
|
||||||
|
|
||||||
|
⚠️ **Not authored from, and they said not to.** *"It shows the shape the
|
||||||
|
count-match asserts is real on the disc; it does not show that event 3 is a
|
||||||
|
particular row."* My `flow.json` already binds buttons to destinations by
|
||||||
|
measured screen rather than by event index, so nothing here changes — and if a
|
||||||
|
button-to-event map were ever needed, **there is not one**.
|
||||||
|
|
||||||
|
📌 Their negative carries its own reach, which is the part I would have got wrong:
|
||||||
|
they searched every pak for an 8-button-record build and found none, but the
|
||||||
|
search assumed DIFFICULTY's four items pair with `f` variants as `GP_TITLE`'s
|
||||||
|
screens do. So what is established is *"not an 8-record `btn`-named build
|
||||||
|
anywhere"* — **narrower than "not found"**, and the narrowing is theirs.
|
||||||
|
|||||||
@@ -104,6 +104,7 @@ func _ready() -> void:
|
|||||||
get_tree().quit(4)
|
get_tree().quit(4)
|
||||||
return
|
return
|
||||||
var export_tree := ExportTree.locate()
|
var export_tree := ExportTree.locate()
|
||||||
|
_tree = export_tree
|
||||||
if export_tree.root == "":
|
if export_tree.root == "":
|
||||||
push_error(export_tree.error)
|
push_error(export_tree.error)
|
||||||
get_tree().quit(2)
|
get_tree().quit(2)
|
||||||
@@ -735,6 +736,33 @@ func _video_finished() -> void:
|
|||||||
var _video_then: Dictionary = {}
|
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:
|
func _unhandled_input(event: InputEvent) -> void:
|
||||||
# HANDOFF Q9, measured: one (A) press skips a movie -- the title was reached
|
# HANDOFF Q9, measured: one (A) press skips a movie -- the title was reached
|
||||||
# at 57 s against a 193 s baseline.
|
# at 57 s against a 193 s baseline.
|
||||||
@@ -1204,6 +1232,10 @@ func _start_recording() -> void:
|
|||||||
|
|
||||||
|
|
||||||
func _exit_tree() -> 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:
|
if _record == null:
|
||||||
return
|
return
|
||||||
_record.set_recording_active(false)
|
_record.set_recording_active(false)
|
||||||
|
|||||||
@@ -80,6 +80,70 @@ func resolve(rel: String) -> String:
|
|||||||
return root.path_join(rel)
|
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
|
# `authored/` sits beside `export/`, never inside it: it is hand-written and
|
||||||
# committed, and a re-export must not be able to touch it.
|
# committed, and a re-export must not be able to touch it.
|
||||||
func authored(name: String) -> Variant:
|
func authored(name: String) -> Variant:
|
||||||
|
|||||||
Reference in New Issue
Block a user