Part of #887 — the known limit documented (not fixed) in #919.
dsl::pipeline::pack_aod_rectangles is the fourth caller of BusGridContext::build_aod_grids, and the only one that does not call close_chain_entries first. The heuristic generator and both entropy candidate paths close their mover sets over conveyor chains before building grids; a Starlark policy's candidate pipeline does not.
Consequence: cause 3 of #910 persists for the DSL pipeline on a chain-capable spec. build_aod_grids derives movers from entries.keys(), and #896's repair closure can only pull in cells whose source is already an entry (it must never promote a stationary atom, or the plan and the hardware disagree). A follower already sitting on its target is never nominated by scoring, so a policy's chain assembles only when every follower happens to be selected on its own merit — on a packed block it isn't, and the policy serializes or deadlocks where the built-in generators now solve.
Fix shape
Mirror the entropy driver (drivers/entropy.rs): call close_chain_entries before build_aod_grids, and fold the returned ChainLinks into entry_by_lane so the co-selected follower moves are recorded in the emitted PackedCandidates — an emitted plan must record every atom it moves.
Tests
Part of #887 — the known limit documented (not fixed) in #919.
dsl::pipeline::pack_aod_rectanglesis the fourth caller ofBusGridContext::build_aod_grids, and the only one that does not callclose_chain_entriesfirst. The heuristic generator and both entropy candidate paths close their mover sets over conveyor chains before building grids; a Starlark policy's candidate pipeline does not.Consequence: cause 3 of #910 persists for the DSL pipeline on a chain-capable spec.
build_aod_gridsderives movers fromentries.keys(), and #896's repair closure can only pull in cells whose source is already an entry (it must never promote a stationary atom, or the plan and the hardware disagree). A follower already sitting on its target is never nominated by scoring, so a policy's chain assembles only when every follower happens to be selected on its own merit — on a packed block it isn't, and the policy serializes or deadlocks where the built-in generators now solve.Fix shape
Mirror the entropy driver (
drivers/entropy.rs): callclose_chain_entriesbeforebuild_aod_grids, and fold the returnedChainLinks intoentry_by_laneso the co-selected follower moves are recorded in the emittedPackedCandidates — an emitted plan must record every atom it moves.Tests
crate::test_utils::chain_arch_json()with the packed-block instance, asserting a one-shot chain candidate is emitted. The generator-level witnesses added in fix(search): stop returning unsolvable on solvable instances #919 are the template.close_chain_entriescannot find a vacating occupant there, but worth pinning.