Simulator + LLM. Baseline and first prompt changes

Today in short

  • Baseline (18 runs). With the current prompt (whole mine state plus action list), the LLM behaves like greedy but is much slower. 9 of 18 runs land within 0.5% of greedy, 8 are clearly worse, and only 1 is clearly better. It never comes close to the MILP with the same information.
  • More information doesn’t help it. Full, blind and partial give similar results, and sometimes full is the worst of the three.
  • Prompt change 1. Actions only, grade on each extraction. Each call has about 2.6 times fewer tokens and each decision is 1.7 to 2.9 times faster (about 10 s instead of 17 to 30 s). But NPV dropped, badly on the waste-cover mine (−90% vs greedy).
  • Prompt change 2. Add a short, general description of the mine structure. The decoy mine recovered fully to greedy level. The waste-cover mine got even worse and finished with a negative NPV.
  • The cause. For every extraction, the number the LLM is given (npv_impact_per_unit) is the mining cost only, the same for every block. Greedy ranks by a different number that also includes the revenue of the ore the extraction frees up. The LLM noticed the rich block, then trusted the number and mined waste.

Setup

The loop is simulator → possible actions → LLM picks one → simulator applies it, repeated until the end. All runs used the six 27-block mines from my MILP experiments, with the same shafts, drilling noise and costs, and greedy reproduces my earlier numbers exactly on all six. The model is gpt-oss (20B) running locally, at temperature 0 with a fixed seed.

What the LLM was given in the baseline

Every call contained:

  1. A system prompt (the same every call). Its role (“maximize NPV”), what each mine-state entry means (block, grade, tons to extract, tons to process), the two action formats and their amount rules, three rules (“process only after full extraction”, “maximize NPV”, “you do not need to track time”), and the JSON reply format.
  2. The current step. Last action, current NPV, period number, the whole mine state and the list of possible actions.
  3. History. The last 3 prompts and the LLM’s 3 replies.

What it was never told: revenue and cost per tonne, capacities, the number of periods, the discount rate, the blend limits, and which blocks sit above which. Its own reasoning says so: “processing revenue per ton? Not given… So we cannot compute NPV.”

Baseline results (current prompt, 18 runs)

Mine LLM full LLM blind LLM partial Greedy Full MILP
3D blend lower limit 14,372 14,372 14,372 14,372 15,329
3D central cone 726 2,574 717 898 4,673
3D central pipe −594 −594 −594 −594 1,488
3D decoy 6,576 5,546 6,598 6,598 9,074
3D waste cover 1,773 1,693 1,693 2,500 3,277
Dipping orebody 13,618 12,311 12,486 13,651 13,681
  • It mostly copies greedy. 9 of 18 runs are within 0.5% of greedy, several to the cent. 8 are clearly worse, by 8.5 to 32%. Only central cone blind is clearly better (+186%), and that run also had the most invalid replies (15), so it needs repeating before I trust it.
  • It never beats the MILP with the same information, except on waste cover blind and partial. That’s because blind MILP is weak there, not because the LLM is strong. It is still 32% below greedy.
  • More information doesn’t help. Central cone gives 726 with full information and 2,574 blind. Waste cover partial is identical to blind even though the shaft shows the rich layer. The LLM seems to decide from the action list and mostly ignore the mine state.
  • Speed. 656 actions took about 3.9 hours, roughly 21 seconds per action. MILP and greedy take under a second per mine.

Prompt change 1. Actions only, with the grade on each extraction

Changes from the baseline:

  • Mine state removed from every prompt, together with its description in the system prompt.
  • Grade added to each extraction action. (1, block, ore_concentration, max_tons, npv_impact_per_unit)
  • Processing rule simplified to “choose an amount between min and max”. The LLM isn’t told about grade blending, it only needs an amount within the limits.
  • Numbers rounded to 2 decimals (no more 2.239999999999995).

A side effect is that full, blind and partial now produce identical prompts. A block only appears as an extraction action once the blocks above it are mined, and by then its grade is always known. Hidden blocks and shaft estimates only ever appeared in the mine state. So one run per mine is enough for this comparison.

Prompt change 2. Add the mine structure

Same as change 1, plus three general lines in the system prompt. The size comes from each mine, and there is no numbering or layout detail.

Mine Structure:
The mine is a 3 x 3 x 3 (x by y by z) block model.
Precedence: a block can only be extracted after every block touching it in the layer directly above has been fully extracted.
Uncertainty: extracting blocks uncovers the blocks beneath them. The grade of a lower block may be higher or lower than the grade of the blocks above it.

Results on two mines (full)

Mine Prompt NPV vs greedy Actions Minutes Seconds per action
3D decoy baseline 6,576 −0.3% 36 18.0 30.0
3D decoy actions + grade 5,524 −16.3% 45 7.9 10.5
3D decoy actions + grade + structure 6,582 −0.2% 60 17.8 17.8
3D waste cover baseline 1,773 −29.1% 74 21.0 17.0
3D waste cover actions + grade 242 −90.3% 36 6.0 10.0
3D waste cover actions + grade + structure −2,943 −217.7% 78 31.8 24.5
  • Speed. Removing the mine state cut tokens per call from a median of about 2,700 to about 1,000, and each decision got 1.7 to 2.9 times faster. Adding the structure lines slowed it again, probably because the model reasons more and takes more, smaller steps (60 and 78 actions).
  • Decoy. Three structure lines were enough to replace the whole mine state. Same NPV as the baseline with a much smaller prompt.
  • Waste cover. It got worse with each change.

Why the LLM lost money on waste cover

Waste cover has a poor surface (grade 0.1 everywhere) over a rich middle layer. Period by period, against greedy:

Period Greedy LLM (actions + grade + structure)
1 blocks 1, 2 blocks 1, 2 (same)
2 2, 3 2, 3 (same)
3 3, 4, 5 3, 4, 5, plus 1 to 2 t nibbles on 6, 7, 9
4 finishes 5, mines block 10 (grade 0.6), processes 30 t finishes 5, so block 10 becomes available, but mines surface blocks 6, 8, 9
5 block 10 and 6, processes 20 t blocks 6 and 7, only 1.7 t of block 10
6 block 11, processes 40 t blocks 7, 8, 9 (all waste)
NPV 2,500 −2,943

So the LLM stripped exactly the right blocks to reach the ore, the same as greedy. Then it walked past the ore. In period 4 it was offered:

(1, 6, 0.1, 21.0, -6.83), (1, 7, 0.1, 21.0, -6.83), (1, 8, 0.1, 21.0, -6.83), (1, 9, 0.1, 21.0, -6.83), (1, 10, 0.6, 21.0, -6.83)

Its reasoning, word for word:

“All have same cost per unit. But block 10 has ore_concentration 0.6, but cost same? … extraction cost is negative NPV per unit, independent of ore concentration. So choose any. But maybe we want to extract block 10 because it might unlock processing later? But we don’t have processing actions yet. So choose block 6.”

It saw the higher grade and even guessed that block 10 might lead to processing. Then it trusted the number. With a tie, it took the first block in the list, which was waste. No processing action ever appeared to correct it, because 0.1-grade ore is below the 0.4 blend limit and can’t be processed on its own.

The real problem. Two different “values”

The LLM and greedy rank actions by different numbers.

  • What the LLM sees (npv_impact_per_unit). For an extraction it is only the discounted mining cost, −10 / 1.1^t per tonne. In period 4 that’s −6.83 for every block, waste or ore.
  • What greedy uses (npv_delta). The same mining cost plus the discounted revenue of the ore that extraction makes processable this period, if processing capacity and the blend limits allow.

For the period 4 choice above (approximate, worked out from the code):

  Block 6 (grade 0.1) Block 10 (grade 0.6)
What the LLM saw per tonne −6.83 −6.83
Mining cost of 21 t −143 −143
Ore freed 2.1 t, below blend limit 12.6 t, processable
Revenue added 0 about +861
What greedy saw about −143 about +717

For processing actions the two numbers are effectively the same, which explains why the LLM matches greedy so closely on mines where processing drives the choices. It also means the LLM’s greedy fallback, which ranks by npv_impact_per_unit, can’t tell ore from waste either.

What I take from today

  • A smaller prompt is much faster and loses nothing when the obvious move is the right one (decoy).
  • Describing the structure isn’t enough when the LLM has to choose between blocks that only differ in what they lead to. The number it trusts says they’re all the same.
  • The LLM isn’t confused about mining. It is misled by the value it is given. Fixing that number is more important than adding more text.

Next

  • New prompt version. Actions only, with each extraction’s value including the ore it frees up (the same npv_delta greedy uses) instead of the flat mining cost. Then rerun decoy and waste cover.
  • Let the simulator pick the amount so the LLM only chooses the block. This stops the 1 t nibbles and cuts the number of calls.
  • Soften the Uncertainty line to a plain fact, so it doesn’t read as an instruction to keep digging.
  • Compare models. Try qwen3:14b and llama3.1:8b locally, and possibly gpt-oss-120b through Groq, to see whether a bigger model makes better use of the same information.