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>
| M | engine/ | 7 | +- |
| M | engine/ | 11 | -- |
| M | engine/ | 12 | +- |
| M | engine/ | 196 | +------------------------------ |
| M | engine/ | 29 | ---- |
| M | site/ | 2 | +- |
| M | site/ | 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,@@ -297,17 +297,6 @@ func TestGenAssertionsSmall(t *testing.T) {
297297 checkAssertions(t, o)298298 }299299 300-func TestGenPacked(t *testing.T) {301- for _, kind := range []string{"bands", "big-first"} {302- o, err := Generate(context.Background(), Params{Seed: "packed", G: 30, Kind: kind})303- if err != nil {304- t.Fatalf("%s: %v", kind, err)305- }306- checkAssertions(t, o)307- t.Logf("%s G=30: %d tiles, %d waves, %dms", kind, len(o.Manifest.Tiles), o.Stats.Waves, o.Stats.Ms)308- }309-}310-311300 func TestGenScatter(t *testing.T) {312301 o, err := Generate(context.Background(), Params{Seed: "scatter", G: 40, Kind: "scatter"})313302 if err != nil {@@ -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 }@@ -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-}@@ -217,35 +217,6 @@ func TestGridMatchesPhaseOne(t *testing.T) {
217217 }218218 }219219 220-func TestSquareKindsUnchanged(t *testing.T) {221- // tile counts the phase-2 packers produced before the shape rewrite222- // (seed a, G=60 big-first: 34 tiles, measured 2026-08-25)223- want := map[string]map[int]int{224- "big-first": {60: 34},225- }226- for _, kind := range []string{"bands", "big-first"} {227- for _, G := range []int{27, 60, 100, 1000} {228- L, err := Pack(Params{Seed: "a", G: G, Kind: kind})229- if err != nil {230- t.Fatal(err)231- }232- check(t, L)233- for _, tl := range L.Tiles {234- if tl.W != tl.H || len(tl.Rects) != 1 {235- t.Fatalf("%s: tile %d is not a square", kind, tl.ID)236- }237- }238- if w, ok := want[kind][G]; ok && len(L.Tiles) != w {239- t.Errorf("%s G=%d: %d tiles, want %d", kind, G, len(L.Tiles), w)240- }241- t.Logf("%s G=%d: %d tiles, coverage %.3f, holes %d, %d overlaps", kind, G, len(L.Tiles), L.Report.Coverage, L.Report.Holes, L.Report.Overlaps)242- }243- }244-}245-246-// replayAccepts re-applies the scatter placement rule to a layout in247-// placement (id) order, so every overlap is verified against the rule and248-// not against the packer's own bookkeeping.249220 func replayAccepts(t *testing.T, L *Layout) {250221 t.Helper()251222 st := newScatterState(L.G, L.Region)@@ -152,7 +152,7 @@ async function hasSetsid(): Promise<boolean> {
152152 export type GenParams = {153153 seed: string;154154 G: number;155- kind: "scatter" | "grid" | "big-first" | "bands" | "field";155+ kind: "scatter" | "grid" | "field";156156 n?: number; // grid only157157 chunks?: number; // field only158158 chunk_g?: number; // field only@@ -128,9 +128,9 @@ function registerStart(server: McpServer): void {
128128 G: z.number().int().min(6).max(1000).default(100).describe(129129 "Cells per side of the field. For kind=grid, G-1 must be a multiple of n-1 (27 for a 2x2 of 14x14).",130130 ),131- kind: z.enum(["scatter", "field", "grid", "big-first", "bands"])131+ kind: z.enum(["scatter", "field", "grid"])132132 .default("scatter").describe(133- "Layout: scatter (shapes of random size dropped with overlaps and holes; the default), field (chunks x chunks scatter chunks of side chunk_g, generated in parallel; G is derived), grid (all tiles the same n, squares), big-first and bands (square packings).",133+ "Layout: scatter (shapes of random size dropped with overlaps and holes; the default), field (chunks x chunks scatter chunks of side chunk_g, generated in parallel; G is derived), grid (all tiles the same n, squares)).",134134 ),135135 n: z.number().int().min(6).max(16).default(14).describe(136136 "Tile size for kind=grid, even; 14 is the standard.",