Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ step and `npm run build:solver` exists only so the package stays publishable

Three things worth knowing when either starts complaining:

- **`BUILDING_TABLE` is `as const`**, so its element type is a union of 38
- **`BUILDING_TABLE` is `as const`**, so its element type is a union of 41
literal object types and `.find()` over that union hides any property some
member lacks. `packages/solver/src/data/buildings.ts` exports it through a
`readonly BuildingDefinition[]` annotation so consumers get the uniform
Expand Down
14 changes: 14 additions & 0 deletions docs/SOLVER.md
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,17 @@ cooler term goes, because adjacency does: the pool reaches the whole island, so
be a reactor. The two-class bound takes one cap on the _best_ of each class, since which class a
tile falls in is a property of that tile and an inland generator may have shore reactors around it.

**A cooler reaches eight producers, and the cooling budget forgot it.** The LP charges cooling as one
island-wide budget, `0.25 · H + wMin · D <= nCool · C_top`, which is exact in the _amount_ and blind to
the _reach_: cooling gets to a producer only from a cooler beside it, so `nCool` coolers keep at most
`8 · nCool` producers online. That was harmless while a cooler covered two or three producers' waste.
Cooler #8 against Generator #7 covers fourteen, and the budget asked for five coolers on Magma Rift's
107 tiles where the best layout found uses thirteen at a third of their capacity — the base-rules bound
sat 3–5% above it on every shipped map and read 92–94% for layouts at 96–98% of the corrected figure.
The LP now caps generators at `8 · nCool` per split (and direct producers too); under a pool the cooling
is not adjacent and neither is the cap. Singularity's bound does not move: its isolated generators are
few enough that eight per cooler never binds.

**The second half of the same argument is how many generators an island can keep apart, and a
generator-bound roster is what runs into it.** Rating every generator at the bonus is what a search
free to spread them out can reach — but isolated generators are pairwise non-adjacent by
Expand Down Expand Up @@ -776,6 +787,9 @@ about before changing anything in the search:
exhaustive brute force rather than by running the solver and writing down what it said. **These
numbers must never go down.** Each size is checked twice: once against the simulator, pinning
the exact layout deterministically, and once against the search, which must reproduce it.
That ladder is a mid-game roster, where cooling binds; **`goldenLayoutsFullTier.test.ts`** is
the same seven islands with every building at its top tier, where cooler8 makes cooling
plentiful and generator capacity binds instead, so the optima are a different ladder.
- **`anomalies.test.ts`**, **`scaling.test.ts`** and **`placementSearch.rating.test.ts`** — the
rules, which the fixtures cannot reach at all because every fixture is solved under the base
ones. Between them they pin each rule reaching the layout the search reports, the search running
Expand Down
4 changes: 3 additions & 1 deletion packages/solver/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -689,7 +689,9 @@ Start with `npm test`. Four parts of it are what actually catch a regression:
correctly rounded and a V8 upgrade can move one by an ULP — enough to diverge
the walk permanently.
- **`tests/goldenLayouts.test.ts`** pins the proven optima for islands of 3–9
tiles. **These numbers must never go down.**
tiles on a mid-game roster, and **`tests/goldenLayoutsFullTier.test.ts`** the
same islands with every building at its top tier. **These numbers must never
go down.**
- **`tests/distribution.test.ts`** pins the flow rules against measurements from
the live game and against an independent max-flow oracle.
- **The anomaly suite** — `tests/anomalies.test.ts`, `tests/scaling.test.ts`,
Expand Down
3 changes: 2 additions & 1 deletion packages/solver/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ there is no build step between an edit and a reload.
Before changing the search, read [`docs/SOLVER.md`](../../docs/SOLVER.md) and
[`docs/game-logic.md`](../../docs/game-logic.md). Two things in particular are
easy to break without noticing: the RNG's pinned golden streams, and the proven
optima in `tests/goldenLayouts.test.ts`, **which must never go down**.
optima in `tests/goldenLayouts.test.ts` and `tests/goldenLayoutsFullTier.test.ts`,
**which must never go down**.

## License

Expand Down
74 changes: 36 additions & 38 deletions packages/solver/fixtures/full_upgrades_seed1.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@
"cooler4": 7,
"cooler5": 8,
"cooler6": 10,
"cooler7": 6,
"cooler7": 8,
"cooler8": 1,
"divine_reactor": 5,
"doomStar_reactor": 5,
"ecto_compressor": 5,
"flux_reactor": 5,
"fusion_reactor": 5,
"gas_plant": 5,
Expand All @@ -33,14 +35,15 @@
"generator4": 5,
"generator5": 8,
"generator6": 8,
"generator7": 4,
"generator7": 7,
"geothermal_plant": 5,
"gravitron_plant": 5,
"heliothermal_plant": 5,
"hydro_plant": 5,
"hyper_space_core": 5,
"neuro_grid_reactor": 5,
"nuclear_reactor": 5,
"precursor_pyramid": 5,
"psionic_tower": 5,
"quantum_reactor": 5,
"sauron_eye": 5,
Expand All @@ -54,161 +57,156 @@
{
"x": 1,
"y": 0,
"building_id": "doomStar_reactor"
"building_id": "generator7"
},
{
"x": 2,
"y": 0,
"building_id": "doomStar_reactor"
"building_id": "ecto_compressor"
},
{
"x": 3,
"y": 0,
"building_id": "cooler7"
"building_id": "generator7"
},
{
"x": 0,
"y": 1,
"building_id": "doomStar_reactor"
"building_id": "generator7"
},
{
"x": 1,
"y": 1,
"building_id": "cooler7"
"building_id": "generator7"
},
{
"x": 2,
"y": 1,
"building_id": "generator7"
"building_id": "cooler8"
},
{
"x": 3,
"y": 1,
"building_id": "cooler7"
"building_id": "generator7"
},
{
"x": 0,
"y": 2,
"building_id": "doomStar_reactor"
"building_id": "ecto_compressor"
},
{
"x": 1,
"y": 2,
"building_id": "generator7"
"building_id": "cooler8"
},
{
"x": 2,
"y": 2,
"building_id": "doomStar_reactor"
},
{
"x": 3,
"y": 2,
"building_id": "doomStar_reactor"
"building_id": "ecto_compressor"
},
{
"x": 0,
"y": 3,
"building_id": "doomStar_reactor"
"building_id": "precursor_pyramid"
},
{
"x": 1,
"y": 3,
"building_id": "doomStar_reactor"
"building_id": "generator7"
},
{
"x": 2,
"y": 3,
"building_id": "cooler7"
"building_id": "generator7"
}
],
"power_output": 2.66e+22,
"power_output": 6.71830985915493e+23,
"island_count": 1
},
"annealed": {
"placements": [
{
"x": 0,
"y": 0,
"building_id": "cooler7"
"building_id": "generator7"
},
{
"x": 1,
"y": 0,
"building_id": "doomStar_reactor"
"building_id": "generator7"
},
{
"x": 2,
"y": 0,
"building_id": "cooler7"
"building_id": "generator7"
},
{
"x": 3,
"y": 0,
"building_id": "generator7"
"building_id": "ecto_compressor"
},
{
"x": 0,
"y": 1,
"building_id": "doomStar_reactor"
"building_id": "ecto_compressor"
},
{
"x": 1,
"y": 1,
"building_id": "generator7"
"building_id": "cooler8"
},
{
"x": 2,
"y": 1,
"building_id": "doomStar_reactor"
"building_id": "generator7"
},
{
"x": 3,
"y": 1,
"building_id": "doomStar_reactor"
"building_id": "ecto_compressor"
},
{
"x": 0,
"y": 2,
"building_id": "doomStar_reactor"
"building_id": "generator7"
},
{
"x": 1,
"y": 2,
"building_id": "cooler7"
"building_id": "generator7"
},
{
"x": 2,
"y": 2,
"building_id": "generator7"
"building_id": "ecto_compressor"
},
{
"x": 3,
"y": 2,
"building_id": "doomStar_reactor"
"building_id": "generator7"
},
{
"x": 0,
"y": 3,
"building_id": "generator5"
"building_id": "ecto_compressor"
},
{
"x": 1,
"y": 3,
"building_id": "cooler7"
"building_id": "generator7"
},
{
"x": 2,
"y": 3,
"building_id": "doomStar_reactor"
"building_id": "cooler8"
},
{
"x": 3,
"y": 3,
"building_id": "doomStar_reactor"
"building_id": "generator7"
}
],
"power_output": 2.7050847458757064e+22,
"power_output": 9.540000000000001e+23,
"island_count": 1
}
}
7 changes: 5 additions & 2 deletions packages/solver/fixtures/no_buildable_tiles.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@
"cooler4": 7,
"cooler5": 8,
"cooler6": 10,
"cooler7": 6,
"cooler7": 8,
"cooler8": 1,
"divine_reactor": 5,
"doomStar_reactor": 5,
"ecto_compressor": 5,
"flux_reactor": 5,
"fusion_reactor": 5,
"gas_plant": 5,
Expand All @@ -33,14 +35,15 @@
"generator4": 5,
"generator5": 8,
"generator6": 8,
"generator7": 4,
"generator7": 7,
"geothermal_plant": 5,
"gravitron_plant": 5,
"heliothermal_plant": 5,
"hydro_plant": 5,
"hyper_space_core": 5,
"neuro_grid_reactor": 5,
"nuclear_reactor": 5,
"precursor_pyramid": 5,
"psionic_tower": 5,
"quantum_reactor": 5,
"sauron_eye": 5,
Expand Down
7 changes: 5 additions & 2 deletions packages/solver/fixtures/single_tile.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@
"cooler4": 7,
"cooler5": 8,
"cooler6": 10,
"cooler7": 6,
"cooler7": 8,
"cooler8": 1,
"divine_reactor": 5,
"doomStar_reactor": 5,
"ecto_compressor": 5,
"flux_reactor": 5,
"fusion_reactor": 5,
"gas_plant": 5,
Expand All @@ -33,14 +35,15 @@
"generator4": 5,
"generator5": 8,
"generator6": 8,
"generator7": 4,
"generator7": 7,
"geothermal_plant": 5,
"gravitron_plant": 5,
"heliothermal_plant": 5,
"hydro_plant": 5,
"hyper_space_core": 5,
"neuro_grid_reactor": 5,
"nuclear_reactor": 5,
"precursor_pyramid": 5,
"psionic_tower": 5,
"quantum_reactor": 5,
"sauron_eye": 5,
Expand Down
Loading
Loading