Skip to content

conveyor-engine: a queue-coupled discrete conveyor's slat contents are not integral, contra conveyors.md 6.4 rule 1 #950

Description

@bpowers

A queue-coupled DISCRETE conveyor admits fractional volume, so its slat contents and exits are not integral. docs/design/conveyors.md §6.4 rule 1 says they must be. One of the two is wrong, and deciding which changes numerics.

The spec claim

docs/design/conveyors.md §6.4 rule 1 (~line 702-707), of a DISCRETE conveyor:

Un-inserted carry is never taken from upstream -- an inflow's reported rate reflects only its inserted units, so conservation and the capacity bound both hold exactly. Slat contents therefore stay integral, and exits arrive as integral lumps. Conveyor-driven inflow bypasses quantization entirely (never blocked -- §4.3; it is already integral when the upstream is discrete, which the queue-fed case mandates).

The bolded parenthetical is load-bearing: coupled admission skips quantization because the incoming volume is assumed already integral. As implemented, it isn't.

Why the assumption fails

A queue-coupled conveyor MUST be discrete (XMILE 3.7.2, enforced as a compile error; conveyors.md §11). But nothing on the coupled admission path quantizes:

  • src/simlin-engine/src/conveyor.rs admission_budget (~line 1072-1087) returns cap_room.min(limit_vol) -- a continuous volume, no floor.
  • src/simlin-engine/src/conveyor.rs consume_inflow_budget (~line 1097-1101) does self.in_carry += vol for the discrete case. Nothing rounds vol to whole units.
  • The coupled volume enters via phase_b's conv_inflows (the conveyor-driven inflow path), which by rule 1's own words "bypasses quantization entirely".
  • The queue's serve is not integral under the XMILE defaults: src/simlin-engine/src/xmile/variables.rs conveyor_to_datamodel defaults one_at_a_time = true, batch_integrity = false (line ~320). With batch_integrity = false, QueueState::take_for_conveyor (src/simlin-engine/src/queue.rs ~line 197) splits its front cohort fractionally to fill exactly the room the belt reports.

So the belt receives a fractional volume and inserts it unquantized.

Empirical demonstration

test/conveyors/queue_coupled_conveyor.xmile (added by #924): discrete belt, len 1, dt 0.5, capacity 2 + STEP(20, 2), queue fed by arrivals = TIME + 1, queue has an <overflow/>.

$ cargo run -q -p simlin-cli -- simulate test/conveyors/queue_coupled_conveyor.xmile

time   belt    boarding  departing
0      0       1         0
0.5    0.5     1.5       0
1      1.25    2         1
1.5    1.75    2         1.5
2      2       3         2
2.5    2.5     3.5       2
3      3.25    4         3

Belt contents run 0.5, 1.25, 1.75, 2.5, 3.25 -- nothing integral. Exits are likewise fractional. Per §6.4 rule 1 they should all be whole units.

This is not a backend-parity bug: the wasm belt pass (src/simlin-engine/src/wasmgen/belt.rs) mirrors the VM bit-for-bit and #924's corpus gate pins that agreement. Both backends share the divergence from the spec.

Why this needs a decision, not just a patch

Two resolutions, and picking the wrong one silently changes numerics:

  1. The spec's parenthetical is wrong. Perhaps Stella really does admit fractional coupled volumes when batch_integrity = false. Then §6.4 rule 1's "slat contents therefore stay integral" sentence and its parenthetical need correcting. The repo's standing rule is Stella-over-spec (docs/design/conveyors.md preamble: a Stella disagreement is a spec bug to fix, not a delta to document). This needs fact-finding against Stella, not a guess.

  2. The VM under-quantizes. If Stella keeps coupled admission integral, then admission_budget should floor its result (and/or take_for_conveyor should never split a batch below a whole unit when the destination is discrete). BOTH backends must move in the same commit -- conveyor.rs and wasmgen/belt.rs -- exactly as engine: <leak_integers/> is silently ignored on an exponential-leak conveyor #942 requires for its own VM quirk.

Urgency: the corpus is about to freeze the possibly-wrong answer

test/conveyors/queue_coupled_conveyor.xmile is now an integration-corpus fixture (simulate_special_path in src/simlin-engine/tests/integration/simulate.rs) with the VM as its oracle. That makes the current, possibly-wrong VM behavior the permanent corpus answer. Whichever way this resolves, that fixture's expected trajectory changes and its checked-in-by-implication values must be re-derived. Resolve this before the fixture's numbers calcify.

Components affected

  • src/simlin-engine/src/conveyor.rs (admission_budget, consume_inflow_budget)
  • src/simlin-engine/src/queue.rs (take_for_conveyor)
  • src/simlin-engine/src/wasmgen/belt.rs (must move in lockstep)
  • docs/design/conveyors.md §6.4 rule 1
  • test/conveyors/queue_coupled_conveyor.xmile + src/simlin-engine/tests/integration/simulate.rs

Related

Identified during review of #924 on branch conveyor-engine.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions