Map Editor Documentation

Troubleshooting by error code

Every code the validator can report, what it means and how to fix it, plus which ones stop a map loading at all.

Reference (generated) · Page 26 of 49

Every issue the validator reports carries a stable code. There are 93 of them, and this page is generated from the same registry the validator stamps them from, so a code you see in a report is always on this page and a code on this page is always one something can emit.

Switch on the code, never on the message. Messages are written for people and get reworded; codes do not change.

Where codes come from

The same validator runs in four places, so the same code means the same thing everywhere:

  • The Problems panel in The Forge, while you work.
  • The validator page and POST /api/validate, before you publish.
  • Publishing, which refuses errors outright.
  • Loading, every time a match opens a custom map: a direct launch, a replay, a host's map arriving at each guest, a ?mod= merge, and The Forge's Test Map. See the load gate below.

Severity and class are different questions

Severity says whether you are blocked: an error stops a publish, a warning does not. Class says what kind of problem it is, and it is the class the load gate reads. Four classes:

ClassWhat it means
securitythe content could harm the machine that loads it. Never a warning, anywhere.
resourcea size, count or per-tick cost budget. The map may be correct and still refuse to run at a playable frame rate, or to fit in a lobby message.
integritythe content is inconsistent, or will silently not do what it says: a dangling reference, an entity that never spawns, a match that cannot be won.
qualityit works; it is probably not what you meant. Typos, imbalance, cosmetics ignored at load.

The load gate

A map you publish was checked when you published it. A map arriving from someone else was not, so it is re-checked at the door, every time. The gate never repairs a map and never rewrites a byte: two clients repairing the same map differently is a desync, which is worse than a refusal.

A code blocks a load when its class is security or resource, when the code is not registered at all (the gate fails closed), or when it is one of the 9 integrity codes below whose bytes cannot be read. Everything else loads. Codes that block are marked blocks loading in the tables.

This build's gate is set to warn. In warn it records the verdict to the browser console and lets the match start anyway; only enforce refuses. The mode is a build-wide setting (NEXT_PUBLIC_LOAD_GATE_MODE) and never a per-client one, because a gate that refused on one client and not another would half-start a lobby. So treat a blocking code as "this map will be refused as soon as enforcement is on", not as "this map works".

The codes

Grouped by class, alphabetical inside each group.

security (3 codes)

the content could harm the machine that loads it. Never a warning, anywhere.

CodeMeansFix
forbidden-key
blocks loading
A reserved key appears in an overrideRename the key. "proto", "constructor" and "prototype" are refused because writing them changes the behaviour of every object in the match.
invalid-id
blocks loading
An override id is not a legal idUse letters, digits, underscores and hyphens only, up to 64 characters. Spaces and punctuation are refused.
script-banned-syntax
blocks loading
A custom script uses something the sandbox will not runThe message names it and the line. Clocks (Date, performance), async/await, BigInt literals, eval and Function, and import/export are all refused: each of them either makes two players compute different matches or goes around the limits on what a script may spend.

resource (16 codes)

a size, count or per-tick cost budget. The map may be correct and still refuse to run at a playable frame rate, or to fit in a lobby message.

CodeMeansFix
invalid-patch-value
blocks loading
An override sets a field to a value of the wrong type or outside its rangeThe message names the field and what it accepts. The bounds are deliberately loose: if a plausible value is refused, the value is not the problem.
layer-too-large
blocks loading
A terrain layer is longer than the map could needThe RLE layer decodes to more tiles than meta.size allows. Re-export from the editor rather than editing the layer by hand.
map-large
blocks loading
The map is large enough to be slow to shareIt will still publish. Consider fewer placed entities or a smaller map if lobby joins feel slow.
perf-hot-periodic-scan
blocks loading
A fast periodic trigger scans units every time it firesRaise the period to 5 ticks or more, or move the scan behind a condition. A region or group scan walks every unit in the match, and at 20 ticks a second that is the whole map per second.
perf-periodic-group-loop
blocks loading
A periodic trigger loops over a unit groupLoop over the smallest group you can (a region or one seat rather than every unit of a type), and raise the period. The body runs once per unit, every time the trigger fires.
perf-scan-in-damage-event
blocks loading
A damage trigger builds a unit group on every hitThe damage events fire once per hit, and there are thousands of hits in a busy fight. Add a filter to the event so it matches far fewer units, or have a periodic trigger refresh a unit-group variable and read that variable inside the damage trigger. A group scan is charged per unit it walks, so one scan per hit spends the tick's whole expression budget in a fraction of a second and every trigger on the map then reads zeros until the next tick.
perf-uncapped-periodic-spawn
blocks loading
A periodic trigger spawns units with nothing to stop itAdd a condition that caps the population (for example, stop once the group is over N), set the trigger to run once, or have it disable itself. Otherwise the unit count rises for the whole match.
scripts-too-large
blocks loading
The map's compiled scripts are too largeEvery player parses and holds all of it before tick 0. Move tables and level data out of the code and into the map, and delete unused code.
too-large
blocks loading
The file is over the size limitCut placed entities, trigger text or embedded overrides. A multiplayer lobby sends the whole map to every player.
too-many-entities
blocks loading
The map places too many entitiesRemove placed units, buildings, resource nodes or spawner squad entries until the total is under the cap.
too-many-entity-props
blocks loading
The map places too many destructible or selectable propsEach destructible or selectable prop is a simulated entity. Replace some with decorative props, or remove them, until the count is under the cap.
too-many-issues
blocks loading
Too many problems to listFix the ones listed and validate again; the rest appear once there is room for them.
too-many-props
blocks loading
The map places too many propsRemove props until the map is under the cap; both placement lists count. Past it they are dropped at load.
too-many-regions
blocks loading
The trigger script defines too many regionsRegions are checked against every moving unit. Merge or delete regions until the script is under the cap.
too-many-scripts
blocks loading
The map carries too many custom scriptsEach script gets its own fixed block of interpreter memory, reserved before the match starts. Merge scripts that belong together until the map is under the cap.
too-many-spawns
blocks loading
The map declares too many spawn pointsA match seats at most eight players. Delete the extra spawn points.

integrity (52 codes)

the content is inconsistent, or will silently not do what it says: a dangling reference, an entity that never spawns, a match that cannot be won.

CodeMeansFix
aid-collisionTwo placed entities share an authored idAuthored ids are how triggers name a specific entity. Rename one; the trigger that looks the id up otherwise finds whichever it meets first.
building-terrain-mismatchA building is placed on terrain it cannot sit onMove the building, or paint the tiles under its whole footprint to terrain it accepts.
custom-runtime-invalidUnsupported custom-game runtimeUse a supported controller and timing profile. Arcade controllers require local simulation.
def-overrideA problem in the data overridesThe path names the override and the field. Fix it in the Data module, or remove the override.
def-referenceA data override points at something that does not existCorrect the id it names. An override whose base cannot be resolved is dropped at load.
duplicate-spawnTwo seats share a spawn pointMove one of them. Two starting bases on one tile overlap and one of the two loses its buildings.
editor-draft
blocks loading
This is an editor draft, not a game mapUse File ▸ Export or Test Map in The Forge, which writes the game format, or validate through /api/validate, which converts drafts for you.
empty
blocks loading
The file is emptyExport the map or fragment again; an empty file usually means a save that failed.
entity-out-of-boundsA placed entity is off the mapMove it inside the map. An entity outside the bounds is dropped at load without a message.
format-too-new
blocks loading
The map was made by a newer buildUpdate the game. This build cannot read the map format the file declares, and reading it anyway would misplace entities.
format-version-understated
blocks loading
The map declares an older format than it usesRe-save the map from this build so it is stamped with the format version its trigger nodes and its props require; older builds must refuse it rather than silently ignore them.
height-rule-failedThe height validator failedThis is a bug in the game, not in your map. Report it with the map attached.
invalid-envA meta.env field is the wrong type or out of rangeFix the field named in the message. timeOfDay is 0..1, dayNightSpeed and fogDensity are finite and not negative, lightTint is a 24-bit integer, sky needs all three of horizon, zenith and haze.
invalid-prop-blockA prop's blocking override is malformedWrite block as "none", or as an object with "layers" (layer names) and/or "cells" (whole-number [x, y] pairs near the prop). Unreadable parts are dropped at load, which changes what the prop blocks.
invalid-tileset-sourceA meta.tilesets entry is not a slot sourceEach entry is { "biome": "<biome>", "variant": <whole number ≥ 0> }; variant is optional.
json-syntax
blocks loading
The file is not valid JSONOpen the file and look near the reported line for a trailing comma, an unquoted key or a missing bracket.
lava-rule-failedThe lava validator failedThis is a bug in the game, not in your map. Report it with the map attached.
layer-malformed-runA run in a terrain layer cannot be readA row is comma-separated runs, each "<id>" or "<count>x<id>", with no other separator or punctuation. Decoding stops at the first run it cannot read and the rest of the row loads as grass, silently — so check the separators before anything else.
layer-row-countA terrain layer has the wrong number of rowsThe layer needs exactly meta.size rows, separated by "|". Rows past the end are dropped unread and rows that are missing load as grass, so re-export the map rather than adding or deleting rows by hand.
layer-row-widthA row of a terrain layer is the wrong widthEvery row must decode to exactly meta.size tiles — add up the run counts. Tiles a row leaves undescribed load as grass, and tiles past the width are dropped unread.
malformed-meta
blocks loading
The map header is missing or wrongCheck that meta.size is a whole number inside the allowed range and that the terrain layer is present.
malformed-propA prop placement is not a placementWrite each prop placement as an object with a "typeId" string and numeric "x" and "y". Anything else is dropped at load.
map-ruleA problem a map rule reported under a code of its ownRead the message: the rule that found it is not one of the shipped rules, so there is no page for its code.
merc-camp-in-buildingsA mercenary camp is in the buildings listAuthor it under mercCamps. A merc camp placed as a plain building spawns with no hireable stock: it looks right and does nothing at all.
mod-packageA problem in the mod packageThe path names the field. Fix it in the .sosmod, or re-export the package.
monument-terrain-mismatchThe monument is not somewhere it can be heldMove the monument onto terrain units can reach and stand on.
multiple-monumentsMore than one monumentKing of the Hill is decided by one monument. Delete the extras.
no-gold-minesThe map has no gold minePlace a gold mine per starting area, or turn standard victory off if this map is not meant to be an economy map.
no-monument-for-hill-victoryKing of the Hill with no monumentPlace the monument the mode is won by, or pick a different victory preset.
no-ramp-off-start-plateauA starting plateau has no ramp off itCarve a ramp. Without one, the seat is walled in by its own cliffs and cannot leave.
overlapping-footprintTwo buildings occupy the same tilesMove one. Only one of a pair of overlapping buildings survives placement.
prop-off-tileA prop that must sit on a whole tile sits between tilesUse whole-number x and y for anything that blocks movement or is an entity, for the pre-props catalogue props, and for every placement in the pre-props list that older builds read, because all of those are drawn or blocked by tile. Other decoration may sit between tiles; the rest is rounded to the nearest tile.
prop-out-of-boundsA prop is off the map, or hangs over its edgeMove it inside the map. A prop whose anchor is off the map is dropped at load; one whose footprint runs past the edge is drawn hanging off it.
root-not-object
blocks loading
The top-level value is not an objectA map, trigger script, override set and mod package are all JSON objects. An array or a bare value cannot be one.
roster-creep-seatA roster entry targets the creep seatThe creep seat is not a playable slot. Give the entry a seat inside the playable range.
roster-rule-failedThe roster validator failedThis is a bug in the game, not in your map. Report it with the map attached.
scriptA problem in one of the map's custom scriptsThe path names the script and the field. A script needs a unique id, a lang of "ts" or "js", its source, the compiled output of that source, and the API level it was written against.
script-compiled-mismatchA script's stored program is not what its source compiles toOpen the script in The Forge and save it again, which recompiles it. The game runs the stored program while everything anyone reads shows the source, so the two are only allowed to differ by being fixed.
scripts-not-syntax-checkedA custom script could not be checkedValidate the map with the in-game validator, the /tools/validator page, or npx tsx tools/validate.ts. A script that has not been checked is reported rather than assumed safe.
single-player-standard-victoryA one-seat map still uses standard victoryStandard victory ends the match the moment there is one player left, which is tick one here. Turn it off in Map ▸ Rules, or add an opposing seat.
spawn-not-walkableA spawn point is on terrain nothing can stand onMove the spawn point onto land, or paint the tile walkable.
spawn-out-of-boundsA spawn point is off the mapMove the spawn point inside the map. A seat that starts off the map never gets its starting town hall.
too-few-spawnsNot enough spawn points for the seats the map declaresAdd a spawn point per seat, or declare fewer seats in the Players panel.
triggerA problem in the trigger scriptThe message names the node and the field. Open that trigger in The Forge and fix the field it names.
unit-terrain-mismatchA unit is placed on terrain it cannot occupyMove the unit, or change the tile. A land unit on water is pushed to the nearest legal tile, which may be across the map.
unknown-allowed-buildingThe allowed-buildings list names a building nothing definesCorrect the id. An unknown entry silently allows nothing, so the seat loses a build option it was meant to have.
unknown-building-typeA placed building has a type id nothing definesCorrect the id, or add a defOverrides entry that defines it. An unknown type never spawns.
unknown-format
blocks loading
Cannot tell what this file isAdd a "$schema" pointing at https://shardsofstone.com/schemas/v1/… , or pass an explicit kind (--kind map).
unknown-prop-typeA placed prop has a type id nothing definesCorrect the id (the message suggests the closest one), or remove the placement. A prop of an unknown type is never drawn.
unknown-terrain-idA terrain layer uses a tile id that is not a terrain typeUse one of the tile ids the editor paints. An id outside the terrain list is walkable by nothing, buildable by nobody and drawn with no texture, and nothing reports it at load.
unknown-unit-typeA placed unit has a type id nothing definesCorrect the id, or add a defOverrides entry that defines it. An unknown type never spawns.
unparseable
blocks loading
The map data could not be readRe-export the map from The Forge. A map that will not parse was almost always truncated in transit.

quality (22 codes)

it works; it is probably not what you meant. Typos, imbalance, cosmetics ignored at load.

CodeMeansFix
invalid-city-tuningA city tuning value in rules.city.tuning is unknown or out of rangeUse a setting, service, need, tool or people the message lists, as a whole number in its range. The city ignores the bad value and plays with its usual number there.
invalid-prop-cityA city building's options are malformed or out of rangeUse only the city options and values the message lists. Unknown options are ignored, and colours too dark or too bright are toned into range.
loading-screen-ignoredThe loading screen art will not be shownPoint it at an artwork path that exists in the asset manifest, or drop the field.
no-gold-sourceNo seat has a way to earn goldGive the seats starting gold, place a mine, or have a trigger grant gold. Otherwise nothing can ever be built.
prop-transform-clampedA prop rotation, scale or variant is outside what the prop allowsUse a value the prop accepts: whole degrees 0 to 359 (90-degree steps for props that snap, 0 for fixed ones), a scale inside its scale range, a variant below its variant count. Other values are clamped when the map is drawn.
resource-imbalanceOne seat starts with far more resources nearby than anotherAdd or move resource nodes so the starting areas are comparable, or accept it if the asymmetry is the point.
spawner-empty-squadA spawner has an empty squadGive the spawner at least one unit type, or delete it. An empty spawner is a prop.
trigger-angle-in-degreesAn angle looks like degrees in a slot that wants radiansDivide by 57.29577951308232, or write the radian value. Every trigonometry node in the trigger language takes radians; create units facing is the one field that takes degrees, and mixing them fails silently because the wrong angle is still a perfectly good number.
trigger-buff-already-expiredA buff is added with a duration that has already run outadd buff takes a duration in TICKS — 20 of them to the second — and -1 for a permanent buff. Zero or any other negative number is a buff that is over before it starts, so the action stores nothing and does nothing. It is silent at runtime because an expired buff is indistinguishable from no buff, which is why this is reported here instead.
trigger-buff-id-reservedA buff id uses a prefix the engine ownsPick an id that does not start with item:, estatus:, aura_, well_fed: or flag:. The engine clears each of those namespaces wholesale — unequipping an item, an elemental status ending, a meal wearing off — so a buff hiding inside one is deleted by something the map never asked about, at a moment nothing in the map explains.
trigger-hero-level-not-raisedA hero level is set to a level the node can never reachset hero level only RAISES. Heroes start at level 1 and the engine records the level but not the growth applied to reach it, so there is no honest way to take a level back — which is why lowering is a no-op rather than an approximation. A literal 1 or below therefore does nothing at all. To reset a hero, remove it and create it again.
trigger-item-count-clampedAn item count is above the per-action cap and will be clampedOne give item hands over at most 64. The count drives a loop of single-item inserts (so the bag's own stacking rules apply) and overflow past a full bag becomes ground items, so an unbounded count would be a hang and an entity flood. Call the action more than once, or ask for a number the bag can hold.
trigger-multiboard-cell-before-setA multiboard cell is written to a board the map never createsAdd a create or replace a multiboard for that board id — on map initialisation is usual — before anything writes a cell into it. A cell written to a board that does not exist is dropped, and at runtime that is indistinguishable from a cell nobody ever wrote, which is why this is reported here.
trigger-multiboard-cell-out-of-boundsA multiboard cell falls outside the grid its board declaredRows and columns are 0-BASED, so a board of 3 rows has rows 0, 1 and 2 — writing row 3 is one past the end. Either widen the board in create or replace a multiboard, or move the cell inside the grid it already has. The write is dropped, silently, because the board simply has nowhere to put it.
trigger-objective-campaign-onlyA campaign objective is set in a map that is not a campaign missionset a campaign objective writes the CAMPAIGN's own mission objectives, which only exist inside a campaign mission — in a skirmish, a multiplayer match or a published custom map it does nothing at all. For a goal list of your own use set quest, which is the trigger layer's quest log and works everywhere.
trigger-sort-order-discardedA sorted group is stored in a variable, which re-sorts itUse the sorted group inline — loop over it, or read a position out of it. Unit-group variables are always re-sorted by unit id on write, which is what keeps every other group identical on every client, so storing a sort throws the sort away.
trigger-sound-first-play-silentA sound is played once, and the first play of an unloaded sound is silentSoundManager fetches and decodes a sound on FIRST request and returns without playing it — so a cue fired exactly once, on the tick it matters, plays nothing at all. Only about a quarter of the shipped library is preloaded. Play the sound somewhere harmless earlier in the map (map initialisation is ideal) so it is resident when the moment comes, or choose a sound the match has already made.
trigger-timer-window-unknown-timerA timer window names a timer nothing ever startsA timer window is a label on a clock, and the clock is the named timer — so a window for a timer no start timer ever starts is a title above a countdown frozen at zero. Start the timer somewhere, or name the timer that is already running.
unknown-biomeThe biome name is not one of the shipped biomesUse one of the biome names the editor offers; an unknown biome falls back to forest at load.
unknown-patch-fieldAn override sets a field no definition of that kind hasCheck the spelling against the suggestion in the message. Nothing reads the field, so the override has no effect.
unknown-tileset-biomeA tileset slot is sourced from a biome that does not existUse a real biome name. An unknown source silently falls back to the map's own biome, so the borrowed texture never appears.
unknown-tileset-slotA tileset slot name is not a real slotUse a slot the tileset panel lists (rock_1 … shore_5). An unknown slot is ignored at load, so the map draws its own biome there.

If your code is not here

Then it did not come from the shared validator. The most likely cause is a newer build reporting a code this page predates, so check the version in the report first. Failing that, an unregistered code is treated as blocking by the load gate, which is the conservative answer rather than a judgement about your map.