FAILURE MAP
← Case archive

FA-80916 / Music interval and transposition theory / Open access

Spelled transposition by named interval: letter moved by the full generic number · case 01

A major third up from C is spelled on F (Fb4) instead of E4, because the letter advances one step too far.

Verified by executionVariant 1 · 8 checks per implementationDownload source bundle ↓JSON ↗

ROOT CAUSE

The letter displacement uses the generic number itself, but a third spans only two letter steps.

VERIFIED REPAIR

Restore the letter step count step so that it reads `sign * (number - 1)`.

Unsuccessful approach: Reducing the displacement modulo seven drops compound octaves and turns every downward move into an upward letter move.

Case contract

Input [note, interval, direction]. note: letter, # or b accidentals, nonnegative octave. interval: quality letter (P, M, m, A, d) and generic number >= 1 with perfect-class numbers 1, 4, 5 (compounds reduce by 7). Direction up or down moves the letter by number-1 steps and the pitch by the interval size; the result is spelled with the moved letter, octave taken from the letter position. Results needing more than two accidentals, or a negative octave, return None.

Why this case matters

Pitch spelling and interval arithmetic decide the notes that notation, transposition and analysis tools display or play.

1 / The failure

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(x):
    base = {'C': 0, 'D': 2, 'E': 4, 'F': 5, 'G': 7, 'A': 9, 'B': 11}
    if not isinstance(x, list) or len(x) != 3:
        return None
    note, ivl, direction = x
    if direction not in ('up', 'down'):
        return None
    if not isinstance(note, str) or len(note) < 2 or note[0] not in base:
        return None
    i = 1
    acc = 0
    while i < len(note) and note[i] in '#b':
        acc += 1 if note[i] == '#' else -1
        i += 1
    if not note[i:].isdigit():
        return None
    octave = int(note[i:])
    if not isinstance(ivl, str) or len(ivl) < 2 or not ivl[1:].isdigit():
        return None
    quality = ivl[0]
    number = int(ivl[1:])
    if number < 1:
        return None
    simple = (number - 1) % 7 + 1
    perfect = simple in (1, 4, 5)
    offsets = {'P': 0, 'A': 1, 'd': -1} if perfect else {'M': 0, 'm': -1, 'A': 1, 'd': -2}
    if quality not in offsets:
        return None
    size = [0, 2, 4, 5, 7, 9, 11][simple - 1] + 12 * ((number - 1) // 7) + offsets[quality]
    sign = 1 if direction == 'up' else -1
    idx = 'CDEFGAB'.index(note[0]) + 7 * octave + sign * number
    pitch = 12 * octave + base[note[0]] + acc + sign * size
    letter = 'CDEFGAB'[idx % 7]
    new_octave = idx // 7
    new_acc = pitch - (12 * new_octave + base[letter])
    if abs(new_acc) > 2 or new_octave < 0:
        return None
    return letter + ('#' * new_acc if new_acc > 0 else 'b' * -new_acc) + str(new_octave)
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[(['C4', 'M3', 'up'], 'E4'), (['C4', 'M3', 'down'], 'Ab3'), (['B3', 'm2', 'up'], 'C4'), (['Cb4', 'd3', 'up'], None), (['C4', 'M9', 'up'], 'D5'), (['C0', 'm2', 'down'], None), (['C4', 'X3', 'up'], None), (['C4', 'M4', 'up'], None)], [(['C4', 'M3', 'up'], 'E4'), (['C4', 'M3', 'down'], 'Ab3'), (['B3', 'm2', 'up'], 'C4'), (['C4', 'm2', 'down'], 'B3'), (['C4', 'M10', 'down'], 'Ab2'), (['C4', 'M4', 'up'], None), (['C4', 'P3', 'up'], None), (['C4', 'M3', 'sideways'], None)], [(['C4', 'M3', 'up'], 'E4'), (['C4', 'M3', 'down'], 'Ab3'), (['B3', 'm2', 'up'], 'C4'), (['C4', 'm2', 'down'], 'B3'), (['E4', 'A4', 'up'], 'A#4'), (['F#4', 'A4', 'up'], 'B#4'), (['C4', 'P8', 'up'], 'C5'), (['C5', 'P15', 'down'], 'C3')], [(['C4', 'm2', 'down'], 'B3'), (['E4', 'A4', 'up'], 'A#4'), (['F#4', 'A4', 'up'], 'B#4'), (['C4', 'd5', 'up'], 'Gb4'), (['C4', 'd7', 'up'], 'Bbb4'), (['C4', 'd3', 'up'], 'Ebb4'), (['C4', 'M9', 'up'], 'D5'), (['D4', 'd4', 'down'], 'A#3')], [(['E4', 'A4', 'up'], 'A#4'), (['C4', 'd5', 'up'], 'Gb4'), (['C4', 'd7', 'up'], 'Bbb4'), (['C4', 'd3', 'up'], 'Ebb4'), (['Db4', 'd3', 'up'], 'Fbb4'), (['C4', 'P8', 'up'], 'C5'), (['C4', 'M10', 'down'], 'Ab2'), (['F4', 'A4', 'down'], 'Cb4')]]
for i, (args, expected) in enumerate(fixtures[N-1]):
    check("oracle %d" % i, solve(args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
Boundary fixtureActualExpectedOutcome
oracle 0Fb4E4Failed
oracle 1G#3Ab3Failed
oracle 2Dbb4C4Failed
oracle 3NoneNonePassed
oracle 4Ebb5D5Failed
oracle 5NoneNonePassed
oracle 6NoneNonePassed
oracle 7NoneNonePassed

SHA-256 / 025b196689b08770daa015dd9b7b548e535f0ea4557d0bd077ac02f0072e2674

2 / The unsuccessful fix

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(x):
    base = {'C': 0, 'D': 2, 'E': 4, 'F': 5, 'G': 7, 'A': 9, 'B': 11}
    if not isinstance(x, list) or len(x) != 3:
        return None
    note, ivl, direction = x
    if direction not in ('up', 'down'):
        return None
    if not isinstance(note, str) or len(note) < 2 or note[0] not in base:
        return None
    i = 1
    acc = 0
    while i < len(note) and note[i] in '#b':
        acc += 1 if note[i] == '#' else -1
        i += 1
    if not note[i:].isdigit():
        return None
    octave = int(note[i:])
    if not isinstance(ivl, str) or len(ivl) < 2 or not ivl[1:].isdigit():
        return None
    quality = ivl[0]
    number = int(ivl[1:])
    if number < 1:
        return None
    simple = (number - 1) % 7 + 1
    perfect = simple in (1, 4, 5)
    offsets = {'P': 0, 'A': 1, 'd': -1} if perfect else {'M': 0, 'm': -1, 'A': 1, 'd': -2}
    if quality not in offsets:
        return None
    size = [0, 2, 4, 5, 7, 9, 11][simple - 1] + 12 * ((number - 1) // 7) + offsets[quality]
    sign = 1 if direction == 'up' else -1
    idx = 'CDEFGAB'.index(note[0]) + 7 * octave + sign * (number - 1) % 7
    pitch = 12 * octave + base[note[0]] + acc + sign * size
    letter = 'CDEFGAB'[idx % 7]
    new_octave = idx // 7
    new_acc = pitch - (12 * new_octave + base[letter])
    if abs(new_acc) > 2 or new_octave < 0:
        return None
    return letter + ('#' * new_acc if new_acc > 0 else 'b' * -new_acc) + str(new_octave)
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[(['C4', 'M3', 'up'], 'E4'), (['C4', 'M3', 'down'], 'Ab3'), (['B3', 'm2', 'up'], 'C4'), (['Cb4', 'd3', 'up'], None), (['C4', 'M9', 'up'], 'D5'), (['C0', 'm2', 'down'], None), (['C4', 'X3', 'up'], None), (['C4', 'M4', 'up'], None)], [(['C4', 'M3', 'up'], 'E4'), (['C4', 'M3', 'down'], 'Ab3'), (['B3', 'm2', 'up'], 'C4'), (['C4', 'm2', 'down'], 'B3'), (['C4', 'M10', 'down'], 'Ab2'), (['C4', 'M4', 'up'], None), (['C4', 'P3', 'up'], None), (['C4', 'M3', 'sideways'], None)], [(['C4', 'M3', 'up'], 'E4'), (['C4', 'M3', 'down'], 'Ab3'), (['B3', 'm2', 'up'], 'C4'), (['C4', 'm2', 'down'], 'B3'), (['E4', 'A4', 'up'], 'A#4'), (['F#4', 'A4', 'up'], 'B#4'), (['C4', 'P8', 'up'], 'C5'), (['C5', 'P15', 'down'], 'C3')], [(['C4', 'm2', 'down'], 'B3'), (['E4', 'A4', 'up'], 'A#4'), (['F#4', 'A4', 'up'], 'B#4'), (['C4', 'd5', 'up'], 'Gb4'), (['C4', 'd7', 'up'], 'Bbb4'), (['C4', 'd3', 'up'], 'Ebb4'), (['C4', 'M9', 'up'], 'D5'), (['D4', 'd4', 'down'], 'A#3')], [(['E4', 'A4', 'up'], 'A#4'), (['C4', 'd5', 'up'], 'Gb4'), (['C4', 'd7', 'up'], 'Bbb4'), (['C4', 'd3', 'up'], 'Ebb4'), (['Db4', 'd3', 'up'], 'Fbb4'), (['C4', 'P8', 'up'], 'C5'), (['C4', 'M10', 'down'], 'Ab2'), (['F4', 'A4', 'down'], 'Cb4')]]
for i, (args, expected) in enumerate(fixtures[N-1]):
    check("oracle %d" % i, solve(args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
Boundary fixtureActualExpectedOutcome
oracle 0E4E4Passed
oracle 1NoneAb3Failed
oracle 2C4C4Passed
oracle 3NoneNonePassed
oracle 4NoneD5Failed
oracle 5NoneNonePassed
oracle 6NoneNonePassed
oracle 7NoneNonePassed

SHA-256 / 92007f0f2d274e1561ac2f9df72d91f8e782ee1653d2b73280515cf33bdf9433

3 / The verified repair

Exit 0
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(x):
    base = {'C': 0, 'D': 2, 'E': 4, 'F': 5, 'G': 7, 'A': 9, 'B': 11}
    if not isinstance(x, list) or len(x) != 3:
        return None
    note, ivl, direction = x
    if direction not in ('up', 'down'):
        return None
    if not isinstance(note, str) or len(note) < 2 or note[0] not in base:
        return None
    i = 1
    acc = 0
    while i < len(note) and note[i] in '#b':
        acc += 1 if note[i] == '#' else -1
        i += 1
    if not note[i:].isdigit():
        return None
    octave = int(note[i:])
    if not isinstance(ivl, str) or len(ivl) < 2 or not ivl[1:].isdigit():
        return None
    quality = ivl[0]
    number = int(ivl[1:])
    if number < 1:
        return None
    simple = (number - 1) % 7 + 1
    perfect = simple in (1, 4, 5)
    offsets = {'P': 0, 'A': 1, 'd': -1} if perfect else {'M': 0, 'm': -1, 'A': 1, 'd': -2}
    if quality not in offsets:
        return None
    size = [0, 2, 4, 5, 7, 9, 11][simple - 1] + 12 * ((number - 1) // 7) + offsets[quality]
    sign = 1 if direction == 'up' else -1
    idx = 'CDEFGAB'.index(note[0]) + 7 * octave + sign * (number - 1)
    pitch = 12 * octave + base[note[0]] + acc + sign * size
    letter = 'CDEFGAB'[idx % 7]
    new_octave = idx // 7
    new_acc = pitch - (12 * new_octave + base[letter])
    if abs(new_acc) > 2 or new_octave < 0:
        return None
    return letter + ('#' * new_acc if new_acc > 0 else 'b' * -new_acc) + str(new_octave)
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[(['C4', 'M3', 'up'], 'E4'), (['C4', 'M3', 'down'], 'Ab3'), (['B3', 'm2', 'up'], 'C4'), (['Cb4', 'd3', 'up'], None), (['C4', 'M9', 'up'], 'D5'), (['C0', 'm2', 'down'], None), (['C4', 'X3', 'up'], None), (['C4', 'M4', 'up'], None)], [(['C4', 'M3', 'up'], 'E4'), (['C4', 'M3', 'down'], 'Ab3'), (['B3', 'm2', 'up'], 'C4'), (['C4', 'm2', 'down'], 'B3'), (['C4', 'M10', 'down'], 'Ab2'), (['C4', 'M4', 'up'], None), (['C4', 'P3', 'up'], None), (['C4', 'M3', 'sideways'], None)], [(['C4', 'M3', 'up'], 'E4'), (['C4', 'M3', 'down'], 'Ab3'), (['B3', 'm2', 'up'], 'C4'), (['C4', 'm2', 'down'], 'B3'), (['E4', 'A4', 'up'], 'A#4'), (['F#4', 'A4', 'up'], 'B#4'), (['C4', 'P8', 'up'], 'C5'), (['C5', 'P15', 'down'], 'C3')], [(['C4', 'm2', 'down'], 'B3'), (['E4', 'A4', 'up'], 'A#4'), (['F#4', 'A4', 'up'], 'B#4'), (['C4', 'd5', 'up'], 'Gb4'), (['C4', 'd7', 'up'], 'Bbb4'), (['C4', 'd3', 'up'], 'Ebb4'), (['C4', 'M9', 'up'], 'D5'), (['D4', 'd4', 'down'], 'A#3')], [(['E4', 'A4', 'up'], 'A#4'), (['C4', 'd5', 'up'], 'Gb4'), (['C4', 'd7', 'up'], 'Bbb4'), (['C4', 'd3', 'up'], 'Ebb4'), (['Db4', 'd3', 'up'], 'Fbb4'), (['C4', 'P8', 'up'], 'C5'), (['C4', 'M10', 'down'], 'Ab2'), (['F4', 'A4', 'down'], 'Cb4')]]
for i, (args, expected) in enumerate(fixtures[N-1]):
    check("oracle %d" % i, solve(args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
Boundary fixtureActualExpectedOutcome
oracle 0E4E4Passed
oracle 1Ab3Ab3Passed
oracle 2C4C4Passed
oracle 3NoneNonePassed
oracle 4D5D5Passed
oracle 5NoneNonePassed
oracle 6NoneNonePassed
oracle 7NoneNonePassed

SHA-256 / 35886f0425abb114b99a9bd971915085f6428d96df3a92e85c807241185c32b1

Verification & scope

A deterministic bounded teaching model with a stipulated toy contract; it is not a complete music notation or theory engine. This reproducer isolates one failure mechanism. Results cover the supplied fixtures. Variants within a family share a test contract and should remain grouped when constructing evaluation splits. Related mechanisms with a shared evaluation_group must also remain together; these controlled models are not independent production incidents.

Observations recorded using Python 3.12.14 at 2026-09-29T14:49:58.077875+00:00.

Case digest / 9c7f57be4e4f65801a1e1105bbd51bcf4f83092760564bd2510d0dda0d577468