homemaker-layout/tests/test_collapse_insearch.py

146 lines
6 KiB
Python
Raw Normal View History

"""Tests for the IN-SEARCH global collapse (homemaker-py-qpk, DESIGN.md §17
follow-on): running Fitness.collapse_global inside every fitness eval instead
of once at finish time.
Covers:
- default-OFF guarantee + conf-driven knobs (adjacency, iters)
- _evaluate_full wiring: collapse_global is invoked (with the right kwargs)
when the flag is on, never when it is off
- end-to-end effect on a real evolved layout, cross-checked against the
documented 94g finish-time result (DESIGN.md §17: 15 -> 12 fails on
evolved-3M-nols-3.dom)
"""
import copy
from pathlib import Path
from unittest.mock import patch
import pytest
§39.7: access requirements become a declared `usage:` attribute (homemaker-py-sel) Closes the second namespace sharing a first character with programme codes: the usage prefixes b/t/l/k, under which a room silently inherited another room's connectivity rules from its spelling. usage is a plain, MANDATORY attribute of the space definition -- not a lookup table. An interim design proposed a top-level usage_classes: table binding author-coined names to behaviour; withdrawn, because an indirect name->behaviour mapping living apart from the thing it describes is exactly the shape of the prefix rule §39 exists to remove, it would be the only such table in a schema where every other space property is a plain attribute, and the need it served was already met -- "building specific" is about what a room is CALLED, and name: is already free text. Rule that settles it: a usage value exists iff the engine treats it differently somewhere. Config selects among behaviours; it cannot invent them. - programme.USAGES (living/kitchen/bedroom/toilet/utility/none) plus the behaviour groupings PRIVATE_USAGES / PRIVATE_STRIPS / TOILET_STRIPS / SOCIABLE_USAGES. Missing or unknown usage is a load error naming the code, from BOTH parse paths. - Code-level, never leaf-level: usage_of(leaf.type) is looked up fresh, so a retype changes the class automatically. 51 sites assign leaf.type, and share/share_type plus the r5a resurrection are the precedent for why leaf-level attributes rot. - graph.has_circulation takes the usage map and trims on declared class; fitness.access and the public-access check likewise. fitness._t0 is DELETED -- no first-character type test remains anywhere in the codebase. - utility is distinct from bedroom (same access requirements today) because it is a different use and gives derive_interchange_classes an axis to relax on. - A toilet now keeps its edge to a terminal room -- the Brand adjacency, which the old b-before-t loop ordering severed. - All 107 corpus entries migrated by experiments/migrate_usage_key.py, comments and layout preserved. MEASURED -- the connectivity model was ~4x too permissive. `none` is not neutral: nothing is trimmed, so the graph may route THROUGH the room, and 34 of 52 codes had no class (Dental Surgery, Records Room, Utilities Closet all served as corridors). Edges trimmed, prefix-inferred vs declared, 3 seeds each: harbor-house 18 (9%) -> 79 (39%) inaccessible fails 0 -> 4 health-centre 12 (8%) -> 59 (40%) inaccessible fails 2 -> 3 maple-court 53 (17%) -> 123 (39%) inaccessible fails 1 -> 5 Re-baseline (seed 1, 20k, harbor): 58 fails (15h/43s) -> 61 (16h/45s), now reporting 1-inaccessible-usable-space x2 plus level 0 and level 1 not connected. The count rose because the objective got honest -- those failures were always true of the layout and the old model could not see them. Every harbor number before this was measured against a graph crediting routes through store cupboards. Sharpens §38.2: the objective pays x60-85 to delete circulation, and until now the deleted corridors were not missed because storage stood in for them. With that substitution gone, homemaker-py-2v1 is the remaining half -- and now measurable, because the fails it should prevent actually fire. 350 passed (+5 new), same 7 pre-existing fixture failures, lint unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB
2026-08-26 13:39:41 +00:00
from _helpers import with_usage
from homemaker_layout import dom as dom_mod
from homemaker_layout.dom import Node, _link_subtree
from homemaker_layout.fitness import Fitness, load_config
HARBOR = Path(__file__).parent.parent / "examples" / "harbor-house"
def _two_leaf_root(t_left: str, t_right: str, side: float = 6.0, div: float = 0.4):
from homemaker_layout import geometry
geometry.clear_cache()
root = Node(
node=[[0, 0], [side, 0], [side, side], [0, side]],
rotation=0, division=[div, div],
left=Node(type=t_left), right=Node(type=t_right),
)
_link_subtree(root, None, "")
return root
# --------------------------------------------------------------------------- #
# Defaults + conf-driven knobs
# --------------------------------------------------------------------------- #
def test_collapse_insearch_default_off():
fit = Fitness()
assert fit._collapse_insearch is False
assert fit._collapse_insearch_adjacency is True
assert fit._collapse_insearch_iters == 3
def test_collapse_insearch_flag_on():
fit = Fitness(conf={"collapse_insearch": True})
assert fit._collapse_insearch is True
def test_collapse_insearch_adjacency_knob_off():
fit = Fitness(conf={"collapse_insearch": True,
"collapse_insearch_adjacency": False})
assert fit._collapse_insearch_adjacency is False
def test_collapse_insearch_iters_knob():
fit = Fitness(conf={"collapse_insearch": True, "collapse_insearch_iters": 5})
assert fit._collapse_insearch_iters == 5
# --------------------------------------------------------------------------- #
# _evaluate_full wiring
# --------------------------------------------------------------------------- #
def test_evaluate_full_calls_collapse_global_when_on():
fit = Fitness(conf={"collapse_insearch": True, "collapse_insearch_iters": 2,
§39.7: access requirements become a declared `usage:` attribute (homemaker-py-sel) Closes the second namespace sharing a first character with programme codes: the usage prefixes b/t/l/k, under which a room silently inherited another room's connectivity rules from its spelling. usage is a plain, MANDATORY attribute of the space definition -- not a lookup table. An interim design proposed a top-level usage_classes: table binding author-coined names to behaviour; withdrawn, because an indirect name->behaviour mapping living apart from the thing it describes is exactly the shape of the prefix rule §39 exists to remove, it would be the only such table in a schema where every other space property is a plain attribute, and the need it served was already met -- "building specific" is about what a room is CALLED, and name: is already free text. Rule that settles it: a usage value exists iff the engine treats it differently somewhere. Config selects among behaviours; it cannot invent them. - programme.USAGES (living/kitchen/bedroom/toilet/utility/none) plus the behaviour groupings PRIVATE_USAGES / PRIVATE_STRIPS / TOILET_STRIPS / SOCIABLE_USAGES. Missing or unknown usage is a load error naming the code, from BOTH parse paths. - Code-level, never leaf-level: usage_of(leaf.type) is looked up fresh, so a retype changes the class automatically. 51 sites assign leaf.type, and share/share_type plus the r5a resurrection are the precedent for why leaf-level attributes rot. - graph.has_circulation takes the usage map and trims on declared class; fitness.access and the public-access check likewise. fitness._t0 is DELETED -- no first-character type test remains anywhere in the codebase. - utility is distinct from bedroom (same access requirements today) because it is a different use and gives derive_interchange_classes an axis to relax on. - A toilet now keeps its edge to a terminal room -- the Brand adjacency, which the old b-before-t loop ordering severed. - All 107 corpus entries migrated by experiments/migrate_usage_key.py, comments and layout preserved. MEASURED -- the connectivity model was ~4x too permissive. `none` is not neutral: nothing is trimmed, so the graph may route THROUGH the room, and 34 of 52 codes had no class (Dental Surgery, Records Room, Utilities Closet all served as corridors). Edges trimmed, prefix-inferred vs declared, 3 seeds each: harbor-house 18 (9%) -> 79 (39%) inaccessible fails 0 -> 4 health-centre 12 (8%) -> 59 (40%) inaccessible fails 2 -> 3 maple-court 53 (17%) -> 123 (39%) inaccessible fails 1 -> 5 Re-baseline (seed 1, 20k, harbor): 58 fails (15h/43s) -> 61 (16h/45s), now reporting 1-inaccessible-usable-space x2 plus level 0 and level 1 not connected. The count rose because the objective got honest -- those failures were always true of the layout and the old model could not see them. Every harbor number before this was measured against a graph crediting routes through store cupboards. Sharpens §38.2: the objective pays x60-85 to delete circulation, and until now the deleted corridors were not missed because storage stood in for them. With that substitution gone, homemaker-py-2v1 is the remaining half -- and now measurable, because the fails it should prevent actually fire. 350 passed (+5 new), same 7 pre-existing fixture failures, lint unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB
2026-08-26 13:39:41 +00:00
"spaces": with_usage({"b1": {"size": [16.0, 4.0], "count": 2}})})
root = _two_leaf_root("b1", "b1")
with patch.object(Fitness, "collapse_global", wraps=fit.collapse_global) as m:
fit.score_with_fails(root)
m.assert_called_once()
_, kw = m.call_args
assert kw["adjacency"] is True
assert kw["objective"] == "threshold"
assert kw["preserve_public_access"] is True
assert kw["iters"] == 2
def test_evaluate_full_does_not_call_collapse_global_when_off():
§39.7: access requirements become a declared `usage:` attribute (homemaker-py-sel) Closes the second namespace sharing a first character with programme codes: the usage prefixes b/t/l/k, under which a room silently inherited another room's connectivity rules from its spelling. usage is a plain, MANDATORY attribute of the space definition -- not a lookup table. An interim design proposed a top-level usage_classes: table binding author-coined names to behaviour; withdrawn, because an indirect name->behaviour mapping living apart from the thing it describes is exactly the shape of the prefix rule §39 exists to remove, it would be the only such table in a schema where every other space property is a plain attribute, and the need it served was already met -- "building specific" is about what a room is CALLED, and name: is already free text. Rule that settles it: a usage value exists iff the engine treats it differently somewhere. Config selects among behaviours; it cannot invent them. - programme.USAGES (living/kitchen/bedroom/toilet/utility/none) plus the behaviour groupings PRIVATE_USAGES / PRIVATE_STRIPS / TOILET_STRIPS / SOCIABLE_USAGES. Missing or unknown usage is a load error naming the code, from BOTH parse paths. - Code-level, never leaf-level: usage_of(leaf.type) is looked up fresh, so a retype changes the class automatically. 51 sites assign leaf.type, and share/share_type plus the r5a resurrection are the precedent for why leaf-level attributes rot. - graph.has_circulation takes the usage map and trims on declared class; fitness.access and the public-access check likewise. fitness._t0 is DELETED -- no first-character type test remains anywhere in the codebase. - utility is distinct from bedroom (same access requirements today) because it is a different use and gives derive_interchange_classes an axis to relax on. - A toilet now keeps its edge to a terminal room -- the Brand adjacency, which the old b-before-t loop ordering severed. - All 107 corpus entries migrated by experiments/migrate_usage_key.py, comments and layout preserved. MEASURED -- the connectivity model was ~4x too permissive. `none` is not neutral: nothing is trimmed, so the graph may route THROUGH the room, and 34 of 52 codes had no class (Dental Surgery, Records Room, Utilities Closet all served as corridors). Edges trimmed, prefix-inferred vs declared, 3 seeds each: harbor-house 18 (9%) -> 79 (39%) inaccessible fails 0 -> 4 health-centre 12 (8%) -> 59 (40%) inaccessible fails 2 -> 3 maple-court 53 (17%) -> 123 (39%) inaccessible fails 1 -> 5 Re-baseline (seed 1, 20k, harbor): 58 fails (15h/43s) -> 61 (16h/45s), now reporting 1-inaccessible-usable-space x2 plus level 0 and level 1 not connected. The count rose because the objective got honest -- those failures were always true of the layout and the old model could not see them. Every harbor number before this was measured against a graph crediting routes through store cupboards. Sharpens §38.2: the objective pays x60-85 to delete circulation, and until now the deleted corridors were not missed because storage stood in for them. With that substitution gone, homemaker-py-2v1 is the remaining half -- and now measurable, because the fails it should prevent actually fire. 350 passed (+5 new), same 7 pre-existing fixture failures, lint unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB
2026-08-26 13:39:41 +00:00
fit = Fitness(conf={"spaces": with_usage({"b1": {"size": [16.0, 4.0], "count": 2}})})
root = _two_leaf_root("b1", "b1")
with patch.object(Fitness, "collapse_global", wraps=fit.collapse_global) as m:
fit.score_with_fails(root)
m.assert_not_called()
# --------------------------------------------------------------------------- #
# End-to-end: matches the documented 94g finish-time result
# --------------------------------------------------------------------------- #
@pytest.mark.skipif(not HARBOR.is_dir(), reason="harbor-house example absent")
Make both failing tests assert their intent, not stale artefacts The suite is green for the first time this session: 376 passed, 0 failed. test_collapse_insearch_reproduces_94g_finish_time_result hard-coded both endpoints of the 17 result -- 15 fails before collapse, 12 after. Those were measured before 39.4, when harbor's effective programme was silently 32 instances because codes like cr1 were read as generic circulation; the same layout now scores 82. But the guarantee the test exists to protect, per its own docstring, is that in-search collapse reaches the SAME layout as finish-time collapse on fixed geometry -- and two independent constants never checked that. They can both drift and stay equal, or both hold and mask an inequality. Rewritten to compute both sides live and assert they agree, plus that collapse does not make the layout worse. Measured: 82 -> 58 in-search, and finish-time collapse independently reaches 58 at iters=3 and iters=6. The invariant holds; only the constants were stale. Restating the reference figure itself remains homemaker-py-ut5. test_classify_fail_tier_covers_full_corpus globbed examples/**/*.fails and asserted checked > 0. Git tracks ZERO .fails -- they are artefacts the scorer writes beside a .dom -- so its docstring described files that by design never exist in the repo, and it passed only on a machine that had already run the scorer. Split into: a test that GENERATES fails by scoring three corpus layouts picked for breadth (requiring >= 8 distinct kinds so it cannot silently narrow); a test that an unclassifiable string actually raises; and an opportunistic .fails sweep that never requires them. Verified by moving every .fails out of the tree and re-running. Closes homemaker-py-1ue. Lint at parity (46). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB
2026-08-29 07:11:25 +00:00
def test_collapse_insearch_matches_finish_time_collapse():
"""in-search collapse must reach the SAME layout as finish-time collapse.
That equality is the actual guarantee: `collapse_insearch` runs the same
`collapse_global` earlier in the same pipeline (before the Phase-1 checks
rather than after the whole search), so on a FIXED geometry the two must
agree. Both sides are computed here rather than hard-coded.
This test previously asserted the §17 constants directly -- `15` fails
before collapse and `12` after. Those were measured before §39.4, when
harbor's effective programme was silently 32 instances because codes like
Replace the stale 15-fail acceptance target with the cold-start baseline DESIGN.md 38.7 pinned Phase 9's acceptance test to "harbor-house reaches its known 15-fail floor". That figure was measured before 39.4, against harbor's *effective* programme of 32 instances -- cr1/of/st1/st2 were being read as generic circulation and silently dropped. Against the real 37-instance programme the same artefact scores 89, so the target is not measurable; and 2v1, the fix it was the acceptance test for, closed NULL (39.8), so there is no combined fix left to accept. New 39.12 records: * the five evolved-3M*.dom artefacts rescored under the current objective (69/85/87/89/145), and why they are not a floor -- they were evolved under one objective and are scored under another; * that the bead's migration premise is stale: experiments/migrate_ju3_rename.py does not exist, because ju3 tightened the matching rule at source (39.3) instead, so the old artefacts parse correctly with no migration; * the 4x3-seed 500k cold-start baseline (~430 h) as the reference from here, with each programme's sd and its minimum detectable difference at n=3 -- harbor mean 39.3, sd 5.5, MDD 13.7; * zero missing-space fails in all twelve runs: the dominant term in the 3M artefacts is not one the live search still fails on; * crinkliness at 112 of the 321 corpus fails (35%), all soft, all in the regime 9gj says quality_uncrinkliness returns a flat 0.0 for -- the largest single component of the residual is one the objective cannot descend; * 66 of the 84 hard fails as one access-topology family (not-adjacent-to, inaccessible usable space, not connected), mechanism in 39.9. 38.7's acceptance paragraph is annotated in place rather than rewritten. The connectivity clause is demoted to a separately tracked standing defect: it appears in 10 of the 12 baseline runs. tests/test_collapse_insearch.py carried its own stale "82 -> 58"; the same layout now scores 89 -> 64, so the docstring dates the figure instead of asserting a current one. Closes homemaker-py-ut5. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB
2026-09-04 17:16:10 +00:00
`cr1` were being read as generic circulation; immediately after §39.4 the
same layout scored 82 -> 58, and it has moved again since. Pinning the
endpoints made a live invariant fail whenever the
Make both failing tests assert their intent, not stale artefacts The suite is green for the first time this session: 376 passed, 0 failed. test_collapse_insearch_reproduces_94g_finish_time_result hard-coded both endpoints of the 17 result -- 15 fails before collapse, 12 after. Those were measured before 39.4, when harbor's effective programme was silently 32 instances because codes like cr1 were read as generic circulation; the same layout now scores 82. But the guarantee the test exists to protect, per its own docstring, is that in-search collapse reaches the SAME layout as finish-time collapse on fixed geometry -- and two independent constants never checked that. They can both drift and stay equal, or both hold and mask an inequality. Rewritten to compute both sides live and assert they agree, plus that collapse does not make the layout worse. Measured: 82 -> 58 in-search, and finish-time collapse independently reaches 58 at iters=3 and iters=6. The invariant holds; only the constants were stale. Restating the reference figure itself remains homemaker-py-ut5. test_classify_fail_tier_covers_full_corpus globbed examples/**/*.fails and asserted checked > 0. Git tracks ZERO .fails -- they are artefacts the scorer writes beside a .dom -- so its docstring described files that by design never exist in the repo, and it passed only on a machine that had already run the scorer. Split into: a test that GENERATES fails by scoring three corpus layouts picked for breadth (requiring >= 8 distinct kinds so it cannot silently narrow); a test that an unclassifiable string actually raises; and an opportunistic .fails sweep that never requires them. Verified by moving every .fails out of the tree and re-running. Closes homemaker-py-1ue. Lint at parity (46). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB
2026-08-29 07:11:25 +00:00
programme or the objective legitimately changed, while not actually
checking the invariant at all (two independent constants can both drift and
Replace the stale 15-fail acceptance target with the cold-start baseline DESIGN.md 38.7 pinned Phase 9's acceptance test to "harbor-house reaches its known 15-fail floor". That figure was measured before 39.4, against harbor's *effective* programme of 32 instances -- cr1/of/st1/st2 were being read as generic circulation and silently dropped. Against the real 37-instance programme the same artefact scores 89, so the target is not measurable; and 2v1, the fix it was the acceptance test for, closed NULL (39.8), so there is no combined fix left to accept. New 39.12 records: * the five evolved-3M*.dom artefacts rescored under the current objective (69/85/87/89/145), and why they are not a floor -- they were evolved under one objective and are scored under another; * that the bead's migration premise is stale: experiments/migrate_ju3_rename.py does not exist, because ju3 tightened the matching rule at source (39.3) instead, so the old artefacts parse correctly with no migration; * the 4x3-seed 500k cold-start baseline (~430 h) as the reference from here, with each programme's sd and its minimum detectable difference at n=3 -- harbor mean 39.3, sd 5.5, MDD 13.7; * zero missing-space fails in all twelve runs: the dominant term in the 3M artefacts is not one the live search still fails on; * crinkliness at 112 of the 321 corpus fails (35%), all soft, all in the regime 9gj says quality_uncrinkliness returns a flat 0.0 for -- the largest single component of the residual is one the objective cannot descend; * 66 of the 84 hard fails as one access-topology family (not-adjacent-to, inaccessible usable space, not connected), mechanism in 39.9. 38.7's acceptance paragraph is annotated in place rather than rewritten. The connectivity clause is demoted to a separately tracked standing defect: it appears in 10 of the 12 baseline runs. tests/test_collapse_insearch.py carried its own stale "82 -> 58"; the same layout now scores 89 -> 64, so the docstring dates the figure instead of asserting a current one. Closes homemaker-py-ut5. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB
2026-09-04 17:16:10 +00:00
still be equal, or both hold and mask an inequality). At the time of
writing the same layout scores 89 -> 64; DESIGN.md §39.12 restates the
reference figure itself and says why no constant belongs here.
Make both failing tests assert their intent, not stale artefacts The suite is green for the first time this session: 376 passed, 0 failed. test_collapse_insearch_reproduces_94g_finish_time_result hard-coded both endpoints of the 17 result -- 15 fails before collapse, 12 after. Those were measured before 39.4, when harbor's effective programme was silently 32 instances because codes like cr1 were read as generic circulation; the same layout now scores 82. But the guarantee the test exists to protect, per its own docstring, is that in-search collapse reaches the SAME layout as finish-time collapse on fixed geometry -- and two independent constants never checked that. They can both drift and stay equal, or both hold and mask an inequality. Rewritten to compute both sides live and assert they agree, plus that collapse does not make the layout worse. Measured: 82 -> 58 in-search, and finish-time collapse independently reaches 58 at iters=3 and iters=6. The invariant holds; only the constants were stale. Restating the reference figure itself remains homemaker-py-ut5. test_classify_fail_tier_covers_full_corpus globbed examples/**/*.fails and asserted checked > 0. Git tracks ZERO .fails -- they are artefacts the scorer writes beside a .dom -- so its docstring described files that by design never exist in the repo, and it passed only on a machine that had already run the scorer. Split into: a test that GENERATES fails by scoring three corpus layouts picked for breadth (requiring >= 8 distinct kinds so it cannot silently narrow); a test that an unclassifiable string actually raises; and an opportunistic .fails sweep that never requires them. Verified by moving every .fails out of the tree and re-running. Closes homemaker-py-1ue. Lint at parity (46). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB
2026-08-29 07:11:25 +00:00
"""
conf, cost = load_config(HARBOR)
conf_ci, _ = load_config(HARBOR, overrides={"collapse_insearch": True})
root = dom_mod.load(str(HARBOR / "evolved-3M-nols-3.dom"))
Make both failing tests assert their intent, not stale artefacts The suite is green for the first time this session: 376 passed, 0 failed. test_collapse_insearch_reproduces_94g_finish_time_result hard-coded both endpoints of the 17 result -- 15 fails before collapse, 12 after. Those were measured before 39.4, when harbor's effective programme was silently 32 instances because codes like cr1 were read as generic circulation; the same layout now scores 82. But the guarantee the test exists to protect, per its own docstring, is that in-search collapse reaches the SAME layout as finish-time collapse on fixed geometry -- and two independent constants never checked that. They can both drift and stay equal, or both hold and mask an inequality. Rewritten to compute both sides live and assert they agree, plus that collapse does not make the layout worse. Measured: 82 -> 58 in-search, and finish-time collapse independently reaches 58 at iters=3 and iters=6. The invariant holds; only the constants were stale. Restating the reference figure itself remains homemaker-py-ut5. test_classify_fail_tier_covers_full_corpus globbed examples/**/*.fails and asserted checked > 0. Git tracks ZERO .fails -- they are artefacts the scorer writes beside a .dom -- so its docstring described files that by design never exist in the repo, and it passed only on a machine that had already run the scorer. Split into: a test that GENERATES fails by scoring three corpus layouts picked for breadth (requiring >= 8 distinct kinds so it cannot silently narrow); a test that an unclassifiable string actually raises; and an opportunistic .fails sweep that never requires them. Verified by moving every .fails out of the tree and re-running. Closes homemaker-py-1ue. Lint at parity (46). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB
2026-08-29 07:11:25 +00:00
_, f_base = Fitness(conf, cost).score_with_fails(copy.deepcopy(root))
_, f_ci = Fitness(conf_ci, cost).score_with_fails(copy.deepcopy(root))
Make both failing tests assert their intent, not stale artefacts The suite is green for the first time this session: 376 passed, 0 failed. test_collapse_insearch_reproduces_94g_finish_time_result hard-coded both endpoints of the 17 result -- 15 fails before collapse, 12 after. Those were measured before 39.4, when harbor's effective programme was silently 32 instances because codes like cr1 were read as generic circulation; the same layout now scores 82. But the guarantee the test exists to protect, per its own docstring, is that in-search collapse reaches the SAME layout as finish-time collapse on fixed geometry -- and two independent constants never checked that. They can both drift and stay equal, or both hold and mask an inequality. Rewritten to compute both sides live and assert they agree, plus that collapse does not make the layout worse. Measured: 82 -> 58 in-search, and finish-time collapse independently reaches 58 at iters=3 and iters=6. The invariant holds; only the constants were stale. Restating the reference figure itself remains homemaker-py-ut5. test_classify_fail_tier_covers_full_corpus globbed examples/**/*.fails and asserted checked > 0. Git tracks ZERO .fails -- they are artefacts the scorer writes beside a .dom -- so its docstring described files that by design never exist in the repo, and it passed only on a machine that had already run the scorer. Split into: a test that GENERATES fails by scoring three corpus layouts picked for breadth (requiring >= 8 distinct kinds so it cannot silently narrow); a test that an unclassifiable string actually raises; and an opportunistic .fails sweep that never requires them. Verified by moving every .fails out of the tree and re-running. Closes homemaker-py-1ue. Lint at parity (46). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB
2026-08-29 07:11:25 +00:00
# the same collapse, applied once at finish time, scored canonically
finished = copy.deepcopy(root)
Fitness(conf, cost).collapse_global(
finished, adjacency=True, objective="threshold",
preserve_public_access=True, iters=3)
_, f_finish = Fitness(conf, cost).score_with_fails(finished)
assert len(f_ci) == len(f_finish), (
"in-search collapse diverged from finish-time collapse on fixed geometry")
assert len(f_ci) < len(f_base), "collapse must not make the layout worse"
@pytest.mark.skipif(not HARBOR.is_dir(), reason="harbor-house example absent")
def test_collapse_insearch_off_reproduces_baseline():
conf, cost = load_config(HARBOR)
fit = Fitness(conf, cost)
root = dom_mod.load(str(HARBOR / "evolved-3M-nols-3.dom"))
s1, f1 = fit.score_with_fails(copy.deepcopy(root))
s2, f2 = Fitness(conf, cost).score_with_fails(copy.deepcopy(root))
assert s1 == pytest.approx(s2)
assert f1 == f2