Resource Manager Decoded
Eagle Dynamics has never published how warehouse resupply works. This is the specification, measured against a running simulator — every figure came off an instrumented mission, not a changelog.
00 What this system is
If you have flown DCS for years without knowing there is a logistics model in it, that is the intended experience. It ships switched off.
Every airfield, FARP and ship in DCS carries an inventory — airframes, munitions, and four kinds of fuel. Most players never see it, because by default every warehouse is marked unlimited and the whole mechanism stays invisible. You call for rearm, and the missiles are simply there.
Clear those checkboxes and DCS turns into a supply simulator. Stock becomes finite and real. Rearming a Hornet takes actual missiles out of the airfield's holdings; refuelling draws actual kilograms of jet fuel. Spawn into a dynamic slot and you have consumed an airframe. When a base runs out of something, it quietly stops appearing in the rearm menu — no warning, no explanation.
The Resource Manager is where this is configured: in the Mission Editor, select an airfield, FARP or ship and open its warehouse. You get its stock list, and — the part this document is about — a supply graph. Any warehouse can be nominated as the supplier of any other. When a consumer's stock in some category drops past a threshold, its supplier ships the difference automatically.
Nothing physically moves. There is no convoy to escort, no truck to ambush, no route across the map. A transfer is an amount, a delay, and an arrival — the goods vanish from one warehouse and reappear in another some minutes later. What the map looks like in between makes no difference at all.
That abstraction is the whole of DCS's built-in attrition model, and it is the only mechanism by which a mission can genuinely run out of something. A campaign designer who wants a front line that degrades, a FARP that can be starved, or an offensive that culminates has this system and nothing else.
It has also never been documented. The four settings that govern it —
speed, periodicity, size and the operating levels —
are each named misleadingly enough that the obvious reading of all four is wrong. What follows is
what they actually do, measured rather than assumed.
01 The six settings
Six things are yours to set. Three of them are named after something they don't do, so read this before trusting the editor's labels.
They divide cleanly by which end of a link they belong to, and that division is the first thing
to internalise, because it is not what the editor implies. A warehouse's
speed, periodicity and size govern only what it
sends. Nothing you set on a FARP affects deliveries arriving at it.
Initial stock
How much of each item a warehouse holds when the mission starts — set per aircraft type, per weapon type, and per fuel. It quietly does a second job: it is also the full mark. Resupply always tops a warehouse back up to this figure, and the minimum-stock percentage is a percentage of it. There is no separate capacity field, so this single number is both the opening balance and the target — and nothing stops deliveries from stacking above it.
Minimum stock, % — OperatingLevel_Air / _Eqp / _Fuel
The reorder point, and the only one of the six that the receiving warehouse controls. When a category drops below this percentage of initial stock, the warehouse asks for the difference. It is set separately for aircraft, equipment and fuel, so the three categories can reorder at different points.
Two traps. It is a percentage, not a number of items — 10 means
10%, not ten missiles. And 0 does not mean "always resupply"; it means the warehouse
can never be below its threshold, so it never asks for anything at all.
The comparison is also strict. A warehouse resting exactly on its threshold is not below it and stays silent: 100 rounds at 10% sits at 10, and a consumer drained to precisely 10 was measured making no request for the length of a run. It takes one more round to start the resupply.
Suppliers
The list of warehouses permitted to feed this one, declared on the receiving end. A link is strictly one-way — naming a depot as your supplier gives you no ability to send anything back — and there is no per-category version, so a supplier offers everything it has.
List several and the nearest is drawn down first, regardless of the order you listed them in, with the rest acting as backstops as each runs dry. The list is fixed at mission load and cannot be changed by script.
Speed
How fast a delivery travels, and the only setting that behaves as its name suggests. Distance is the straight line between the two warehouses; roads, terrain and water are all ignored, though altitude difference does count.
The editor displays km/h, but the .miz stores
metres per second — so hand-editing the file and typing 60 gives you
216 km/h, not 60.
Periodicity
Nothing about this is periodic. It is not a schedule, not a poll interval, and not how often resupply is checked — that check is a fixed ~5 seconds you cannot influence.
What it actually does is add a flat delay to every delivery: 18 seconds per
unit, on top of the travel time. The default of 30 therefore adds
nine minutes to every single delivery, which most missions are paying without
knowing it. Lower is strictly better; 0 is the floor, and still costs 15 s.
Size
How quickly cargo comes off at the receiving end, in tonnes per minute. It does not cap how much gets sent — an order always dispatches in full, however large — it only meters the arrival. A heavy order therefore lands in instalments, each usable the moment it arrives.
Because the budget is measured in tonnes, an item's mass sets its rate: at size = 10
that is 221 Hellfires a minute, or about two AH-64D airframes. It is also one shared budget for
the whole link, so a bulk airframe order delays the fuel queued behind it.
size = 0 does not disable the link — it destroys the cargo. The
supplier is still debited in full, and the goods simply never arrive. No error, no log entry.
If you change one thing, change periodicity. It is the only
setting whose default is actively working against you, it costs nothing to fix, and on a
short link it dominates the delivery time completely — nine minutes of penalty on a journey
that might otherwise take two.
02 The law
Everything here reduces to the block below.
Units are where most mistakes live. speed is
metres per second in the file while the editor shows km/h;
periodicity is a delay multiplier, not a poll interval;
size is a shared throughput budget, not a batch size; and
OperatingLevel is a percentage, not a count.
Transit & unload calculator
Queue calculator — one supplier, two consumers
The supplier commits both orders in the same tick, and each shipment flies its own route. But the supplier unloads one consumer at a time, in the order the nodes were placed in the editor — so C's cargo can sit finished-in-transit, on nobody's books, until B is completely done.
03 Every field in the warehouse record
The warehouses entry in a .miz has two top-level keys:
airports (keyed by airdrome id) and warehouses (keyed by the unit id
of a FARP, ship or static warehouse). Both hold the same record shape.
| Field | Unit / type | Range | Default | What it does |
|---|---|---|---|---|
speed | metres/second | 0 – 277.777 | 16.666666 | Transit rate. Editor shows km/h — 16.666666 m/s = 60 km/h. |
periodicity | delay multiplier | 0 – 1000 | 30 | Adds 18 s per unit to every delivery. Not a poll interval. |
size | tonnes / minute | 0 – 1000 | 100 | Shared unload budget for the whole link. 0 destroys the cargo. |
OperatingLevel_AirOperatingLevel_EqpOperatingLevel_Fuel |
percent | 0 – 100 | 10 | Request fires below this % of initial stock. Only per-category setting. 0 disables resupply. |
jet_fuel.InitFuel + gasoline, methanol_mixture, diesel |
tonnes | 0 – 5000 | 100 | Starting liquid; becomes kilograms at runtime (×1000). |
suppliers | {Id, type} list | — | {} |
type is "airports" or "warehouses". Declared on the consumer. No per-category field. |
coalition | string | — | varies | Sets an airfield's side. A FARP/ship takes its side from the country it is placed under. |
aircrafts.planes[name]aircrafts.helicopters[name] |
{initialAmount, wsType, unlimited} | 0 – 1 000 000 | 100 | Keyed by DCS type name. Note the per-type unlimited. |
weapons[i] | {wsType, initialAmount} | 0 – 1 000 000 | 100 | Array-indexed with no name — only a 4-integer wsType. |
unlimitedAircraftsunlimitedMunitionsunlimitedFuel |
boolean | — | true | Warehouse-wide. Ships without depleting; reads back as the 1 000 000 sentinel. |
dynamicSpawn, allowHotStart, dynamicCargo |
boolean | — | false | Player-facing features that share the warehouse's stock. |
There is no capacity field. A record has initialAmount and
nothing else — no maximum. Deliveries stack without limit, so "this FARP holds 50" is not
expressible. Enforce a ceiling in script if you need one.
Decoding the weapons array
Weapons carry no names, only a wsType quadruple following the hierarchy in
Scripts/Database/wsTypes.lua. AGM-114K is {4, 4, 8, 39} —
Weapon → Missile → AS_Missile → 39. Going the other way, getResourceMap() returns
all 2,148 valid item keys with their quadruples.
04 What everyone gets wrong
Four widely held beliefs about this system are false. Each was tested; each failed.
Overturned “size caps how much moves per delivery”
It caps nothing. Thirteen links were given an identical 1,000-unit shortfall and different
size values; every one shipped the full 1,000 — including the link
with size = 0.
size is a shared unload budget in tonnes per minute. At
size = 10 a link delivered 221 AGM-114K per minute; at 45.4 kg each that is
10 t/min, matching to 0.3%.
Overturned “periodicity is how often it checks”
The check interval is a fixed ~5 s regardless. Links at periodicity 0, 1 and 30
all dispatched at the same instant, 5.09 s after the shortfall appeared.
What it actually does is lengthen the journey, by 18 s per unit. At the default of 30 that is nine extra minutes on every delivery — a pure penalty most missions are paying unknowingly.
| periodicity | 0 | 1 | 2 | 3 | 5 | 10 | 30 |
|---|---|---|---|---|---|---|---|
| added delay (s) | 15 | 18 | 36 | 54 | 90 | 180 | 540 |
18 × periodicity holds exactly from 1 to 30, with zero a special case
giving 15 s. You cannot switch the penalty off, only reduce it to its floor.
Overturned “speed is in km/h”
The stored value is metres per second; the editor converts for display. Hand-editing a
.miz and writing 60 actually sets 216 km/h. The default
16.666666 is exactly 60 km/h — the figure the DCS 1.2.5 patch note referred to.
Overturned “distance is measured across the map”
It is the 3D straight line. Four links with an identical 2,000 m horizontal separation — two flat, two spanning 1,720 m of Elbrus relief — split by 38.5 s. A second site at a 2% relief ratio ruled out a linear elevation term: Pythagorean missed by 0.4 s, the linear alternative by 33 s.
Roads and terrain are ignored entirely — a link can cross a mountain range, a lake or open sea with no penalty — but altitude is part of the distance. Transit is symmetric: uphill and downhill arrive together.
05 How a delivery works
speed and periodicity; only
unload depends on size. Detection is a fixed ~5 s and is not configurable.Stock exists nowhere during transit
The supplier is debited at dispatch and the consumer credited on arrival. In between, the goods are in neither warehouse. A supplier can look drained while the consumer is still empty — normal, and the most confusing thing to watch live.
One shared budget, not one per commodity
Each link has a single unload pipeline of size tonnes per minute, spent in order
weapons, then aircraft, then liquids. A shipment of 200 AGM-114K plus 100 t of
fuel delivered 200 weapons (9,060 kg) and 90,940 kg of fuel in its first minute —
exactly 100 t combined — with the remaining 9,060 kg following 60 s later.
Bulk cargo of one kind delays every other kind on the same link. A large
airframe order will hold up fuel behind it until it clears. Raise size, or use a
separate link.
Within a category: ascending wsType
Order is by the item's wsType level-4 id — not list order, not alphabetical, not
stock level. Measured with five weapon types whose orderings all disagreed:
| # | item | wsType | credited at |
|---|---|---|---|
| 1 | weapons.bombs.GBU_12 | 38 | 356.7 |
| 2 | weapons.missiles.AGM_114K | 39 | 416.7 |
| 3 | weapons.bombs.MK_82AIR | 75 | 416.7 |
| 4 | weapons.missiles.AGM_65D | 77 | 536.7 |
| 5 | weapons.nurs.HYDRA_70_M151 | 147 | 656.7 |
Delivery is per-tonne, by item mass
Rate is the budget divided by the item's transport mass. Weapons match their real mass
closely; aircraft use a figure a few percent below the listed M_empty, from a source
that could not be identified — so use measured rates rather than a formula for airframes.
| item | rate at size 100 | implied mass | listed mass |
|---|---|---|---|
| AGM-114K | 2,205/min | 45.3 kg | 45.4 kg |
| F-15C | 8/min | 12,500 kg | ~12,700 |
| AH-64D | 18.5/min | 5,405 kg | 5,930 |
| Ka-50 | 13/min | 7,692 kg | 8,030 |
Planes and helicopters are not distinct — both sit in the same
aircraft queue and obey the same ordering.
Partial deliveries are immediately usable
Each 60-second batch is live stock the moment it lands. Mid-unload, a warehouse holding 333 of an inbound 3,000 reported exactly 333, allowed 166 to be consumed, and carried on delivering from the reduced figure. Nothing is held in escrow until a shipment completes.
Top-up is always to full
OperatingLevel decides whether to ask; it never limits how much. A
warehouse at 30% with a 10% threshold asks for nothing; the same warehouse with a 50% threshold
asks for the entire 70% shortfall.
Requests account for cargo already in flight
A consumer at 500 with 500 already inbound, drained to zero, requested exactly 500 more — not 1,000. Multiple shipments can share a link and arrive independently at their own transit times, but they queue through the one unload budget.
An order that can't be filled keeps its balance
When the suppliers between them hold less than the consumer asked for, they ship everything
they have — and the shortfall is remembered. The consumer does not need to fall
below OperatingLevel a second time; the balance is reclaimed the moment any supplier
has stock again.
A consumer wanting 1,000 from a depot holding 800 took the 800 and settled at 80% — comfortably above its own 10% threshold, so no fresh trigger was possible. Restocking the depot produced a request for exactly 200 on the next cycle. A second consumer, 20 short of a 100% threshold, likewise collected precisely its missing 20.
You cannot starve a FARP by being briefly short. A partial fill is a deferred debt, not a lost delivery. The corollary is that an empty depot silently accumulates every consumer's unmet balance, and restocking it fires all of them at once.
A reclaimed balance waits its normal turn
Being owed stock earns a consumer no priority. The balance is dispatched immediately — within one cycle of stock appearing, alongside every other pending order — but it then joins the supplier's unload queue in ordinary declaration order, behind consumers that ordered later.
.miz declaration order decides everything.The practical hazard is visibility. Those 200 missiles were debited from the depot at t+7 and credited to the FARP at t+746. For twelve minutes they appeared on neither node's books — a designer watching stock levels sees the depot pay and the FARP stay flat, and reasonably concludes the delivery failed. It had not; it was queued.
06 Many links at once
A link is configured entirely from its supplying end
All three transit and throughput parameters — speed, periodicity and
size — are read from the supplier. The consumer's copies are ignored.
Measured by mismatching the two ends of otherwise identical 10 km links:
| lane | supplier | consumer | measured | if supplier | if consumer |
|---|---|---|---|---|---|
| control | 60 km/h | 60 km/h | 618.0 | 618 | 618 |
| speed | 60 km/h | 300 km/h | 618.0 | 618 ✓ | 138 ✗ |
| speed | 300 km/h | 60 km/h | 138.0 | 138 ✓ | 618 ✗ |
| periodicity | p=1 | p=30 | 138.0 | 138 ✓ | 660 ✗ |
| size | 100 | 10 | fast | ✓ | ✗ |
| size | 0 | 100 | never arrived | ✓ | ✗ |
| size | 100 | 0 | arrived fine | ✓ | ✗ |
The size = 0 pair is decisive on its own: a zero on the supplier destroyed
the cargo, a zero on the consumer was ignored completely.
Tuning a FARP changes what it sends, never what it receives. To speed up deliveries into a FARP you edit the depot feeding it. And because those settings are single scalars on the supplier, they apply to every link it feeds — you cannot give one consumer a fast pipeline and another a slow one from the same source. Different service means different suppliers.
The consumer contributes exactly one thing to a link: OperatingLevel, which
decides when to ask.
Consumers are served in placement order
With a supplier holding 100 against three consumers wanting 60 each, the result is 60 / 40 / 0 — each filled completely before the next is considered, then the remainder, then nothing. No proportional sharing.
The order is the objects' order in the .miz, i.e. the order you placed
them in the editor. Tested against every alternative with all four made to disagree:
| node | declared | unitId | name | distance | received |
|---|---|---|---|---|---|
| Mike | 1st | 6030 (highest) | M | 8000.000 m | 60 — full |
| Zulu | 2nd | 6010 (lowest) | Z | 8000.000 m | 40 |
| Alpha | 3rd | 6020 | A (first) | 8000.000 m | 0 |
This priority is invisible in the editor. Renaming won't change it, ids won't change it, moving the FARP closer won't change it. The only way to reorder is to delete and re-place. A depot feeding three FARPs will always starve the last one you placed.
Suppliers are chosen nearest-first, with fall-through
With two suppliers able to fill an order, only the nearest ships — and
reversing the suppliers list changes nothing, so proximity decides, not list order.
When the nearest cannot cover it, the remainder falls through exactly to the
next: a 1,000-unit order split 300 / 700 between a short near supplier and a full far one, with
nothing unmet.
So a rear depot is a genuine backstop. Chain suppliers by distance and the system drains the nearest first, cascading outward as each runs dry.
The split is decided in a single pass, on one tick — the consumer does not wait to see what the near supplier actually delivers before approaching the far one. Two suppliers holding 50 and 30 against a 100-unit order were both emptied on the same trace line, 5 s after the request, leaving 20 unmet and carried as a balance. Each shipment then travels its own route at its own transit time, so a split order arrives in pieces.
The pipeline is per link — two suppliers double intake
A consumer drawing from two suppliers unloads both simultaneously, each at its own
size t/min. A single link measured exactly size; two links ran
concurrently. Two shipments on the same link, by contrast, queue and share one budget.
Categories can come from different suppliers
There is no per-category link field, but stocking one supplier with only airframes and another with only fuel works: the consumer draws each category from whichever supplier has it, and both ship at once.
Chains cascade one hop at a time
With C supplying B supplying A, draining A makes B ship (~5 s), which drops B below its own threshold, which makes C ship to B (~5 s later). Transits do not compound — each link runs its own journey.
An intermediate's top-up is sized to its own initialAmount
— it knows nothing of downstream demand. If it holds less than its consumers need, it can only
commit what it has, and the rest waits for it to be refilled. There is no lateral flow between
siblings; each consumer needs its own link.
But a relay is not throttled by being in the middle. Its inbound and
outbound pipelines run concurrently: a node was measured receiving from its
supplier at a full size t/min while simultaneously sending to its own consumer at
a full size t/min. The only queueing is outbound, across consumers.
Two warehouses supplying each other can shuttle stock forever
Nothing prevents A from naming B as its supplier while B names A as its. There is no cycle detection, and under the right conditions the pair will trade the same crates back and forth for the rest of the mission.
The cause is that each end tops up to its own initialAmount and knows
nothing about the other's needs. Two warehouses that each want 100 collectively want
200. Hold fewer than that between them and neither can ever be satisfied, so both
stay permanently below threshold, and each keeps requisitioning from the other whatever just
arrived.
Each shipment is individually correct. A consumer asks to be topped up; its supplier sends what it has, minus whatever is already in flight; the unfilled remainder is carried as a balance. Every rule behaves exactly as documented — the failure is emergent, which is why it is so hard to spot in the editor.
The stock does not just move — it effectively leaves the mission. Because goods in transit sit on neither node's books, an oscillating pair keeps most of its ammunition permanently on the road. In the measured cycle the pool averaged 14.4 rounds on hand out of 109, and for 22.5% of the time both warehouses were simultaneously at zero — a pilot at either base finding nothing to load, while the mission file says there are 109 rounds.
Mutual links are not inherently broken, though. The identical topology with 1,000 rounds at A settled in a single delivery: B asked for 95, A sent it and stayed above its own threshold, and the pair went permanently quiet. The rule that separates the two cases is simply whether there is enough to go round.
A mutual pair is stable only while the shared pool exceeds the sum of both
initialAmount values. Below that line the deficit circulates forever. Note
how easily it is crossed: two 100-round bases need 200 between them, so they begin oscillating
the moment the pair dips below completely full — and the higher either minimum-stock % is set,
the sooner the reordering starts.
A supplier serves its consumers one at a time, to completion
Outbound is serialized, not shared and not parallel. Both shipments are committed in the same tick, but the second consumer receives nothing until the first is completely unloaded — then it runs at the full rate in its turn.
| t | event |
|---|---|
| 48.6 | supplier commits both shipments at once (−4,000 = 2,000 + 2,000) |
| 191.6 | MD1 begins unloading at 221/min — identical to a single-consumer control |
| 731.6 | MD1 completes |
| 851.6 | MD2 finally begins — after 660 s of nothing |
So each consumer gets the full size budget, but they queue.
Adding consumers to a depot does not slow any individual delivery; it puts them in line. The
handover itself costs one or two 60 s ticks — measured at 120 s here and at 60 s in a separate
run, a difference small enough that it has not been pinned down.
This compounds with placement-order priority. At size = 10 with
2,000-unit orders (~9 min each), the first-placed FARP sees its first crate at ~2 min, the
second at ~13 min, the third at ~24 min — and nothing in the editor tells you which is which.
The fix is more suppliers, not a bigger one. Raising size
shortens each unload and so the queue, but the serialization remains. Two depots feeding two
FARPs each beat one depot feeding four.
What the queue costs
Because outbound is serialized, a depot's consumers form a line. Each still gets the full
size, but the nth waits for all n−1 before it. Splitting the same
FARPs across two depots halves the wait, because the two queues run independently.
07 Node types & coalition
Airfields, FARPs and ships are interchangeable. Static warehouses are not.
| Node | Category | Supply role | Reached in script by |
|---|---|---|---|
| Airfield | AIRDROME (0) | supplier & consumer | Airbase.getByName("Senaki-Kolkhi") |
| FARP / helipad | HELIPAD (1) | supplier & consumer | Airbase.getByName("MY FARP") |
| Ship | SHIP (2) | supplier & consumer | Airbase.getByName("CVN-74") |
| Static “Warehouse” | — | record exists | Warehouse.getByName(unitId) — by id |
All three airbase kinds were measured in live chains and behave identically —
same ~5 s detection, same transit law, same 60 s unload batching. An airfield supplies from its
getPoint() position: a link built at exactly 10,000 m produced a transit of
318.00 s against a prediction of 318.0. A ship works over open water with the
same law, as supplier, consumer, or the middle of a chain.
Coalition must match, and mismatches fail silently. Caucasus airfields default to NEUTRAL in their warehouses record. A neutral airfield linked to a blue FARP produced nothing in either direction for ten check cycles — no error, no log entry. Setting both to BLUE made both links fire within one cycle.
Note the ownership mechanism differs by node type: a FARP or ship takes its
side from the country it is placed under; an airfield takes it from the
coalition field in its own warehouses record.
Any script that filters airbases on HELIPAD silently skips ships and airfields.
Filter on the presence of a warehouse instead.
08 Capture, destruction, and change
Capture severs the chain in both directions
The test used a three-node blue chain — A supplies B, and B supplies C — with red armour driven onto B, the middle node. B flipped to red in ~22 seconds. Because B sits in the middle it is a consumer and a supplier at once, so one capture answers both halves of the question:
- B stopped being supplied. A, still blue and still full, never shipped to it again — while an identical untouched chain in the same run flowed normally throughout.
- B stopped supplying. Holding 5,000 rounds, it sent nothing to C across 32 cycles, even though C was still blue and still asking.
- Cargo already in flight from A to B was refunded to A, intact, one cycle after the capture. Nothing is handed to the enemy.
So a chain running through a captured warehouse breaks on both sides at once: C is orphaned even though A — its ultimate source — is untouched and full. There is no fallback to a more distant supplier, because links cannot change at runtime.
What "destroyed" actually means here
Worth being precise, because you cannot bomb a warehouse node out of existence. Airfields are indestructible, and a FARP pad has no damage model that removes it. Three different events get loosely called "destroying" a base, and they behave completely differently:
| What happens to the node | How it can occur | Effect on the warehouse |
|---|---|---|
| Its buildings are flattened | ordnance — the realistic case | Node survives and keeps every link; only its stock is spilled, once — see below |
| It changes coalition | enemy ground units occupy it | Cut off in both directions, permanently |
| Its object is removed from the world | script only — StaticObject:destroy(), or a despawn |
Node gone; a shipment already in flight still arrives |
Only the third row is "destruction" in the sense used below, and it is a scripted despawn rather than anything a pilot can cause. It was measured by deleting a supplier FARP's underlying static object while its shipment was in transit: the consumer received the shipment in full anyway, from a supplier that no longer existed.
| Which end was removed, and how | Where in-flight cargo ends up |
|---|---|
| The consumer is captured by the enemy | back to the supplier |
| The supplier is despawned by script | onward to the consumer |
Cargo in transit is never simply lost. Which end receives it depends on which end went away and by what means — capture returns it to the sender, despawning delivers it to the recipient.
Bombing an airfield's buildings empties it — once
An airfield's fuel tanks and warehouse sheds are ordinary destructible scenery, and levelling them does take the stock with them. The two structure types map onto two commodity classes, cleanly and independently:
| Structures destroyed | Map object types | Liquids | Munitions |
|---|---|---|---|
| Fuel storage | TOPLIVO-BAK_NEW, BAK_NEW, HIM_BAK_A_NEW |
→ zero | untouched |
| Warehouses | SKLAD_NEW, SKLADIK |
untouched | → near zero |
Measured by flattening only one type at each of two airfields: the one that lost its 45 fuel tanks dropped 4,000 t of fuel to zero and kept all 50,000 of its missiles, while the one that lost its 61 warehouses fell from 500 missiles to 44 and kept all 100 t of its fuel. The loss lands within about ten seconds and happens exactly once.
The tanks are not the storage — they are the contents. An airfield with all 45 fuel tanks destroyed was hand-loaded with 500 t of jet and held it indefinitely, and one with every structure on the field levelled held 1,000 missiles and 250 t without loss. So this is spillage, not lost capacity. A base you bombed flat this hour can be refuelled the next and will work normally.
The supply network is untouched by any of it. A wrecked airfield keeps its links, its timings and its thresholds:
- As a supplier it goes on shipping to schedule; it simply has less to send. One shipped its surviving 366 rounds and went to zero, and its consumer took the partial fill and carried the balance like any other shortfall.
- As a consumer it is entirely unaffected — drained to zero with its fuel storage in ruins, it requested and received a full 500 rounds and 100 t, then later topped its own fuel back up on the normal threshold trigger.
One detail resisted explanation: the warehouse-bombed airfield settled on exactly 44 rounds in two independent runs, and levelling all 217 remaining structures did not move it. It is not a cap — the same base later held 1,000 — and it is not stored in any building. Treat the floor as unexplained rather than assuming it is zero.
Links cannot be changed at runtime
The supply graph is resolved once at mission load and never revisited. A FARP spawned at runtime is a real warehouse — reachable, stockable, scriptable — but it spawns empty and can never join a chain. A consumer whose declared supplier was a unit id created later, and then stocked with 50,000 rounds, received nothing across 48 cycles while an ordinary link in the same mission shipped on schedule.
There is also no API to read or write suppliers, speed,
periodicity, size or OperatingLevel — a sweep of all 1,684
mission-state globals and 2,209 GUI-state globals found nothing. Every link you will ever
have must be placed in the Mission Editor.
Nothing persists automatically
The persistence mechanism is a generic named-handler hook — and the engine registers zero handlers of its own, so warehouse stock does not survive a save/load unless you save it yourself.
The read-back half does not exist, and setPersistencePassthrough /
getPersistenceData named in older notes are both absent on this build.
09 What actually consumes stock
| Path | Deducts? | Evidence |
|---|---|---|
| Dynamic spawn (player takes a slot) | yes | reported from play |
| Player rearm / refuel | yes | reported from play |
coalition.addGroup (script spawn) | no | measured — delta 0 over 90 s |
AI placed in the .miz | untested | — |
If you script your attrition, the warehouse is not modelling it.
Spawning AI waves with coalition.addGroup moves no stock at all — so resupply never
triggers and every speed, periodicity and size value you
tuned is inert. Deduct by hand with removeItem / setLiquidAmount if you
want the logistics system to participate. A dynamic-spawn mission gets it for free.
This is why dynamicSpawn and allowHotStart sit in the warehouse record
beside the stock and supply settings — they are part of the same consumption mechanism.
10 Traps
Aviation gasoline, MW50 and diesel can never be resupplied. Every delivery of them arrives as jet fuel. The supplier really does lose the correct liquid, so the numbers look like something worked.
Five lanes, each consumer drained of exactly one liquid and starting full on the other three, so anything landing in the wrong bucket is pushed above its configured level and cannot be mistaken for a normal top-up:
| Consumer short of | Supplier debited | That liquid at the consumer | Consumer's jet fuel |
|---|---|---|---|
| Jet fuel | jet −100 t | refilled correctly | 100 t → 100 t |
| Aviation gasoline | gasoline −100 t | stayed at 0 | 100 t → 200 t |
| MW50 | MW50 −100 t | stayed at 0 | 100 t → 200 t |
| Diesel | diesel −100 t | stayed at 0 | 100 t → 200 t |
| Gasoline, from a supplier holding no jet at all | gasoline −100 t | stayed at 0 | 100 t → 200 t |
The last row is the one that settles it. A supplier holding zero jet fuel caused 100 t of jet to appear at the consumer — the engine is not misrouting jet it had, it is creating it. No design intent explains minting a commodity the sender does not possess, so this cannot be read as "only jet was ever meant to transfer": a deliberate restriction would decline the request, not debit gasoline and mint jet.
Everything except the final step is correct. The trigger notices that gasoline is short, the quantity is right, and the supplier debits the right liquid. Quantity is conserved and kind is not — exactly 100 t out, exactly 100 t in, wrong bucket — which is the signature of a destination index hardcoded to 0 in the crediting step.
It happens once, and the reason it never retries is instructive. A short-shipped order normally keeps its balance and reclaims it later — but here nothing was short-shipped. The full 100 t was dispatched and the full 100 t arrived; it was merely credited to the wrong liquid. The order is complete as far as the engine is concerned, so no balance survives and the request is never raised again. The type is simply dead for the rest of the mission, and anyone flying piston aircraft on gasoline has a supply chain that cannot work.
Why it might exist. The scheduler is compiled, so the defect
itself is not inspectable, and one obvious theory is wrong: the .miz and
the editor both order the liquids jet, gasoline, methanol_mixture, diesel
(me_mission.lua:9430), matching runtime indices 0–3 exactly, so this is not a
file-versus-runtime ordering mismatch. What the shipped Lua does show is fuel being treated as a
single thing in three places: OperatingLevel_Fuel is one threshold covering all
four liquids where aircraft and equipment each get their own; the only live resupply handlers in
the editor are the fuel pair; and the status dialog takes all four as positional arguments to
one set_fuel() call. That is consistent with transfer code written when a warehouse
had a single fuel number, generalised on the request side when the four types arrived while the
credit side kept its original write to liquid 0 — a plausible origin, not a verified one.
size = 0 destroys cargo
A zero-throughput link still dispatches. The supplier is debited and nothing ever arrives — 1,000 airframes, 1,000 missiles and 100 t of fuel vanished this way with no error and no log entry.
Never set size to 0
Mutual suppliers loop forever
A and B each naming the other is not caught. If the pair holds less than both
initialAmount values combined, the shortfall shuttles between them permanently —
measured at a fixed 590 s cycle, with 87% of the stock in transit and
therefore unusable.
Prefer one-way links
Percentages break on small stocks
The default 10% on a FARP holding 4 airframes puts the trigger at 0.4. Lose one and you sit at 3 — far above threshold, so nothing is ever requested. Such a FARP only restocks when completely empty.
Raise OperatingLevel on small warehouses
OperatingLevel = 0 is an off switch
Not “always resupply”. With a threshold of zero, current < 0 is never true,
so a drained warehouse never asks for anything.
Zero disables the link
Two different “unlimited” flags
The warehouse-wide flag reports the 1,000,000 sentinel and never depletes.
The per-type flag inside aircrafts.helicopters[name] instead sets stock
to 2³⁰−1 and decrements normally — it is not unlimited, just
a very large number.
Likely source of “unlimited doesn't propagate”
== between warehouses is always true
The class has a broken __eq. A crate compared equal to a FARP holding entirely
different stock, and every pair tried returned true, while
rawequal correctly said false.
Compare with rawequal, never ==
getByName misses return no value
Not nil — nothing at all. So tostring(Warehouse.getByName(x))
throws "value expected" rather than printing nil, and pcall hides it
by normalising the miss to nil.
Assign before testing
getItemCount() never fails
It returned 1000000 for "" and for
"NOT_A_REAL_ITEM_12345" alike. A typo produces a plausible number rather than an
error, so misspelled keys survive indefinitely.
Validate against getResourceMap()
Zero stock removes the key
When an item reaches 0 it disappears from getInventory() rather than
reporting zero. Code reading a missing key as “never stocked” will be wrong.
Treat missing as zero
Neutral nodes are silently inert
Coalition must match. A neutral airfield linked to a blue FARP does nothing at all, with no error — and Caucasus airfields are neutral by default.
Check the coalition field
Dead UI in the editor
The Resource Manager shows per-item “Resupply, hours” and “Resupply, amount” columns for aircraft and equipment. Only the fuel pair has live handlers behind it.
Ignore those columns
11 Scripting API
The Warehouse class exposes thirteen methods. Two —
getResourceMap and getCargoAsWarehouse — do not appear in the changelog
that introduced the class.
Warehouse.getByName() takes a name for airbases but a
unitId for static warehouses. Passing a static's name returns
nil, which reads as “not supported” — and has probably convinced many people that
static warehouses aren't scriptable. They are; you just need the id.
getInventory() shape
Three sections: aircraft keyed by DCS type name, weapon keyed by
dotted path ("weapons.missiles.AGM_114K"), and liquids keyed by integer
0–3 (jet, gasoline, MW50, diesel). Liquids are in kilograms.
Enums that don't exist
Warehouse.Liquid and Airbase.Liquid are both nil — pass
raw integers. Airbase.Category does exist:
AIRDROME = 0, HELIPAD = 1, SHIP = 2.
Reaching mission state from a hook
From a GameGUI hook the mission environment is net.dostring_in("server", …) —
not "mission". The "mission" environment exists,
succeeds, and returns normally, but holds only the standard Lua library: no world,
no coalition, no Warehouse. Reads come back nil and writes
go nowhere, with no error to notice.
| env | globals | mission API |
|---|---|---|
mission | 289 | none |
server | 1684 | full |
scripting | 1684 | alias of server |
gui | 2209 | GUI side: DCS, net; no world |
config / export | 101 / 245 | neither |
getCargoAsWarehouse() — a crate is a warehouse
A sling-loadable cargo crate is not merely a mass on a hook: it can hold aircraft, munitions
and liquids exactly as an airfield does. This is the scripting half of the
dynamicCargo feature, whose own tooltip describes "packing items from warehouse into
the single cargo item spawned in the world for transportation", and it is why the editor shows a
Resource payload grid for any static of category Cargo.
It lives on Warehouse but takes a StaticObject.
Because StaticObject has no such method, the colon form throws and only the plain
call works. Called on an airbase's own warehouse it returns nil — which reads as
"unimplemented" and is the likely reason this method has stayed undocumented.
The handle is a complete warehouse — every getter and setter works, and crates are independent of each other and of nearby airbases. Two things do not follow, though:
- Payload and physical mass are unconnected. A 2,000 kg container loaded with
5,000 missiles and 90 t of fuel still reported
getCargoWeight()of exactly 2000, and still displayed as "2000 kg". If you want mass to track contents, compute it yourself. - A crate is not a supply node. The editor writes a warehouse record only for
Heliport,WarehouseandGrassAirfieldstatics — neverCargo— so a crate cannot supply or be supplied, andWarehouse.getByNamewill not find it. It is a container you move yourself.
getResourceMap() — the only reliable name check
Returns all 2,148 valid item keys with their wsType quadruples: aircraft and
ground types by bare name, weapons under weapons.missiles.*,
weapons.bombs.*, weapons.shells.*, weapons.nurs.* and five
more namespaces. Since getItemCount silently accepts garbage, this is the only way to
confirm a key is real.
12 Method & evidence
The resupply scheduler is not in Lua. Searching the shipped Scripts/ and
MissionEditor/ trees finds only editor UI, .miz serialization and the
in-game status dialog — the simulation is compiled into the engine. Everything here is therefore
black-box measurement.
Missions were generated programmatically with independent supply links laid out as parallel
lanes, since the settings are per-warehouse and invisible at runtime; draining every consumer at
one instant runs a whole matrix in a single sitting. A GameGUI hook sampled stock through
net.dostring_in("server", …), and a mission-side tracer recorded changes at
0.5 s resolution. MissionScripting.lua was never modified, so the
multiplayer integrity check stayed intact throughout.
Every run carried an unsupplied control warehouse. It never moved in any run, so all observed movement is resupply and nothing else.
| Finding | Status | Evidence |
|---|---|---|
| transit = 3D distance/speed + 18 × periodicity | Confirmed | Exact at p = 1, 2, 3, 5, 10, 30; three replicates each at p = 0 and 1 with 0.0 s spread; distances 2.5–40 km; speeds 30–300 km/h |
| distance is Pythagorean 3D, not 2D | Confirmed | Two relief ratios an order of magnitude apart (2% and 32%), both fitting within one 0.5 s tick; the linear alternative misses by 33 s |
| size = shared tonnes/min budget | Confirmed | Weapon rate matches real mass to 0.3%; a mixed shipment split 9,060 + 90,940 kg to exactly 100 t in one minute |
| size = 0 destroys cargo | Confirmed | Dispatched, debited, never arrived across a 23-minute run |
| unload order: category, then ascending wsType | Confirmed | Five weapon types with list, alphabetical, stock-level and wsType orderings all made to disagree |
| consumer priority = .miz declaration order | Confirmed | unitId, name and distance all eliminated; three consumers at exactly 8000.000 m |
| supplier preference = nearest, with fall-through | Confirmed | List order reversed with fixed geometry; 300/700 split when the near supplier was short |
| inbound pipeline is per link | Confirmed | One link measured exactly size; two links ran concurrently |
| outbound is serialized per consumer | Confirmed | Two consumers on one supplier: the second received nothing for 660 s, then ran at the full rate. Re-tested at scale with three consumers: 600/400/0, served strictly in turn. Earlier runs missed this — every multi-consumer order fitted in one 60 s tick, making serialization indistinguishable from a 60 s dispatch cadence. |
| a node's inbound and outbound run concurrently | Confirmed | A relay received at 221/min while forwarding at 221/min in the same ticks. An earlier description wrongly attributed a relay's sequencing to stock exhaustion; with ample stock it still serializes, and both pipelines run at once. |
| detection is a fixed ~5 s delay, not a grid | Confirmed | Four drains staggered across 3.9 s gave four separate dispatches at 5.38 / 5.09 / 5.29 / 5.48 s. A grid would have collapsed them onto one instant. |
| inbound parallelism holds for the same commodity | Confirmed | Re-tested with one commodity rather than two: 442/min from two suppliers against 221/min from one, side by side in the same run |
| speed, periodicity and size all come from the supplier | Confirmed | Both ends mismatched on 10 km links; five lanes, all matching the supplier prediction exactly. size = 0 destroys only when set on the supplier |
| airfields, FARPs and ships are equivalent | Confirmed | All three in live chains; airfield transit 318.00 s against a 318.0 prediction |
| coalition must match | Confirmed | Neutral airfield inert for 10 cycles; both directions fired within one cycle once blue |
| capture refunds to the supplier, despawning delivers to the consumer | Confirmed | Middle node of an A→B→C chain captured by red armour in ~22 s: cut in both directions, and cargo in flight from A refunded to A one cycle later. Separately, a supplier FARP's static object deleted mid-transit — the consumer still received the shipment in full. Both reproduced. |
| links immutable, resolved at load | Confirmed | 48 cycles with a stocked, correctly-numbered runtime FARP; no API in 1,684 + 2,209 globals |
| circular links oscillate permanently when the pool is short | Confirmed | A mutual pair holding 109 against a combined target of 200 entered a limit cycle of exactly 590 s — every event recurring on schedule over 16 minutes, no decay. Mean on hand 14.4 of 109; both nodes simultaneously empty 22.5% of the time. The same topology with slack settled in one delivery and went silent. |
the threshold test is strictly < | Confirmed | A consumer drained to exactly its threshold (10 of 100 at 10%) made no request for the length of a run, while lanes drained one round lower fired within a cycle |
| an unfilled balance persists and is reclaimed | Confirmed | A consumer left at 80% — above its own 10% threshold, so no fresh trigger was possible — requested exactly the missing 200 one cycle after its depot was restocked; a second consumer collected exactly its missing 20 |
| a balance queues by declaration order, not age | Confirmed | Backorder dispatched 2 s after a later-declared consumer's fresh order, then waited 740 s in transit while that consumer unloaded 2,000 units ahead of it |
| split orders are decided in one pass | Confirmed | Two suppliers holding 50 and 30 against a 100-unit order both debited on the same trace line at t+5, leaving 20 unmet |
| non-jet liquids never resupply — every delivery lands as jet | Confirmed | Five-lane matrix, one liquid drained per consumer and the other three left full: jet refilled correctly, while gasoline, MW50 and diesel each left their own type at zero and raised jet from 100 t to 200 t. Both read paths agree; no repeat over 150 cycles. |
| the engine creates jet fuel that no supplier held | Confirmed | A supplier stocked with gasoline and zero jet still produced 100 t of jet at its consumer — misrouting alone cannot explain it, and it rules out a deliberate jet-only restriction |
| cause of the liquid bug | Inferred | Scheduler is compiled and not inspectable. A file-versus-runtime ordering mismatch is ruled out — both order jet, gasoline, methanol, diesel. Quantity conserved with kind wrong points to a hardcoded destination index; the single OperatingLevel_Fuel and the fuel-only resupply handlers suggest code that predates the four liquid types. |
| script spawn does not consume stock | Confirmed | Delta 0 across 90 s |
| dynamic spawn and player rearm consume stock | Reported | From play, not measured here — a player in a slot is required |
| aircraft transport mass | Partial | Rate is mass-proportional across a 2.3× range, but the figure is a few percent below M_empty and its source was not identified |
| bombing fuel tanks zeroes liquids; bombing warehouses zeroes munitions | Confirmed | Attribution run destroying only one type at each of two airfields: fuel-only lost 4,000 t and kept 50,000 rounds; warehouse-only lost 500→44 rounds and kept 100 t. Both within ~10 s, once. |
| destroyed structures cost stock, not capacity or links | Confirmed | A tankless airfield held 500 t hand-loaded; one with all 217 structures levelled held 1,000 rounds and 250 t indefinitely, supplied a FARP on schedule, and received and held a full resupply as a consumer |
| the munitions floor after bombing | Unexplained | Exactly 44 of 500 survived in two independent runs and did not move when all remaining structures were levelled; not a cap, since the same base later held 1,000 |
| cargo crates carry a full warehouse payload | Confirmed | Warehouse.getCargoAsWarehouse(staticObject) returns a working warehouse on a Cargo static; every getter and setter exercised, crates independent of each other and of a nearby FARP, contents persisting. Earlier runs called it as a method on the warehouse and got nil, and wrongly recorded it as having no scriptable surface. |
| crate payload is independent of crate mass | Confirmed | 5,000 missiles and 90 t of fuel loaded into a 2,000 kg container left getCargoWeight() at exactly 2000 |
== on Warehouse handles is meaningless | Confirmed | Every pair compared true, including a crate against a FARP with different contents, while rawequal returned false and writes to one did not affect the other |
| multiplayer with clients | Untested | Runs used --server, so the server code path, but never with a client connected |