G=200 scatter reached the coupled count (now SAT) and then looped for 40 minutes on one pair: tiles 62/295 share a rectangle that flips validly in both, so no choice of givens lets either witness the other, and the 3*tiles round bound was effectively unbounded. When the same pair comes back a third time, the stuck tile's interior solution is redrawn (new flip geometry) before re-giving; rounds are capped at 60.
- commit
- 07323e75886b9df6979a0e931e5a277b70dc8f4d
- parent
- 349e2727edc5a52bf8110fec00342a8acfd55ceb
- author
- dami <gazey55@gmail.com>
| M | engine/ | 33 | ++++++++++++++++++++++++++++-- |
@@ -171,6 +171,10 @@ type Ladder struct {
171171 Complete bool `json:"complete"`172172 }173173 174+// maxRepairRounds bounds the coupled-uniqueness repair loop; each round175+// costs a makeGivens, so 3*tiles at G=200 was effectively unbounded.176+const maxRepairRounds = 60177+174178 type Stats struct {175179 Ms int64 `json:"ms"`176180 Attempts int `json:"attempts"`@@ -180,6 +184,7 @@ type Stats struct {
180184 Releases int `json:"releases"` // repairs that took back neighbours' seam clues181185 Waves int `json:"waves"`182186 WaveRetries int `json:"wave_retries"` // tiles whose givens needed a fresh draw within the attempt187+ Redraws int `json:"redraws"` // stuck tiles whose interior solution was redrawn in repair183188 // phase timings of the successful attempt (wall clock) and of the184189 // attempts wasted before it185190 FillMs int64 `json:"fill_ms"`@@ -683,6 +688,7 @@ func generateAttempt(ctx context.Context, p Params, L *pack.Layout, attempt int)
683688 }684689 released := make([]int, total)685690 badRounds := make([]int, total)691+ pairRounds := map[[2]int]int{}686692 // repair rebuilds bad's givens; when that fails because the neighbours'687693 // clues left nothing to pin on, it releases those clues, rebuilds bad688694 // and then the neighbours that lost a clue, at most twice per tile@@ -749,7 +755,7 @@ func generateAttempt(ctx context.Context, p Params, L *pack.Layout, attempt int)
749755 if count == 1 {750756 break751757 }752- if round >= 3*total {758+ if round >= min(3*total, maxRepairRounds) {753759 return nil, fmt.Errorf("coupled system has %d solutions after %d repairs", count, round)754760 }755761 bad, nb := m.witnessGap(tiles)@@ -759,8 +765,31 @@ func generateAttempt(ctx context.Context, p Params, L *pack.Layout, attempt int)
759765 // the second time the same tile lacks a witness, its ambiguity is760766 // correlated with the neighbour's (both alternatives agree on the761767 // shared cells): rebuild that neighbour's givens first, so one of762- // the two can pin the other768+ // the two can pin the other. When the same pair comes back a third769+ // time the correlation is in the solutions themselves (a rectangle770+ // on the overlap that flips validly in both tiles), and no choice771+ // of givens breaks it: redraw the stuck tile's interior solution so772+ // its flip geometry changes, then re-given both.763773 badRounds[bad]++774+ pair := [2]int{bad, nb}775+ pairRounds[pair]++776+ if pairRounds[pair] >= 3 {777+ if Verbose {778+ fmt.Fprintf(os.Stderr, "gen: tiles %d/%d stay mutually unwitnessed; redrawing tile %d's interior\n", m.geom[bad].pubID, m.geom[nb].pubID, m.geom[bad].pubID)779+ }780+ for _, id := range tiles[bad].protect {781+ delete(m.used, id)782+ }783+ m.clearInterior(bad)784+ if ok, err := m.fillTile(ctx, fmt.Sprintf("%s|redraw%d", tag, round), bad, 200+round); err != nil {785+ return nil, err786+ } else if !ok {787+ return nil, fmt.Errorf("redraw tile %d: no completion", m.geom[bad].pubID)788+ }789+ pairRounds[pair] = 0790+ badRounds[bad] = 0791+ m.stats.Redraws++792+ }764793 if badRounds[bad] >= 2 && nb >= 0 && !m.geom[nb].fixed {765794 if Verbose {766795 fmt.Fprintf(os.Stderr, "gen: tile %d lacks a witness again; regiven its neighbour %d first\n", m.geom[bad].pubID, m.geom[nb].pubID)