Field reference · reverse-engineered

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.

2.9.29.27278version pinned
27 runsinstrumented
0.5 smeasurement resolution
1 engine bugfound

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.

SUPPLIER the end that sends speed trip time periodicity extra delay size unload rate initial stock what it can give goods one-way CONSUMER the end that asks min stock % when to ask suppliers who to ask initial stock the full mark to change how a delivery behaves, edit the left-hand box
The consumer contributes only when and whom to ask. Every property of the delivery itself — how long it takes and how fast it lands — comes from the sender, and applies identically to every consumer that sender feeds.

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.

-- every ~5 s, per dependent warehouse, per item TRIGGER current < OperatingLevel% × initialAmount -- strictly < OR an unfilled balance survives from an earlier order QUANTITY initialAmount − current − (already in transit) short-shipped? the balance persists and is reclaimed as soon as a supplier has stock — no second trigger needed DISPATCH within ~5 s; stock leaves the supplier immediately a supplier commits every shipment at once, but UNLOADS its consumers one at a time, each to completion TRANSIT distance ÷ speed + 18 × periodicity -- periodicity ≥ 1 distance ÷ speed + 15 -- periodicity = 0 distance is the 3D straight line √(Δx²+Δy²+Δz²) elevation counts; roads and terrain do not UNLOAD ONE shared budget of size tonnes per minute, per link spent in order: weapons → aircraft → liquids within a category: ascending wsType credited in 60-second batches, usable immediately size = 0 ⇒ nothing arrives; the cargo is destroyed TOPOLOGY consumers served in .miz declaration order, greedily suppliers chosen nearest-first, ALL debited in one pass INBOUND parallel — N suppliers give a consumer N × throughput OUTBOUND serialized — a supplier finishes one consumer, then starts the next a reclaimed balance queues by declaration order, not by age shortfalls cascade up a chain, ~5 s per hop strictly one-way; no recall, no lateral flow NO CYCLE GUARD — mutual links can shuttle stock forever WHERE speed, periodicity and size all come from the SUPPLIER the consumer's copies are ignored entirely the consumer contributes only OperatingLevel — when to ask

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.

The supplier
The consumers — B was placed first, so B is served first

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.

FieldUnit / typeRangeDefaultWhat it does
speedmetres/second0 – 277.77716.666666 Transit rate. Editor shows km/h — 16.666666 m/s = 60 km/h.
periodicitydelay multiplier0 – 100030 Adds 18 s per unit to every delivery. Not a poll interval.
sizetonnes / minute0 – 1000100 Shared unload budget for the whole link. 0 destroys the cargo.
OperatingLevel_Air
OperatingLevel_Eqp
OperatingLevel_Fuel
percent0 – 10010 Request fires below this % of initial stock. Only per-category setting. 0 disables resupply.
jet_fuel.InitFuel + gasoline, methanol_mixture, diesel tonnes0 – 5000100 Starting liquid; becomes kilograms at runtime (×1000).
suppliers{Id, type} list{} type is "airports" or "warehouses". Declared on the consumer. No per-category field.
coalitionstringvaries 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 000100 Keyed by DCS type name. Note the per-type unlimited.
weapons[i]{wsType, initialAmount}0 – 1 000 000100 Array-indexed with no name — only a 4-integer wsType.
unlimitedAircrafts
unlimitedMunitions
unlimitedFuel
booleantrue Warehouse-wide. Ships without depleting; reads back as the 1 000 000 sentinel.
dynamicSpawn, allowHotStart, dynamicCargo booleanfalse 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.

periodicity012351030
added delay (s)1518365490180540

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

SHORTFALL ~5 s detect DISPATCH supplier debited 3D distance ÷ speed + 18 × periodicity in transit — the stock exists nowhere ARRIVAL size t/min, 60 s batches weapons→aircraft→liquids unload
Only transit depends on 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:

#itemwsTypecredited at
1weapons.bombs.GBU_1238356.7
2weapons.missiles.AGM_114K39416.7
3weapons.bombs.MK_82AIR75416.7
4weapons.missiles.AGM_65D77536.7
5weapons.nurs.HYDRA_70_M151147656.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.

itemrate at size 100implied masslisted mass
AGM-114K2,205/min45.3 kg45.4 kg
F-15C8/min12,500 kg~12,700
AH-64D18.5/min5,405 kg5,930
Ka-5013/min7,692 kg8,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.

depot restocked — both dispatched within 2 s QB · declared 2nd fresh order, 2000 unloading — 221/min for ten minutes QA · declared 3rd owed 200 already in transit — on neither node's books, 740 s dashed = in transit t+5 t+146 t+686 t+746 the older claim is first to leave, and last to arrive
Dispatch and unload are separate stages, and only dispatch is prompt. QA's 200 missiles left the depot 2 s after QB's 2,000 and arrived 740 s later — one unload tick after QB finished. Seniority counts for nothing; .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:

lanesupplierconsumermeasuredif supplierif consumer
control60 km/h60 km/h618.0618618
speed60 km/h300 km/h618.0618 ✓138 ✗
speed300 km/h60 km/h138.0138 ✓618 ✗
periodicityp=1p=30138.0138 ✓660 ✗
size10010fast
size0100never arrived
size1000arrived 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:

nodedeclaredunitIdnamedistancereceived
Mike1st6030 (highest)M8000.000 m60 — full
Zulu2nd6010 (lowest)Z8000.000 m40
Alpha3rd6020A (first)8000.000 m0

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.

rear depot relay FARP FARP 1 FARP 2 receiving · size sending · size queued behind FARP 1 these two run at the same time, each at the full budget
A relay is not a bottleneck for being in the middle. Measured: the relay took delivery at 221/min while forwarding at 221/min concurrently. Its second consumer waited 120 s after the first completed — the queue is outbound only.

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.

warehouse A min stock 30% tops up to 100 warehouse B min stock 10% tops up to 100 supplies supplies each way the pair needs 200 — but holds only 109 the 91-round shortfall circulates instead of settling where the 109 rounds actually are, averaged over one 590 s cycle in transit — usable by nobody 14.4 94.6 of 109 — 86.8%
Measured on the pair above, drained to 9 rounds at B. The result is not decay toward equilibrium but a stable cycle of exactly 590 s, every event recurring on schedule. Two bases holding 109 rounds between them behave like two bases holding 14.

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.

tevent
48.6supplier commits both shipments at once (−4,000 = 2,000 + 2,000)
191.6MD1 begins unloading at 221/min — identical to a single-consumer control
731.6MD1 completes
851.6MD2 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.

INBOUND — parallel depot A depot B FARP size size both deliver at once — FARP receives 2 × size OUTBOUND — serialized depot FARP 1 FARP 2 size idle stock for both is committed at once, but FARP 2 gets nothing until FARP 1 completes
The asymmetry that governs every topology decision. Measured: a consumer on two suppliers received 442 AGM-114K/min against a single-supplier control's 221/min; a supplier with two consumers delivered nothing to the second for 660 s, then ran it at the full rate.

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.

ONE depot → four FARPs FARP 1 FARP 2 FARP 3 FARP 4 36 min TWO depots → two FARPs each FARP 1 FARP 2 FARP 3 FARP 4 18 min each bar = one 9-minute unload at the full size budget
Adding consumers to a depot does not slow any single delivery — it lengthens the queue. The same four FARPs finish in half the time when split across two depots, because a second supplier is a second queue.

07 Node types & coalition

Airfields, FARPs and ships are interchangeable. Static warehouses are not.

NodeCategorySupply roleReached in script by
AirfieldAIRDROME (0)supplier & consumerAirbase.getByName("Senaki-Kolkhi")
FARP / helipadHELIPAD (1)supplier & consumerAirbase.getByName("MY FARP")
ShipSHIP (2)supplier & consumerAirbase.getByName("CVN-74")
Static “Warehouse”record existsWarehouse.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 nodeHow it can occurEffect on the warehouse
Its buildings are flattenedordnance — the realistic case Node survives and keeps every link; only its stock is spilled, once — see below
It changes coalitionenemy ground units occupy it Cut off in both directions, permanently
Its object is removed from the worldscript 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 howWhere in-flight cargo ends up
The consumer is captured by the enemyback to the supplier
The supplier is despawned by scriptonward 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 destroyedMap object typesLiquidsMunitions
Fuel storageTOPLIVO-BAK_NEW, BAK_NEW, HIM_BAK_A_NEW → zerountouched
WarehousesSKLAD_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.

-- Scripts/World/PersistenceHandlers.lua, in full world.setPersistenceHandler(name, handler) -- name matches [a-zA-Z0-9_ -]+ world.runPersistenceHandlers(storageFunc) -- calls storageFunc(name, handler()) world.getPersistenceData -- a COMMENT in the source; never implemented

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

PathDeducts?Evidence
Dynamic spawn (player takes a slot)yesreported from play
Player rearm / refuelyesreported from play
coalition.addGroup (script spawn)nomeasured — delta 0 over 90 s
AI placed in the .mizuntested

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 ofSupplier debitedThat liquid at the consumerConsumer's jet fuel
Jet fueljet −100 trefilled correctly100 t → 100 t
Aviation gasolinegasoline −100 tstayed at 0100 t → 200 t
MW50MW50 −100 tstayed at 0100 t → 200 t
Dieseldiesel −100 tstayed at 0100 t → 200 t
Gasoline, from a supplier holding no jet at allgasoline −100 tstayed at 0100 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.

-- getting a handle Airbase.getByName("Senaki-Kolkhi"):getWarehouse() -- airfield Airbase.getByName("MY FARP"):getWarehouse() -- FARP: helipads ARE airbases Airbase.getByName("CVN-74"):getWarehouse() -- ship Warehouse.getByName(5005) -- static warehouse: by unitId! -- methods getInventory() getItemCount(key) getLiquidAmount(i) getOwner() getResourceMap() setItem(k,n) addItem(k,n) removeItem(k,n) setLiquidAmount(i,n) addLiquid(i,n) removeLiquid(i,n) -- a sling-load crate's payload: a PLAIN CALL taking a StaticObject Warehouse.getCargoAsWarehouse(StaticObject.getByName("CRATE"))

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.

envglobalsmission API
mission289none
server1684full
scripting1684alias of server
gui2209GUI side: DCS, net; no world
config / export101 / 245neither

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.

-- NOT crate:getCargoAsWarehouse() -- that throws local crate = StaticObject.getByName("CRATE") local w = Warehouse.getCargoAsWarehouse(crate) w:setItem("weapons.missiles.AGM_114K", 30) w:getInventory() -- the usual aircraft / weapon / liquids sections

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, Warehouse and GrassAirfield statics — never Cargo — so a crate cannot supply or be supplied, and Warehouse.getByName will 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.

FindingStatusEvidence
transit = 3D distance/speed + 18 × periodicityConfirmed 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 2DConfirmed 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 budgetConfirmed 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 cargoConfirmed Dispatched, debited, never arrived across a 23-minute run
unload order: category, then ascending wsTypeConfirmed Five weapon types with list, alphabetical, stock-level and wsType orderings all made to disagree
consumer priority = .miz declaration orderConfirmed unitId, name and distance all eliminated; three consumers at exactly 8000.000 m
supplier preference = nearest, with fall-throughConfirmed List order reversed with fixed geometry; 300/700 split when the near supplier was short
inbound pipeline is per linkConfirmed One link measured exactly size; two links ran concurrently
outbound is serialized per consumerConfirmed 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 concurrentlyConfirmed 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 gridConfirmed 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 commodityConfirmed 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 supplierConfirmed 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 equivalentConfirmed All three in live chains; airfield transit 318.00 s against a 318.0 prediction
coalition must matchConfirmed Neutral airfield inert for 10 cycles; both directions fired within one cycle once blue
capture refunds to the supplier, despawning delivers to the consumerConfirmed 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 loadConfirmed 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 shortConfirmed 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 reclaimedConfirmed 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 ageConfirmed 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 passConfirmed 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 jetConfirmed 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 heldConfirmed 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 bugInferred 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 stockConfirmed Delta 0 across 90 s
dynamic spawn and player rearm consume stockReported From play, not measured here — a player in a slot is required
aircraft transport massPartial 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 munitionsConfirmed 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 linksConfirmed 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 bombingUnexplained 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 payloadConfirmed 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 massConfirmed 5,000 missiles and 90 t of fuel loaded into a 2,000 kg container left getCargoWeight() at exactly 2000
== on Warehouse handles is meaninglessConfirmed 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 clientsUntested Runs used --server, so the server code path, but never with a client connected