Skip to content

Replace the node score sentinel with an explicit valid flag - #99

Merged
rasros merged 1 commit into
mainfrom
node-valid-flag
Aug 18, 2026
Merged

Replace the node score sentinel with an explicit valid flag#99
rasros merged 1 commit into
mainfrom
node-valid-flag

Conversation

@rasros

@rasros rasros commented Aug 17, 2026

Copy link
Copy Markdown
Owner

SearchNode.score was a float holding one of two sentinel values. Nothing read it as a quantity — every one of its 47 references was a < INVALID_SCORE validity check — but it read as a quality anyone could sort by, and three separate defects this week came from something sorting by it after it had stopped meaning anything:

  • epoch_parents sorted its Pareto front by it, a no-op ordering
  • select_survivors picked the elite by min(key=n.score), which would have raised at runtime
  • node filenames led with it, so the best-named file on disk was not the artifact the evaluator chose

What it becomes

SearchNode.valid: bool — the candidate's markup parsed, it rasterized, and the measures computed. Not a quality judgement and not orderable: order=True is gone, because ordering by a constant is what caused the above. Candidates are ranked by dominance over their measures, which needs a population, so a single node has no position of its own.

Result.score becomes Result.measured: bool — whether the measures have been taken yet. That is all it encoded: a result crosses the queue once before anything is known about it, and Result.valid already existed alongside it.

ChainState.score is gone; it duplicated the node's.

What went with it

INVALID_SCORE, VALID_SCORE, valid_scores() and score_std(), plus the statistics derived from them — pool_score_std in stats.csv and the spread readout in the dashboard. A spread of a constant measures nothing. Crowding distance keeps an infinity sentinel, now math.inf, which is a genuine infinite distance rather than a disguised boolean.

Elitism is redefined. It reserved a slot for the best score; with no single best it now guarantees a member of the top Copeland tier survives, which is the same intent — the majority relation cycles and crowding distance sorts for spread, so a generation can otherwise evict everything unbeaten.

--resume-top and clean_runs.py read the older filename shapes, and plot_run.py's front plot now draws two objectives against each other rather than a metric against a score, since there is no score axis to draw.

Scale

28 files, 428 insertions, 544 deletions. The test migration was the bulk of it: roughly 200 call sites constructed nodes or results with a score. I first tried it as a bulk regex, which produced more failures than it fixed and mangled several lines outright; that was reverted and the files were migrated one at a time, running each before moving on.

@rasros
rasros changed the base branch from occasional-panel to main August 18, 2026 05:20
@rasros
rasros merged commit 9c71120 into main Aug 18, 2026
1 check passed
@rasros
rasros deleted the node-valid-flag branch August 18, 2026 05:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant