Skip to content

Let any worker stock a completed building (draft: keep the schooling gate for sites only) - #255

Closed
Giszmo wants to merge 2 commits into
masterfrom
feat/any-worker-fills
Closed

Let any worker stock a completed building (draft: keep the schooling gate for sites only)#255
Giszmo wants to merge 2 commits into
masterfrom
feat/any-worker-fills

Conversation

@Giszmo

@Giszmo Giszmo commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Draft for discussion. Stacked on #256 (first commit is that one; the second is this change). One-line behaviour change plus harness cases; the question is whether the game should work this way.

What the code does today

Building::canUnitWorkHere (src/building/Misc.cpp) refuses a worker for any fetch job at a non-flag building whose type->level is above the worker's level[HARVEST]. That covers construction and upgrade sites, which is the reading everyone has of the rule ("you need schooled workers to build level 2"), but it equally covers completed buildings that only want stock:

  • inn level 2 and 3: wheat and fruit
  • defence tower level 2 and 3: stone

A level-0 worker four tiles from a completed level-2 inn, carrying wheat, is refused with UnitTooLowLevel (real-engine run on master, see the harness in this PR). Only the school raises the harvest level, and it raises harvest and build together, so in a normal game the gate is invisible: you cannot upgrade an inn without schooled workers, and once you have them they also feed it. It bites when the schooled units are busy, dead, or few: every unschooled worker in the colony is then barred from feeding the upgraded inns and stocking the upgraded towers, and the only sign is the "N units too low level" line in the building panel.

The upgrade button is gated separately on Team::maxBuildLevel(); #256 underneath this makes both read the same worker level.

The change

Keep the gate for sites (type->isBuildingSite), drop it for completed buildings. Any worker can carry. VERSION_MINOR 96 → 97 and the replay floor with it; no save-format change.

Gameplay discussion (please weigh in)

For lifting it

  • The rule is not explained anywhere in the game. [%0 units too low level] is the only string that mentions it, one of eight failure reasons in the building panel, and the tutorial and help texts do not cover it (grep over data/texts.en.txt and docs/). A player with many level-2 towers and few schooled workers gets towers that never fill and has to work out why.
  • The "builder" reading of the school is intact: sites keep the gate, so upgrading still needs schooled workers, and schooling still buys chop speed and build speed.
  • The punishment is disproportionate to the mistake: one dead schooled worker can leave a level-3 inn unfed while ten idle level-0 workers stand next to it.

Against lifting it

  • It is a balance lever. Today an upgraded inn or tower is a commitment: you need schooled workers and you need to keep them alive and free to keep it running. After this, one schooled worker builds the level-3 inn and the swarm feeds it. Level-3 inns feed faster (timeToFeedUnit 24 → 15 → 9), so this makes the upgrade cheaper to sustain.
  • AI teams benefit too, and Nicowar upgrades inns freely; the AI bed numbers below show how much.
  • Players who have internalised the rule lose a reason to build a second school.

My read: lift it. The rule punishes a state the game never explains, and the strategic content (school before you upgrade) survives in the site gate. But this is a feel decision for a maintainer, per CLAUDE.md.

Measurement

Eight Nicowars on Playground, resumed from a tick-25000 save, run to 40000, ASLR off, MALLOC_PERTURB_ swept over 0/85/170 (the protocol from #253; this branch does not carry that fix, so the spread across fills is reported rather than hidden). End-of-run totals are in the first comment. Leo's point stands that the effect can only start once the first inns and towers are upgraded, so a per-512-tick series of deliveries against the count of upgraded buildings is being measured and will follow in a second comment.

Verification

  • level-gate-test harness (from Read one worker level where hiring read harvest and the menu read build #256, cases updated here): a level-0 worker is hired by completed level-2/3 inns and a level-3 tower, refused by a level-2 site; level-1/2 workers build level-2/3 sites; build=1, harvest=0 builds a level-2 site. On master the three completed-building cases fail with UnitTooLowLevel.
  • TestsRunner (187) green.

🤖 Generated with Claude Code

@Giszmo

Giszmo commented Sep 11, 2026

Copy link
Copy Markdown
Contributor Author

Measurement

Eight Nicowars on Playground, resumed from the tick-25000 saves, run to 40000, ASLR off (setarch -R), MALLOC_PERTURB_ swept over 0 / 85 / 170. Both columns are without the #253 fix (this branch is on master), so seed 7 is heap-sensitive on both sides and is shown at two fills; every other seed was identical across all three fills on both sides. Master numbers are the same protocol on master 4e00c2b (Carol's runs); this branch is 4c00685 with the same delivery/hunger instrumentation applied locally. Population = units alive at tick 39936.

seed deliveries master / this starvation deaths master / this population master / this
7 (fill 0) 3322 / 3272 39 / 53 272 / 249
7 (fill 170) 3289 / 3153 44 / 62 244 / 255
11 2839 / 3070 23 / 26 228 / 253
12 1792 / 1809 53 / 36 169 / 168
13 2669 / 2917 35 / 17 282 / 300
14 2939 / 2919 21 / 12 287 / 268
15 2987 / 2933 30 / 24 243 / 234
total (fill 0) 16548 / 16920 (+2.2%) 201 / 168 (−16%) 1481 / 1472 (−0.6%)

Read: starvation deaths drop on five of six seeds, deliveries are up on the two seeds where the AI had upgraded inns it could not staff (11, 13) and flat elsewhere; seed 7 goes the other way and is the noisy one. Consistent with the mechanism: Nicowar upgrades inns freely and this lets its unschooled workers feed them. Not a large effect on this bed, and this bed is AI-only; the human-play question above is the one that decides the PR.

TestsRunner: 187 OK on 4c00685.

A worker has two schooling levels that mean the same thing. Only the
school raises either, it raises both in one visit (upgradeInParallel),
and nothing else in the game tells them apart: the harvest level gated
which building tier a worker may work for and its chop speed, the build
level gated which upgrades the menu offers (Team::maxBuildLevel) and its
fill speed. So the hiring gate and the upgrade button read different
numbers for the same question, and the map editor offered two boxes for
one value. Set apart in the editor, a unit could be allowed to upgrade a
building it was then refused by.

Make it one value:

- Unit::workerLevel() reads it; Unit::setWorkerLevel() sets both halves
  and their performance together. The hiring gate, Team::maxBuildLevel
  and the fetch ranking read through it.
- A unit loaded with the two apart keeps the higher one, so maps edited
  before this keep working and cannot desync on it.
- A non-parallel upgrade of build or harvest raises both.
- The editor keeps the build box and drops the harvest box; the unit
  panel drops the Harvest row, which always showed the Build value.

The save format and race tables are untouched; both abilities are still
stored and still drive their own speeds.

A real-engine harness (test/LevelGateHarness.cpp, target level-gate-test,
run in CI) checks the gate against completed buildings and sites and that a
level-2 site hires build 1 with harvest 0 and refuses the reverse.

Simulation behaviour can change for maps with the two levels apart, so
VERSION_MINOR goes to 96 and replays older than that are refused.

(cherry picked from commit e5b1cf6)

Fable 5.1 helped authoring this commit.
`Building::canUnitWorkHere` gated every fetch job for a worker on the
building's level: a worker could only carry to a building whose level
was at most its harvest level. That gate dates from the 2007 unit
allocation rewrite and has one plain reading -- raising or upgrading a
level-N building takes a worker schooled to level N -- but it also
applied to completed buildings that only want stock: a level-2 or
level-3 inn wanting wheat and fruit, a level-2 or level-3 tower wanting
stone. An unschooled worker standing next to a hungry level-3 inn with
wheat on its back was refused, and the building panel counted it under
"units too low level".

Keep the gate for construction and upgrade sites, drop it for completed
buildings. Any worker can carry.

The real-engine check in test/LevelGateHarness.cpp runs one hiring pass
per case: a level-0 worker is hired by completed level-2/3 inns and a
level-3 tower, is refused by a level-2 inn site, and level-1/2 workers
are hired by level-2/3 sites. Built with the level-gate-test target and
run in CI like the other hiring harnesses.

Simulation behaviour changes, so VERSION_MINOR goes to 97 and replays
older than that are refused. No save-format change.

(cherry picked from commit 4c00685)

Fable 5.1 helped authoring this commit.
@Giszmo
Giszmo force-pushed the feat/any-worker-fills branch from 4c00685 to 6d80c86 Compare September 11, 2026 11:51
@Giszmo

Giszmo commented Sep 11, 2026

Copy link
Copy Markdown
Contributor Author

Deliveries over time, master vs this branch

Same bed and protocol as the first comment (eight Nicowars, tick-25000 saves, run to 40000, ASLR off, MALLOC_PERTURB_=0; this time both binaries carry a per-512-tick counter). One thing the series settles first: the warm saves already hold upgraded inns and towers at tick 25000 (39 across the six seeds, 51 by tick 26624), so the whole measured range is after the first upgrade and there is no unupgraded lead-up diluting it.

Deliveries per 2048-tick window, summed over the six seeds and all teams; "upgraded" = completed level-2/3 inns + towers, summed over seeds, at window end.

window start master this delta upgraded master / this
24576 (from 25000) 1341 1392 +3.8% 39 / 38
26624 2611 2660 +1.9% 51 / 48
28672 2306 2393 +3.8% 60 / 60
30720 2124 2372 +11.7% 65 / 75
32768 2267 2357 +4.0% 75 / 83
34816 2102 2140 +1.8% 77 / 79
36864 2214 1997 −9.8% 86 / 80
38912 (to 40000) 1486 1434 −3.5% 78 / 79

Per seed, deliveries from the first upgraded building (tick 25088 on every seed) to the end, master / this: 7: 3256 / 3124, 11: 2810 / 3038, 12: 1768 / 1780, 13: 2640 / 2890, 14: 2919 / 2903, 15: 2951 / 2903. Four up, two down; the two down are seed 7 (the heap-sensitive one, see #253) and seed 15 (−1.6%).

Read: the branch runs ahead for the first six windows, by 2–12%, while the colonies are upgrading and unschooled workers can now feed the new inns, and falls behind in the last two windows, when it has more upgraded buildings and more units alive (population +2.3% at the end, starvation −16%) and the AI economies saturate. Fill 85 gives the same shape (+2.5 / +7.0 / +11.2 / +2.0 / +3.2 / −6.9 / −5.7%). Net over the run +2.2% deliveries, as in the first comment. Same conclusion: a modest, front-loaded gain on the AI bed; the decision is the human-play one.

@Giszmo Giszmo closed this Sep 11, 2026
@Giszmo

Giszmo commented Sep 11, 2026

Copy link
Copy Markdown
Contributor Author

Closed on Leo's call: the effect is real (up to +12% deliveries per window on the AI bed), which means the schooling gate is a real balance lever and stays. The player-facing problem is that the game does not show which units fail the gate; that gets its own PR: red shapes next to the failure counts in the building panel, and the same shapes over the exact units in the map view.

@Giszmo
Giszmo deleted the feat/any-worker-fills branch September 11, 2026 12:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant