Files
bambuddy/backend/tests/unit/services
maziggy 935d4b5bbf Charge the tray the printer said it used, not the first one loaded (issue #2953)
A sliced file numbers its filaments 1..4; which AMS tray each came from is
decided when the job is sent. #2768 gave the Spoolman writer two ways to
recover that decision when the print did not come through Bambuddy: the
printer's own mapping field, and a colour match of the 3MF's slots against the
loaded trays. An A1 satisfies neither. It publishes no mapping field, and it
drops the MQTT connection when we subscribe to its request topic, so the
slicer's instruction never arrives either. That leaves the colour match, and it
compares hex strings exactly.

The reporter sliced with a generic black profile against a tray they had set to
charged slot 1 to whatever sat in the first tray -- 2.17 g onto a grey PLA+
spool, while the print was fed from tray 3. Their bundle carries the printer's
own answer: "Tray change during print: tray=3 at layer=0", recorded 90 seconds
in, and read further down the same completion pass by _print_used_tray_keys to
decide which slots the print had touched. The same pass then charged tray 0 on
a guess, and logged "AMS0-T3: remain% did not fall over the print" about the
tray that had actually done the work.

_single_slot_tray_from_state adds the third rung. For a print with exactly one
slot carrying usage, the one slot came from the one tray, so the printer's tray
reporting answers the question directly: the mid-print tray-change log, then
the tray loaded at print start, then the current one, then the last real tray
seen. That is the ladder usage_tracker.on_print_complete has consulted since it
started resolving mappings at completion -- Spoolman users were the only ones
not getting it, which is why an install running the built-in inventory has
never shown this. On this printer only the first and last rungs can fire:
tray_now_at_start is 255 because print start runs before the filament is
loaded, and the A1 parks tray_now back at 255 the moment a print ends.

Gated on exactly one slot with usage, like the internal writer: a multi-colour
print moves tray_now on every change, so one reading cannot then be attributed
to one slot. It also declines when the log holds more than one switch, because
an AMS-backup runout is split per segment (#1793) and a single-tray mapping
would land the whole print on one spool.

That gate needed the guess warning to exclude the split path too. The split
never reads slot_to_tray at all -- it charges each segment to the tray the
printer announced switching to, which is the same evidence this fallback is
built on -- so a declined mapping there is not a guess, and calling it one
suppressed the archive rewrite for exactly the prints whose attribution is best
supported. Nothing covered that combination; a test does now.

Where nothing names a tray the positional default still stands, because it is
right for an AMS loaded in slicer order. It now says so at warning level so a
support bundle carries the reason, and it no longer restamps the archive's
filament colour and material from a spool it picked by position. That restamp
is what made the fault read as data loss: the grams can be put back, whereas
overwriting what the slicer recorded leaves nothing to compare against, and the
reporter's archive had already been rewritten from #000000 to the wrong spool's
grey. A slot that consumed nothing also stops claiming a tray in the handled
set -- it was never charged, so the remain-delta path should stay free to cover
it rather than be suppressed by an estimate of zero.

The request-topic probe is the same failure reached from the other side. A
printer that refuses kills the TCP connection instead of returning a SUBACK
failure, so the only signal is "we subscribed, then got disconnected", and that
was believed the first time it happened. Every other reason a connection drops
inside the same window looks identical -- a network blip, the printer
rebooting, the container stopped mid-probe -- and the verdict was cached per
serial with no re-probe anywhere, so on a printer that supports the topic one
unlucky drop cost mapping capture for the rest of the process and every slicer
print after it was charged by tray position. It now takes two consecutive
drops, and a disconnect we asked for is not counted. A printer that genuinely
refuses answers the same way every time and pays one extra reconnect; one
already known to refuse still skips the subscription outright rather than
reopening a reconnect loop.
2026-08-25 16:22:43 +02:00
..