clone

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

engine, site: drop the big-first and bands square packers

dami committed 4b5c707
Only scatter, field and grid remain (grid for the 2x2 and X=15
fixtures). Old logs with packer.kind big-first still replay: the fold
treats the kind as a string (on-disk log test).
commit
4b5c7070fe07b7a07c0b65295d66afb94ad0691c
parent
360473c4ec380a76d3e0619c4a2da827097ee5cb
author
dami <gazey55@gmail.com>
7 files changed, +14 -247
Mengine/gen/gen.go7+-
Mengine/gen/gen_test.go11--
Mengine/main.go12+-
Mengine/pack/pack.go196+------------------------------
Mengine/pack/pack_test.go29----
Msite/lib/engine.ts2+-
Msite/lib/mcp.ts4+-
M engine/gen/gen.go +3 -4
@@ -67,11 +67,10 @@ type Params struct {
6767 Seed string `json:"seed"`6868 Nonce string `json:"nonce,omitempty"`6969 G int `json:"G"`70- Kind string `json:"kind"` // scatter | grid | big-first | bands70+ Kind string `json:"kind"` // scatter | grid7171 N int `json:"n,omitempty"`7272 // legacy grid shorthand: X tiles per side of size N7373 X int `json:"X,omitempty"`74- BigArea float64 `json:"big_area,omitempty"`7574 Coverage float64 `json:"coverage,omitempty"`7675 Compound float64 `json:"compound,omitempty"`7776 BandUnique bool `json:"band_unique"`
@@ -372,7 +371,7 @@ func generate(ctx context.Context, p Params) (*Output, error) {
372371 }373372 fixed = append(fixed, f.Tile)374373 }375- pp := pack.Params{Seed: p.Seed + "|" + p.Nonce, G: p.G, Kind: p.Kind, N: p.N, BigArea: p.BigArea, Coverage: p.Coverage, Compound: p.Compound,374+ pp := pack.Params{Seed: p.Seed + "|" + p.Nonce, G: p.G, Kind: p.Kind, N: p.N, Coverage: p.Coverage, Compound: p.Compound,376375 Region: p.Region, Fixed: fixed, IDBase: p.IDBase}377376 var ctxBoard []rules.Cell378377 if len(fixed) > 0 {
@@ -1042,7 +1041,7 @@ func generateAttempt(ctx context.Context, p Params, L *pack.Layout, attempt int)
10421041 ID: hex.EncodeToString(commit[:]),10431042 SeedCommit: hex.EncodeToString(commit[:]),10441043 G: m.G,1045- Packer: PackerInfo{Kind: L.Kind, Params: map[string]any{"n": p.N, "big_area": p.BigArea, "coverage": p.Coverage, "compound": p.Compound}},1044+ Packer: PackerInfo{Kind: L.Kind, Params: map[string]any{"n": p.N, "coverage": p.Coverage, "compound": p.Compound}},10461045 MByN: mByN,10471046 Rules: Rules{Overlaps: true, CrossTile: false, SeamClues: len(m.seamGivens) > 0, BandUnique: m.bandUniqueHeld},10481047 SeedTile: m.geom[L.SeedTile].pubID,
M engine/gen/gen_test.go +0 -11
M engine/main.go +5 -7
@@ -2,7 +2,7 @@
22 //33 // mosaic-engine gen [--seed s --G 100 --kind scatter --coverage 0.72 --compound 0.4 | --X 2 --n 14] (or JSON params on stdin)44 // mosaic-engine judge (JSON on stdin)5-// mosaic-engine pack [--seed s --G 100 --kind scatter|grid|big-first|bands --n 14]5+// mosaic-engine pack [--seed s --G 100 --kind scatter|grid --n 14]66 // mosaic-engine field --seed s --chunks 9 --chunk-G 60 [--band 16 --jobs 8 --coverage --compound]77 // mosaic-engine version (generator version; cache key)88 //
@@ -64,16 +64,15 @@ func runGen(args []string) {
6464 seed := fs.String("seed", "", "seed string; empty reads JSON params from stdin")6565 nonce := fs.String("nonce", "", "commitment nonce (derived from seed when empty)")6666 G := fs.Int("G", 0, "cells per side (packed layouts)")67- kind := fs.String("kind", "scatter", "scatter | grid | big-first | bands")67+ kind := fs.String("kind", "scatter", "scatter | grid")6868 n := fs.Int("n", 14, "tile size for kind=grid (even, 6..16)")6969 X := fs.Int("X", 0, "legacy grid shorthand: X tiles per side of size n")70- bigArea := fs.Float64("big-area", 0, "big-first: share of area placed as large tiles first")7170 coverage := fs.Float64("coverage", 0, "scatter: target share of the field covered (default 0.72)")7271 compound := fs.Float64("compound", 0, "scatter: share of drops that are unions of two rectangles (default 0.4)")7372 band := fs.Bool("band-unique", false, "reject tiles whose rows repeat within the band (grid only)")7473 timeout := fs.Duration("timeout", 30*time.Minute, "overall budget")7574 _ = fs.Parse(args)76- p := gen.Params{Seed: *seed, Nonce: *nonce, G: *G, Kind: *kind, N: *n, X: *X, BigArea: *bigArea, Coverage: *coverage, Compound: *compound, BandUnique: *band}75+ p := gen.Params{Seed: *seed, Nonce: *nonce, G: *G, Kind: *kind, N: *n, X: *X, Coverage: *coverage, Compound: *compound, BandUnique: *band}7776 if *seed == "" {7877 if err := json.NewDecoder(os.Stdin).Decode(&p); err != nil {7978 fail(err)
@@ -194,16 +193,15 @@ func runPack(args []string) {
194193 fs := flag.NewFlagSet("pack", flag.ExitOnError)195194 seed := fs.String("seed", "a", "seed string")196195 G := fs.Int("G", 100, "cells per side")197- kind := fs.String("kind", "scatter", "scatter | grid | big-first | bands")196+ kind := fs.String("kind", "scatter", "scatter | grid")198197 n := fs.Int("n", 14, "tile size for kind=grid")199- bigArea := fs.Float64("big-area", 0.45, "big-first: share of area placed as large tiles first")200198 coverage := fs.Float64("coverage", 0, "scatter: target share of the field covered (default 0.72)")201199 compound := fs.Float64("compound", 0, "scatter: share of drops that are unions of two rectangles (default 0.4)")202200 maxHole := fs.Int("max-hole", 0, "scatter: fill holes larger than this many cells (default 24)")203201 minCells := fs.Int("min-cells", 0, "scatter: smallest tile (cells)")204202 maxCells := fs.Int("max-cells", 0, "scatter: largest tile (cells)")205203 _ = fs.Parse(args)206- L, err := pack.Pack(pack.Params{Seed: *seed, G: *G, Kind: *kind, N: *n, BigArea: *bigArea, Coverage: *coverage, Compound: *compound, MaxHole: *maxHole, MinCells: *minCells, MaxCells: *maxCells})204+ L, err := pack.Pack(pack.Params{Seed: *seed, G: *G, Kind: *kind, N: *n, Coverage: *coverage, Compound: *compound, MaxHole: *maxHole, MinCells: *minCells, MaxCells: *maxCells})207205 if err != nil {208206 fail(err)209207 }
M engine/pack/pack.go +3 -193
@@ -7,8 +7,6 @@
77 // Kinds: "scatter" drops shapes at random positions, sizes and orientations,88 // growing one cluster from the seed tile; "grid" is X-by-X squares of side99 // N on a pitch of N-1 (the phase-1 standard and the envelope test);10-// "big-first" and "bands" are the phase-2 square packers (kept; they11-// produce square tiles that overlap by one cell).1210 package pack1311 1412 import (
@@ -47,13 +45,9 @@ type Params struct {
4745 Seed string `json:"seed"`4846 Salt string `json:"salt,omitempty"` // extra rng salt (gen re-packs a layout that cannot be given)4947 G int `json:"G"` // cells per side of the field50- Kind string `json:"kind"` // scatter | grid | big-first | bands48+ Kind string `json:"kind"` // scatter | grid5149 // grid: every tile the same size5250 N int `json:"n,omitempty"`53- // big-first: fraction of pitch-space area to cover with large squares54- // before filling, and the large sizes to draw from (n values).55- BigArea float64 `json:"big_area,omitempty"`56- BigSizes []int `json:"big_sizes,omitempty"`5751 // scatter: target share of the field covered (default 0.72), cell count5852 // range of a tile (default 16..256), share of drops that are unions of5953 // two rectangles (default 0.4).
@@ -614,7 +608,7 @@ func Pack(p Params) (*Layout, error) {
614608 if err != nil {615609 return nil, err616610 }617- case "grid", "bands", "big-first":611+ case "grid":618612 squares, err := squarePack(p, rng)619613 if err != nil {620614 return nil, err
@@ -635,7 +629,7 @@ func Pack(p Params) (*Layout, error) {
635629 return L, nil636630 }637631 638-// squarePack runs the square kinds in pitch space (a tile of side n is a632+// squarePack lays the grid kind in pitch space (a tile of side n is a639633 // square of side n-1; tiles are disjoint there and share a border of cells640634 // in field space) and returns the squares sorted by y then x.641635 func squarePack(p Params, rng *rand.Rand) ([]Rect, error) {
@@ -655,16 +649,6 @@ func squarePack(p Params, rng *rand.Rand) ([]Rect, error) {
655649 squares = append(squares, Rect{x, y, s, s})656650 }657651 }658- case "bands":659- squares = fillRect(Rect{0, 0, S, S}, rng, nil)660- case "big-first":661- if p.BigArea == 0 {662- p.BigArea = 0.45663- }664- if len(p.BigSizes) == 0 {665- p.BigSizes = []int{14, 16}666- }667- squares = bigFirst(S, p, rng)668652 }669653 sort.Slice(squares, func(i, j int) bool {670654 if squares[i].Y0 != squares[j].Y0 {
@@ -1356,177 +1340,3 @@ func compoundCount(tiles []Tile) int {
13561340 }13571341 return n13581342 }1359-1360-// ---------------------------------------------------------------- squares1361-1362-// bigFirst drops large squares at seeded positions until BigArea of the1363-// board is covered (or placements stop fitting), then fills the remaining1364-// guillotine leaves with bands.1365-func bigFirst(S int, p Params, rng *rand.Rand) []Rect {1366- leaves := []Rect{{0, 0, S, S}}1367- var placed []Rect1368- covered := 01369- target := int(p.BigArea * float64(S*S))1370- misses := 01371- for covered < target && misses < 400 {1372- n := p.BigSizes[rng.IntN(len(p.BigSizes))]1373- s := n - 11374- var fit []int1375- for i, l := range leaves {1376- if l.W >= s && l.H >= s && splittable(l.W-s) && splittable(l.H-s) {1377- fit = append(fit, i)1378- }1379- }1380- if len(fit) == 0 {1381- misses++1382- continue1383- }1384- i := fit[rng.IntN(len(fit))]1385- l := leaves[i]1386- corner := func(max int) int {1387- switch rng.IntN(4) {1388- case 0:1389- return snap(rng.IntN(max+1), max)1390- case 1:1391- return max1392- default:1393- return 01394- }1395- }1396- x := l.X0 + corner(l.W-s)1397- y := l.Y0 + corner(l.H-s)1398- sq := Rect{x, y, s, s}1399- placed = append(placed, sq)1400- covered += s * s1401- rest := []Rect{}1402- if x > l.X0 {1403- rest = append(rest, Rect{l.X0, l.Y0, x - l.X0, l.H})1404- }1405- if x+s < l.X0+l.W {1406- rest = append(rest, Rect{x + s, l.Y0, l.X0 + l.W - (x + s), l.H})1407- }1408- if y > l.Y0 {1409- rest = append(rest, Rect{x, l.Y0, s, y - l.Y0})1410- }1411- if y+s < l.Y0+l.H {1412- rest = append(rest, Rect{x, y + s, s, l.Y0 + l.H - (y + s)})1413- }1414- leaves = append(leaves[:i], leaves[i+1:]...)1415- leaves = append(leaves, rest...)1416- }1417- for _, l := range leaves {1418- placed = fillRect(l, rng, placed)1419- }1420- return placed1421-}1422-1423-// splittable reports whether a leftover length max can be divided into two1424-// representable parts (either may be zero).1425-func splittable(max int) bool {1426- for x := 0; x <= max; x++ {1427- if representable(x) && representable(max-x) {1428- return true1429- }1430- }1431- return false1432-}1433-1434-// snap keeps an offset at 0, max, or a representable length in between so1435-// the strips left beside a big square stay tileable more often than not.1436-func snap(v, max int) int {1437- if v <= 2 {1438- return 01439- }1440- if v >= max-2 {1441- return max1442- }1443- for d := 0; d <= max; d++ {1444- for _, x := range []int{v - d, v + d} {1445- if x >= 0 && x <= max && representable(x) && representable(max-x) {1446- return x1447- }1448- }1449- }1450- return 01451-}1452-1453-// representable reports whether a length can be exactly filled by odd1454-// sides 5..15: every odd length from 5 and every even length from 10.1455-func representable(x int) bool {1456- return x == 0 || (x >= MinN-1 && x%2 == 1) || (x >= 2*(MinN-1) && x%2 == 0)1457-}1458-1459-// fillRect tiles a pitch-space rectangle with squares of odd side 5..15 by1460-// bands: a band of height b runs along the longer side and is filled with1461-// b-squares; the leftover column is cut by Euclid; the rest recurses.1462-// Pieces narrower than 5 are left uncovered.1463-func fillRect(r Rect, rng *rand.Rand, out []Rect) []Rect {1464- if r.W < MinN-1 || r.H < MinN-1 {1465- return out1466- }1467- w, h := r.W, r.H1468- horizontal := w >= h1469- long, short := w, h1470- if !horizontal {1471- long, short = h, w1472- }1473- b := pickBand(short, long, rng)1474- k := long / b1475- rem := long - k*b1476- for i := 0; i < k; i++ {1477- if horizontal {1478- out = append(out, Rect{r.X0 + i*b, r.Y0, b, b})1479- } else {1480- out = append(out, Rect{r.X0, r.Y0 + i*b, b, b})1481- }1482- }1483- if rem > 0 {1484- if horizontal {1485- out = fillRect(Rect{r.X0 + k*b, r.Y0, rem, b}, rng, out)1486- } else {1487- out = fillRect(Rect{r.X0, r.Y0 + k*b, b, rem}, rng, out)1488- }1489- }1490- if short > b {1491- if horizontal {1492- out = fillRect(Rect{r.X0, r.Y0 + b, w, h - b}, rng, out)1493- } else {1494- out = fillRect(Rect{r.X0 + b, r.Y0, w - b, h}, rng, out)1495- }1496- }1497- return out1498-}1499-1500-// pickBand chooses a band size b in 5..min(15, short) preferring values1501-// whose remainders (short-b and long mod b) are 0 or at least 5, so the1502-// leftovers stay tileable.1503-func pickBand(short, long int, rng *rand.Rand) int {1504- max := short1505- if max > MaxN-1 {1506- max = MaxN - 11507- }1508- if max%2 == 0 {1509- max--1510- }1511- good := []int{}1512- for b := MinN - 1; b <= max; b += 2 {1513- if representable(short-b) && representable(long%b) {1514- good = append(good, b)1515- }1516- }1517- if len(good) == 0 {1518- for b := MinN - 1; b <= max; b += 2 {1519- if representable(short - b) {1520- good = append(good, b)1521- }1522- }1523- }1524- if len(good) == 0 {1525- return max1526- }1527- i := int(float64(len(good)) * (1 - rng.Float64()*rng.Float64()))1528- if i >= len(good) {1529- i = len(good) - 11530- }1531- return good[i]1532-}
M engine/pack/pack_test.go +0 -29
M site/lib/engine.ts +1 -1
M site/lib/mcp.ts +2 -2