Imported from rockymine/pgm-studio-mapgen (
.claude/skills/pgm-board-warmup/SKILL.md). Install upstream withnpx skills add rockymine/pgm-studio-mapgen --skill pgm-board-warmup. Copyright stays with the author.
--- name: pgm-board-warmup description: The first ten minutes of an authoring run — what this repository costs to read, what must never be opened, what a board is made of, and how its grounds are made to meet. Load once, at the start of a run, before pgm-board. ---
Before the first board
ORDER-OF-WORK.md is the page that comes before this one: the nine decisions a board is made of, in
order, and the four that cannot be taken back. WHAT-A-BOARD-IS-MADE-OF.md is the author's ruling on how
a board should look. Read those two, then this skill.
Two things here, in order: what reading costs, and what a board is made of. Together they run about 12k tokens and ten minutes, and both are done before a plan is written.
1. The budget, because this repository is larger than any context window
What is read up front is small and everything else is opened at a question. The three documents
before this skill — ORDER-OF-WORK.md, WHAT-A-BOARD-IS-MADE-OF.md and AUTHORING-BRIEF.md — come
to ~13k tokens, and the two skills to ~9k. That is the whole of what a run loads before its
first request.
What is reachable is far more than a window holds, and none of it is a reading list. The studio
documents the brief's question table names come to ~105k tokens, this repository's own
long-form — GENERATION-NOTES.md, the two adaptation briefs, tools/README.md, BOARDS-BUILT.md —
to ~54k, and the 27 technique cards to ~66k. Open one at the question that needs it.
| Read | ~tokens | When |
|---|---|---|
| one technique card | ~2.4k median | freely — the card nearest what is being built |
pgm-studio/docs/gameplay/match-flow.md |
~16k | once, before the board is decided — or its §4, §6 and §10 alone |
pgm-studio/docs/gameplay/approaches.md |
~5k | with it; every claim is the author's and settled |
00-board.txt |
0.4–3k | freely |
one named renders/*.txt |
~1.1k median | freely, named individually |
02-heightmap.txt · 03-slopes.txt |
1.3–10k | one board at a time |
| every text render of one board | 30–57k | never — name the file wanted |
median *.layout.json |
~12k | only through jq, never whole |
fable-millrace-revamp, opus5-slipway layouts |
397k · 367k | never open. Either one ends the run. |
Never cat a *.layout.json. Two of them exceed a whole window and 27 exceed 20k. A
layout is queried: jq '.shapes | length', jq '.shapes[] | select(.id=="…")'. Same for a
large *.finish.json.
A one-line answer is a grep, not a file read. 03-slopes.txt runs to 10k tokens and its
verdict is one line: grep 'cells:' …/03-slopes.txt.
Do not open another board's spec, and that is a rule about authoring rather than about budget.
An arrangement read is the arrangement reached for, because it is the one already known to satisfy
WL9 and GO1 without an argument. Every run that has copied one shipped the first board's shape in
different blocks, and neither the gates nor a render says so.
techniques/ is where a technique is read, and it is built for exactly this. A card states one
instrument with its variants side by side, cites no past map, and costs ~2.4k tokens against a
thousand-line finish. Where a field name rather than a technique is wanted, GET /api/openapi/v1.json
is the contract and a committed spec is dated evidence of it.
specs/ is the log's evidence, and it is opened to answer what a past board did — a run report's
claim, a review's number, a debt in BOARDS-BUILT.md. Eighteen boards sit flat; the other 118 are under
specs/archive/, and nothing there is a model for anything either.
When a number is wanted rather than an example, GET /api/rules and GET /api/rules/terms
answer in one fetch and are greppable. That is cheaper than any board.
2. A board is grounds that meet, and the meetings are the craft
Decide what the board is about before deciding what it is made of, and keep it to one thing. Write the sentence down. If it cannot be written, the board is not ready.
That decision is a question about how the map is played, and this repository cannot answer it. The cards measure what an instrument does and the API answers what it will accept, and neither says what makes a match. Two documents in the studio do, and they are the only ones worth opening whole.
docs/gameplay/approaches.md is the author's law on what an objective needs around it, every claim
marked and settled, so it governs rather than advises.
docs/gameplay/match-flow.md is how a map is actually played — the funnel, the wall and the pit, where
the ground game ends and the sky begins, which wool falls first — read off recorded matches rather than
reasoned. Its §4, §6 and §10 are the three to open if the whole is too much.
Then: a board is two or three or four grounds, each stated by the one instrument that can state it, and every join between them is chosen rather than left over. That is the whole technique, and it is what separates a board that reads as a place from a board that reads as one field with a gradient in it.
A shore board, worked all the way through, as the pattern to copy:
| the ground | stated by | why that instrument |
|---|---|---|
| the beach | a relief with flow — marks pinning only the waterline, grain, a low step |
it has to read as deposited, so nothing is pinned that does not have to be |
| the dunes behind it | a second relief, on its own layer, held a little higher | relief solves per layer and ReliefFields shifts each into world Y, so two reliefs can meet. One relief across both is one field, and no join at all |
| the yard the houses stand on | a shape carrying relief_scope: "exclude" |
exclude takes the footprint out of the solve, so the two tiers meet at a face. hold lets the relief bring the lower tier up to the shape, and then there is no step and no reason for a stair |
| beach → dune | the two reliefs' own meeting, across the layer seam | the join has a shape because two fields made it |
| dune → yard | an authored flight: height_mode: "level", anchor_heights, skirt: 0, keepClear: true, a material rather than a theme, run at least twice the rise |
a relief graded across the seam deletes the boundary; a flight states it |
| yard → quay | a ramp of the same kind, or a polyline where the join is a wall | |
| the sand ON the beach, the gravel ON the tide line | an add shape carrying a theme, base_height equal to the ground it lies on, and no relief_scope at all |
a shape owns the paint only on a cell it forms the surface of: one stated lower runs under the ground, paints nothing, and says so on a 200. And a relief_scope is a statement about height — follow seats the shape on the solved field and then pins its whole ring there rigid, which over a terrain patch is a floor: the strand solves level to the block and every mark inside it is overwritten in silence |
One map needn't be one ground. Plains everywhere is not simplicity, it is one instrument.
The plan phase is small, and stays small
The plan states the arrangement and nothing else. Two shapes it may take:
- three or four distinct height zones as pieces, which the sketch then pulls into shape; or
- one rectangle, with every landform authored downstream.
A plan that grows a piece per landform is a plan whose paint will grow a theme per piece, and the board's look ends up decided by how it happened to be cut up. The worked failure is 13 pieces at 6 surface heights, then a theme per height.
What makes an area read as what it is
The relief says where the ground goes. These say what the place is, and each one is placed because there is an answer to why here:
- a landmark sculpted out of layers —
tools/sculpt/props.pyemitsdome,spire,ring_wall,ellipse_wall,tapered_tower,arch,colonnade,ziggurat,bowl,crenellated_wall,drum_towerand a compositegatehouseas ordinary sketch shapes — circles and polygons with a floor and a height, not stamped block soup. A lighthouse is atapered_towerunder adome; eight of the nine single forms cost one layer. Give the layerkind: "made"andpart_of, which keepsSK10's pair walk andSK11's reachability walk off it — a solid standing in a hill has no gap to lose, and its roof is not a stair somebody forgot. - tunnels, walls and undercrofts out of the same layers, drawn as the complement of the
space rather than cut with a
subtract:SK13reads a subtract as the board's negative space and refuses any add that fills it, on any layer. - copied trees rather than the vanilla stamp. A
copiedrecipe carries abodyblock for block;pgm-studio/tools/seed-trees.csfiles bodies out of a world into the library, andcorpus/tree-showcaseis the world they come from. State them under names indressing.stylesand let the placements name those —specs/fable-millrace-revamp/trees.jsonis 22 of them, keyed the way its placements name them. A seeded name isshowcase-r<row>-<n>and says only where the tree stood, so which one to ask for iscorpus/README.md's table: the row is a band, the band is a kind, and the kind is the author's — a pine, an olive, a jungle tree, a willow. The log a tree is built of is not what it is. - boulders, which are stone — stone, cobblestone, andesite, and nothing else.
- polylines for anything that flows. The rasterizer splines a polyline's points before offsetting the band, so four points draw as a curve: a wall, a lane, a watercourse.
- paths that are
solid, three blocks a reader cannot quite tell apart, running to a door.
3. The ten minutes: read the card nearest the thing being built
techniques/ is one card per instrument, each holding its variants side by side in one world with the
text reads that prove them. Read the card nearest the thing about to be built — relief-on-shapes and
hollows are the two that carry ground, ramp-and-stair and polylines the two that carry a join — before
opening anything else. techniques/README.md indexes them.
Every card's world is in the studio, so a card can be opened as well as read. Each names its slug —
technique-<card> — and tools/seed-studio.py --check says whether this database has them.
It answers for the copied trees too, and those are the half a fresh studio is missing. A studio seeds
its own library on every boot — materials, house presets, themes, biomes, four boulders and the six
vanilla tree species — but the 84 trees cut out of corpus/tree-showcase are this repository's, and
--check reporting none of them means the recipe named two sections below does not exist yet.
A card's variants stand side by side in one world, which is what makes the comparison the lesson. Read the two nearest what is about to be built, and say what separates their panels before authoring anything — the card's committed reads are what settle a number, not a picture of one of them.
Numbers off a finished board are a diagnostic, not a control. scramble%, barrier% and
the face count are read out of a built world, and no authoring decision is made against them:
a cliff is settled with face, a push with the arithmetic RL6 measures, a flight with a
transect. Predicting them calibrates nothing, and a predicted figure written into a report
becomes the next reader's bias.
One fact about them is worth carrying anyway, because it is not what it looks like: barrier
is not the tail of the scramble distribution. Scramble is ground a player climbs and barrier
is ground a player cannot, and barrier comes from vertical walls and shoreline rather than from
steepness — opus5-millrace is the quietest board on the shelf underfoot, at 0.9% scramble,
and carries the most impassable ground in the set at 8.0% barrier.
4. Before a board is called done, count its own instruments
Run this over the spec that was just written. A zero is not a fault; four zeros is a board that used one instrument and called it terrain.
# python3 - specs/<slug>/<slug>.finish.json
import json, sys
d = json.load(open(sys.argv[1]))
shapes = list(d.get("addShapes") or [])
layers = d.get("addLayers") or []
for L in layers:
shapes += (L.get("shapes") or [])
hm = [s.get("height_mode") for s in shapes]
rel = d.get("relief") or {}
marks = [m for g in rel.values() for m in g.get("marks", [])]
styles = (d.get("dressing") or {}).get("styles") or {}
print("reliefs", len(rel), "— two or more is two grounds meeting")
print("marks", len(marks), sorted({m["kind"] for m in marks}))
print("pushes", sum(len(g.get("pushes", [])) for g in rel.values()))
print("level", hm.count("level"), "raise", hm.count("raise"), "sink", hm.count("sink"))
print("made ground", sum(1 for s in shapes if s.get("relief_scope")))
print("polyline", sum(1 for s in shapes if s.get("type") == "polyline"))
print("made layers", sum(1 for L in layers if L.get("kind") == "made"))
print("copied trees", sum(1 for v in styles.values()
if isinstance(v, dict) and v.get("form") == "copied"))
It reads the finish the spec generated, not the script that generated it. A
build-spec.py that states a flight through a helper writes height_mode once and
uses it four times, and a grep over the source counts one.
And read 05-themes.txt. A theme registered and not on the ground is a theme that painted
nothing, and nothing anywhere raises a finding for it. A theme at a fraction of a percent is
the same fault: the shape is under the ground rather than on it, or another shape of equal
area is taking the cell.
And POST /sketch/relief/read with the stored layout, which is the only read that says what
the marks did to each other: silentMarks is every mark that landed nowhere, seams
names the pairs that meet on a step.
And a seam naming a shape rather than a mark is a relief_scope pinning ground you meant
the marks to shape.
level and largestField are the two numbers that say whether the ground has a shape at all;
over about 0.45 and 0.13 it is a table with edges.
5. Stop
Load pgm-board and begin.