One missing patch says one thing

A patch file that could not be read left the name unregistered, so every
#Voice naming it failed as well, and then the check that a voice has an
instrument failed for each of those. One wrong path produced seven
messages and only the first was worth reading.

A patch that cannot be read is still a patch that was NAMED. It is
registered either way now, with its bytes marked missing, so everything
below resolves the name and says nothing. Nothing is written regardless -
one problem is enough to stop that - so a patch with no bytes never
reaches a file.

The damage from the old behaviour was not the extra lines. It is that a
compiler which says one thing seven ways teaches people to read the last
line, which is the one that matters least.

Checked by counting: one missing patch, three voices using it, and the
count of messages mentioning it has to be one. break.sh confirms it by
putting the old behaviour back on the failure path alone - the first
attempt at that break stopped every tune compiling and the disk build
failed before any test ran, which is break.sh being right about a break
that proved nothing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
This commit is contained in:
Anachronaut
2026-09-05 22:09:18 -04:00
co-authored by Claude Opus 5
parent fb672d7761
commit fee2b1ef10
2 changed files with 39 additions and 5 deletions
+23 -5
View File
@@ -62,6 +62,7 @@ typedef struct {
unsigned char bytes[512]; unsigned char bytes[512];
int length; int length;
int line; int line;
int bad; // Named, but its bytes could not be had. See doPatch.
} Patch; } Patch;
typedef struct { typedef struct {
@@ -215,26 +216,41 @@ static void doPatch(void) {
complain(name->line, "that patch is named twice", name->text); complain(name->line, "that patch is named twice", name->text);
return; return;
} }
// ---- A PATCH THAT CANNOT BE READ IS STILL A PATCH THAT WAS NAMED ----
//
// It is registered either way, with its bytes marked missing. Nothing will be written -
// one problem is enough to stop that - but everything below can now resolve the name, so
// a single missing file says one thing instead of seven.
//
// It used to return here, and the damage was out of all proportion: the name went
// unregistered, so every #Voice naming it failed as well, and then the check that a voice
// has an instrument failed for each of those. One wrong path, seven messages, and only
// the first of them worth reading. A compiler that says one thing seven ways teaches
// people to read the last line, which is the one that matters least.
Patch *p = &patches[patchCount];
snprintf(p->name, MAX_NAME, "%s", name->text);
p->line = name->line;
p->length = 0;
p->bad = 0;
patchCount++;
char where[1024]; char where[1024];
FILE *in = openPatch(file->text, where, sizeof(where)); FILE *in = openPatch(file->text, where, sizeof(where));
if (!in) { if (!in) {
complain(file->line, "cannot find that patch beside the tune or on the -I path", complain(file->line, "cannot find that patch beside the tune or on the -I path",
file->text); file->text);
p->bad = 1;
return; return;
} }
Patch *p = &patches[patchCount];
snprintf(p->name, MAX_NAME, "%s", name->text);
p->length = (int)fread(p->bytes, 1, sizeof(p->bytes), in); p->length = (int)fread(p->bytes, 1, sizeof(p->bytes), in);
p->line = name->line;
fclose(in); fclose(in);
// A patch is a count and that many pairs. Anything else is a file that is not one, and // A patch is a count and that many pairs. Anything else is a file that is not one, and
// embedding it would make a tune that writes rubbish at the sound device. // embedding it would make a tune that writes rubbish at the sound device.
if (p->length < 1 || p->length != 1 + p->bytes[0] * 2) { if (p->length < 1 || p->length != 1 + p->bytes[0] * 2) {
complain(file->line, "that is not a patch: SoundPatch --blob writes one", file->text); complain(file->line, "that is not a patch: SoundPatch --blob writes one", file->text);
return; p->bad = 1;
} }
patchCount++;
} }
static void doVoice(void) { static void doVoice(void) {
@@ -269,6 +285,8 @@ static void doSequence(void) {
return; return;
} }
if (findSequence(name->text) >= 0) { if (findSequence(name->text) >= 0) {
// The first one keeps the name, so an order list naming it still resolves and the
// duplicate is reported once rather than once here and again at every use.
complain(name->line, "that sequence is named twice", name->text); complain(name->line, "that sequence is named twice", name->text);
return; return;
} }
+16
View File
@@ -862,6 +862,22 @@ PY2
refuses "a duration is from" \ refuses "a duration is from" \
"#Tick 0d125000" "#Patch A low.patch" "#Voice 0d0 A" "#Sequence S" "0d60 0d0" \ "#Tick 0d125000" "#Patch A low.patch" "#Voice 0d0 A" "#Sequence S" "0d60 0d0" \
"#Order 0d0" "S" "#Order 0d0" "S"
# ---- AND IT SAYS IT ONCE ----
#
# One missing patch used to be seven messages: the name went unregistered, so every #Voice
# naming it failed too, and then the check that a voice has an instrument failed for each
# of those. Only the first was worth reading. A compiler that says one thing seven ways
# teaches people to read the last line, which is the one that matters least.
printf '%s\n' "#Tick 0d125000" "#Patch A nosuch.patch" \
"#Voice 0d0 A" "#Voice 0d1 A" "#Voice 0d2 A" \
"#Sequence S" "0d60 0d4" \
"#Order 0d0" "S" "#Order 0d1" "S" "#Order 0d2" "S" > "$BUILD/cascade.tune.txt"
SAID="$("$ROOT/TuneC" "$BUILD/cascade.tune.txt" "$BUILD/cascade.tune" 2>&1 \
| grep -c "cannot find\|no patch of that name\|no instrument")"
[ "$SAID" = "1" ] \
&& result ok "TuneC says a missing patch once" "one message, not one for every use of it" \
|| result no "TuneC says a missing patch once" "$SAID messages for one missing file"
refuses "no sequence of that name" \ refuses "no sequence of that name" \
"#Tick 0d125000" "#Patch A low.patch" "#Voice 0d0 A" "#Sequence S" "0d60 0d4" \ "#Tick 0d125000" "#Patch A low.patch" "#Voice 0d0 A" "#Sequence S" "0d60 0d4" \
"#Order 0d0" "Nope" "#Order 0d0" "Nope"