clone

a takuzu puzzle field for agents to explore tile by tile, with a referee and a replayable spectator view

engine: chunks generate on a local board; study lines per chunk and batch

dami committed 835132a
A chunk with Params.Region is generated on a board covering only its
region and the context tiles around it and translated back to field
coordinates, so owners, rules geometry and cloned boards are region-
sized instead of G x G. Measured on a ring-1 chunk of the G=1000 daily
(25 context tiles): 618 MB and 13.7 s -> 246 MB and 6.0 s
(/usr/bin/time -l, max RSS). The field runner prints per chunk: region,
context tiles and cells, wall, max RSS, tiles, fill/givens/settle ms,
SAT calls, waves, attempts, regivens, redraws, fill retries; and per
batch (ring): chunks, jobs, wall. The assertion checker accepts a seam
clue two owners share under a third tile that arrived later as fixed
context (chunked fields and growth do this by construction).
commit
835132ad7a94b6ffa42664b580d07ae5dd775fd0
parent
6133b0f54f8dafa9c80e2d669715951b65a22eb2
author
dami <gazey55@gmail.com>
3 files changed, +117 -4
Mengine/gen/field.go25++++++++++-
Mengine/gen/gen.go69++++++++++++++++++++++++++++++
Mengine/gen/gen_test.go27+++++++++++-
M engine/gen/field.go +22 -3
@@ -161,6 +161,9 @@ func SubprocessRunner(exe string, timeout time.Duration) Runner {
161161 cmd.Stdout = &out162162 cmd.Stderr = &errb163163 cmd.Env = os.Environ()164+ if Verbose && os.Getenv("MOSAIC_CHUNK_DUMP") != "" {165+ _ = os.WriteFile(fmt.Sprintf("%s/mosaic-chunk-%d.json", os.TempDir(), p.Chunk), in, 0o644)166+ }164167 t0 := time.Now()165168 runErr := cmd.Run()166169 if Verbose {
@@ -181,10 +184,20 @@ func SubprocessRunner(exe string, timeout time.Duration) Runner {
181184 }182185 }183186 }184- fmt.Fprintf(os.Stderr, "field: chunk %d region %v fixed %d: %s, max rss %d MB\n", p.Chunk, p.Region, len(p.Fixed), time.Since(t0).Round(time.Millisecond), rss)185- if os.Getenv("MOSAIC_CHUNK_DUMP") != "" {186- _ = os.WriteFile(fmt.Sprintf("%s/mosaic-chunk-%d.json", os.TempDir(), p.Chunk), in, 0o644)187+ fixedCells := 0188+ for _, ft := range p.Fixed {189+ fixedCells += ft.Tile.Cells190+ }191+ line := fmt.Sprintf("field: chunk %d region %v fixed %d tiles %d cells: wall %s, max rss %d MB", p.Chunk, p.Region, len(p.Fixed), fixedCells, time.Since(t0).Round(time.Millisecond), rss)192+ if runErr == nil {193+ var o Output194+ if json.Unmarshal(out.Bytes(), &o) == nil {195+ st := o.Stats196+ line += fmt.Sprintf(", tiles %d, fill %d ms, givens %d ms, settle %d ms, sat %d calls, waves %d, attempts %d, regivens %d, redraws %d, fill_retries %d",197+ len(o.Manifest.Tiles), st.FillMs, st.GivensMs, st.SettleMs, st.SatCalls, st.Waves, st.Attempts, st.Regivens, st.Redraws, st.FillRetries)198+ }187199 }200+ fmt.Fprintln(os.Stderr, line)188201 }189202 if err := runErr; err != nil {190203 msg := strings.TrimSpace(errb.String())
@@ -420,6 +433,12 @@ func ApplyGrowth(state, delta *Output) (*Output, error) {
420433 func generateChunks(ctx context.Context, p FieldParams, plan FieldPlan, chunks []latticeChunk, baseTiles []TilePublic, baseSol []string, salt string, idBase int, run Runner) ([]*Output, error) {421434 outs := make([]*Output, len(chunks))422435 var mu sync.Mutex436+ tBatch := time.Now()437+ defer func() {438+ if Verbose {439+ fmt.Fprintf(os.Stderr, "field: batch %s: %d chunks, jobs %d, wall %s\n", salt, len(chunks), p.Jobs, time.Since(tBatch).Round(time.Millisecond))440+ }441+ }()423442 for colour := 0; colour < 4; colour++ {424443 var idx []int425444 for i, c := range chunks {
M engine/gen/gen.go +69 -0
@@ -8,6 +8,7 @@
88 package gen99 1010 import (11+ "bytes"1112 "context"1213 "crypto/sha256"1314 "encoding/binary"
@@ -283,7 +284,75 @@ func rngFor(parts ...string) *rand.Rand {
283284 // Generate runs the full pipeline. The layout is drawn once from the seed;284285 // attempts redraw the tile contents with a derived rng when an attempt285286 // cannot satisfy every assertion.287+// Generate runs the full pipeline. A chunk (Params.Region set) is288+// generated on a local board that covers just its region and the context289+// tiles around it, then translated back to field coordinates: the290+// per-cell structures (owners, rules geometry, boards cloned by291+// propagation) are region-sized instead of G x G, which at G=1000 was292+// hundreds of megabytes per chunk process for a 60x60 region.286293 func Generate(ctx context.Context, p Params) (*Output, error) {294+ if p.Region == [4]int{} || p.G == 0 {295+ return generate(ctx, p)296+ }297+ x0, y0 := p.Region[0], p.Region[1]298+ x1, y1 := p.Region[0]+p.Region[2], p.Region[1]+p.Region[3]299+ for _, f := range p.Fixed {300+ x0, y0 = min(x0, f.Tile.X0), min(y0, f.Tile.Y0)301+ x1, y1 = max(x1, f.Tile.X0+f.Tile.W), max(y1, f.Tile.Y0+f.Tile.H)302+ }303+ S := max(x1-x0, y1-y0)304+ lp := p305+ lp.G = S306+ lp.Region = [4]int{p.Region[0] - x0, p.Region[1] - y0, p.Region[2], p.Region[3]}307+ lp.Fixed = make([]FixedTile, len(p.Fixed))308+ for i, f := range p.Fixed {309+ t := f.Tile310+ t.X0, t.Y0 = t.X0-x0, t.Y0-y0311+ lp.Fixed[i] = FixedTile{Tile: t, Cells: f.Cells}312+ }313+ out, err := generate(ctx, lp)314+ if err != nil {315+ return nil, err316+ }317+ G := p.G318+ toGlobal := func(id int) int { return (id/S+y0)*G + id%S + x0 }319+ out.Manifest.G = G320+ for i := range out.Manifest.Tiles {321+ out.Manifest.Tiles[i].X0 += x0322+ out.Manifest.Tiles[i].Y0 += y0323+ }324+ for i := range out.Tiles {325+ for k := range out.Tiles[i].Givens {326+ out.Tiles[i].Givens[k][0] = toGlobal(out.Tiles[i].Givens[k][0])327+ }328+ for k := range out.Tiles[i].Flip {329+ out.Tiles[i].Flip[k] = toGlobal(out.Tiles[i].Flip[k])330+ }331+ }332+ sol := make([][]byte, G)333+ for y := range sol {334+ sol[y] = bytes.Repeat([]byte{'.'}, G)335+ }336+ for ly, row := range out.Solution {337+ gy := ly + y0338+ if gy < 0 || gy >= G {339+ continue340+ }341+ for lx := 0; lx < len(row); lx++ {342+ gx := lx + x0343+ if row[lx] != '.' && gx >= 0 && gx < G {344+ sol[gy][gx] = row[lx]345+ }346+ }347+ }348+ out.Solution = out.Solution[:0]349+ for y := range sol {350+ out.Solution = append(out.Solution, string(sol[y]))351+ }352+ return out, nil353+}354+355+func generate(ctx context.Context, p Params) (*Output, error) {287356 p, err := defaults(p)288357 if err != nil {289358 return nil, err
M engine/gen/gen_test.go +26 -1