Hire a fetcher for one delivery at a time - #254
Conversation
petri2 (AINone) measurementLeo asked for the pathfinding bed instead of the AI one:
Per 20000-tick window, summed over seeds and teams: master 8432 / 5932 / 5907 / 5780 / 5775 / 5733, gig 8393 / 5888 / 5908 / 5762 / 5771 / 5689. Identical within seed-to-seed spread throughout; no window where one build pulls ahead. Read: on an AINone bed with steady-state economies, per-gig hiring is throughput-neutral. The behavioural change (workers change employer instead of belonging to one building) is real but does not move deliveries here. Together with the Nicowar table above: this change is about the model, not throughput; a bed where buildings actually compete for scarce workers (few workers, several hungry inns) is where a difference would show and that bed does not exist yet. |
A worker stayed subscribed to its building until that building's whole wish list was satisfied. After every drop-off it picked its own next resource from `attachedBuilding->computeWishedResources()`, so the trip belonged to whoever happened to deliver there last -- never to whoever was closest to it, and never to another building that wanted a worker more. Release it instead. `Team::updateAllBuildingTasks` runs later in the same tick, after every unit has stepped, and `considerUnitForBuilding` only accepts ACT_RANDOM units, so a unit freed during its own step is back on the market before that tick ends. The random step it starts on release is still in flight while the auction runs, so a unit that loses its own job back pays one tile for it. This is not a new mechanism, only a wider one: the release path already ran whenever a building wanted nothing the unit could reach, which was 35-40% of deposits in an eight-Nicowar Playground run. Known gap: fetching a resource out of a market -- an inn pulling fruit a market holds -- lives only in the self-renewal block this removes, and the building-side hiring scorer has no exchange option, so that path is now unreachable. It never fired in six 40000-tick eight-Nicowar runs (exchange_picked=0, exchange_taken=0), but it is a real gap and porting the exchange option into `Building::considerUnitForResource` is the fix. `Building::insertUnitToHarvesting` is left in place for that. Simulation behaviour changes, so VERSION_MINOR goes to 97 and replays older than that are refused. (cherry picked from commit b1ab3cb65b3c2540dfa78077b459dfc78aeed383) Fable 5.1 helped authoring this commit.
… idle Per-delivery hiring on its own moved no work: on the petri2 and Nicowar beds deliveries were flat to within two percent, in the early minutes as much as later. The auction only changes anything when a building has to wait for a worker, and with nobody idle it can only hand the trip back to the unit that just delivered, one tile later. So gate the release on idle hands. After a deposit the worker goes back to the pool only when at least RELEASE_IDLE_WORKER_PERCENT (20) of the team's living workers are idle -- those are the ones that may by chance stand closer to the next job. Otherwise it keeps its building and picks its next trip itself, the way master does, including the market path for an inn's fruit. Team::idleWorkerShareAtLeast counts workers and idle workers over the unit table; it runs once per deposit. The real-engine check in test/GigReleaseHarness.cpp puts a worker at an inn's door with wheat on its back and deposits: with four of five workers idle it is released and off the inn's crew; with nobody idle it keeps the inn and is already heading for the next wheat; with exactly one in five idle it is released. Built with gig-release-test and run in CI. Fable 5.1 helped authoring this commit.
a85c8b0 to
f9aacfa
Compare
Measurement of the gated version (release only while ≥20% of workers are idle)Same beds and protocol as above, both builds on the current master ( Eight Nicowars on Playground, tick 25000 → 40000:
Per 2048-tick window, summed over seeds and teams (M / G): 1341 / 1360, 2611 / 2539, 2306 / 2286, 2124 / 2212, 2267 / 2379, 2102 / 2112, 2214 / 1954, 1486 / 1308. Flat to slightly up through tick 36000, then down 12% in the last two windows, the same shape as the ungated version. Seed 11 is a clear win (+14% deliveries, starvation 23 → 5, population +37%); seed 15 a clear loss (−24% deliveries, population −33%); the other four are within a few percent. Starvation deaths fall on five of six seeds. petri2 (four AINone colonies, ten seeds, 120000 ticks): identical to master on every seed, checksum for checksum. The gate never opened: on that bed fewer than a fifth of the workers are ever idle, so the branch is byte-for-byte master there. That also answers the earlier "why no early-minutes gain": the AINone bed runs at full employment from the first minute. Read: with the gate the change is neutral on throughput, clearly good on starvation, and does nothing on a fully employed colony. A fire-rate count (how many deliveries were released vs kept) follows in a comment. |
How often the gate opensSame six Nicowar runs, with a counter at the two branches of the post-delivery decision (bench build only). "released" = handed back to the auction because ≥20% of the team's workers were idle; "kept" = stayed with its building as on master.
So on this bed about one delivery in nine ends in a release, and those are enough to move the starvation numbers above. On petri2 the count is zero. |
Rebased onto master now that #253 is in. Two commits: per-delivery hiring, then Leo's gate on top.
Summary
A worker stayed subscribed to its building until the building's whole wish list was satisfied. After every drop-off it picked its own next resource from
attachedBuilding->computeWishedResources(), so the trip belonged to whoever had delivered there last, never to whoever was closest to it and never to another building that wanted a worker more.First commit: release the unit after every deposit (
stopAttachedForBuilding(false)), one gig at a time.Team::updateAllBuildingTasksruns later in the same tick, after every unit has stepped, andconsiderUnitForBuildingonly acceptsACT_RANDOMunits, so a unit freed during its own step is back on the market before that tick ends. The random step it starts on release is still in flight while the auction runs, so a unit that wins its own job back pays one tile for it.Second commit (Leo, after the numbers below): release only while at least 20% of the team's living workers are idle (
RELEASE_IDLE_WORKER_PERCENT,Team::idleWorkerShareAtLeast). The auction only moves work when idle workers exist that may by chance stand closer to the next job; with nobody idle it could only hand the trip back to the deliverer a tile later. Otherwise the worker keeps its building and picks its next trip as on master, market path included, so the "market fetch gap" of the first commit only exists while a fifth of the team is idle.Simulation behaviour changes, so
VERSION_MINOR→ 97 and the replay floor moves with it. No save-format change.Measurement of the first commit (release always)
Eight Nicowars on Playground, resumed from a tick-25000 save, run to 40000, both builds on top of #253, ASLR off,
MALLOC_PERTURB_=0(every cell identical across fills 0/85/170). M = master, G = release always.petri2 (four AINone colonies, ten seeds, 120000 ticks): 37559 master vs 37411 (−0.4%), flat per 2048-tick window from the first minutes on (comment below). ~25% of deliveries changed employer the same tick, ~59% re-hired at the same building: the mechanism works, it just moves no work while free workers are plentiful.
Measurement of the gated version
Running; tables follow in a comment.
Feel
Per CLAUDE.md, this changes how the economy feels: while a fifth of the colony is idle, workers no longer "belong" to a building between trips, so units switch employers more often and the "N units working" counters fluctuate. With everyone busy nothing changes.
Verification
gig-release-testharness (new, in CI): four of five idle → released; nobody idle → keeps the inn and heads for the next wheat; exactly one in five → released.TestsRunner(187) plus the hiring harness set.Authored by Carol (first commit) on Leo's idea; gate and rebase by Bob.
🤖 Generated with Claude Code