A building has one output buffer per item it can produce, so the singular
caption no longer matches what the section lists -- and the input side, which
has always had one buffer per material, already read "Input buffers".
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ne3mejABZoLWKLh8fgpM3x
Implements the requirements from 8401b20. OutputBuffer's type-blind `capacity`
becomes a `caps` map keyed by item, mirroring InputBuffer. The items stay in
one production-ordered vector, because that order is what the output port hands
out (REQ-MAT-OUTPUT-EMERGE); only the capacity is now per type, so one item's
backlog no longer occupies another's room.
The cycle gate moves ahead of the roll in tickProduction: recipeOutputsFit()
requires every output a cycle could produce to fit, which for a deterministic
recipe is just its own outputs and for the Reprocessing Plant is every possible
roll. That is what denies the reroll the old one-item cap was there for -- a
full buffer for one item stops the plant entirely rather than letting it keep
producing the others -- while letting it hold two cycles' worth per outcome and
produce amounts above one.
getCycleOutputItemCount(), which judged the plant by the smallest amount any
roll could yield, is gone: it only existed because the classifier could not know
the roll, and the all-outcomes rule needs no such approximation. Its unlock
caveat goes with it, since a locked item is never produced and so never fills.
The Salvage Bay's config-defined capacity becomes its single scrap cap, and the
shipyard simply has no caps at all. The checksum hashes the map the way the
input caps are hashed, so determinism is unchanged.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ne3mejABZoLWKLh8fgpM3x
The Reprocessing Plant held at most one cycle's output, so a single item
anywhere in the building -- buffer or still emerging -- stopped the next cycle.
That cap existed only to deny a reroll: with room for one item, stalling the
output belt could not make the plant discard a roll and try again.
Replace it with the general rule. Every item a recipe can produce gets its own
buffer of twice its per-cycle amount, mirroring the input side, and a cycle
starts only when every output it could produce fits. For a deterministic recipe
that is just its own outputs, so nothing changes there. For the plant it means
all possible rolls must fit, which denies the reroll as before -- a full buffer
for one item stops the plant entirely rather than letting it go on producing
only the others -- while allowing an output amount above one.
Also pins what "the next buffered item" means at the output port now that one
queue no longer implies it: items leave in production order whatever their
type. Status light and output chip wording follow, since a buffer short of a
full cycle's worth blocks exactly as a full one does.
Requirements only; the implementation follows.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ne3mejABZoLWKLh8fgpM3x
tickProduction and tickShipyardProduction cleared the finished cycle and then
refused to start another in the same tick, so every cycle really took its
duration plus one tick. The cost scales inversely with duration: a 1s recipe
ran at 31 ticks instead of 30 and lost 3.2% of its throughput, a 12s recipe
0.3%, which skewed the relative rates the recipe tree is tuned around.
Fall through from completion into the start attempt instead. The start code is
straight-line, so at most one cycle still begins per building per tick, even
for a duration that rounds to zero ticks, and the outputs just deposited count
against the space check, so a cycle whose output no longer fits waits exactly
as it did before.
The balancing model already computed rates as amount / duration_seconds
(tools/threat_report.py), so this brings the simulation up to the documented
numbers rather than moving them. The combat arena never ticks production, so
its tuning is untouched.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ne3mejABZoLWKLh8fgpM3x
tickProduction never starts a cycle in the tick one completed, so a building
running back to back is idle for exactly one tick per cycle. The status light
classified that tick as "output full" -- hasInputsToStart looks at the input
buffers alone -- so the pill blinked green/yellow once per cycle, roughly once
a second on a one-second recipe.
REQ-UI-STATUS-LIGHT already says a building that is idle yet blocked by
neither condition shows green. Add canStartCycle(), which requires a candidate
recipe's inputs *and* room for its output, and classify on it; yellow now
needs an output buffer that genuinely cannot take the next cycle. The
Reprocessing Plant rolls its output in the simulation, so the classifier
judges it by the smallest amount any roll could yield.
tickProduction's space check goes through the same outputBufferHasRoom(), so
the simulation and the light cannot drift apart.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ne3mejABZoLWKLh8fgpM3x
The three block-icon call sites each spelled out the same expression that
ItemIconCache::getInlineIcon() now is: the bare icon at the line's text
height, null when the file is missing. They call the helper instead, and
HeaderBar::blockIcon() -- which was that expression and nothing else --
is gone along with its stale "loaded once on first use" comment.
Each site still passes its own widget font, so nothing about the sizing
changes; the rule now lives in one place rather than four.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ne3mejABZoLWKLh8fgpM3x
The debris card's row now reads "Remaining" with the amount and the bare
scrap icon after it, and the count summary's indented sub-row states its
total the same way; both fall back to naming the item in words when no
scrap icon file exists.
StatRow gained the value-plus-icon form: a label draws either text or a
pixmap, so the two are composed into one via renderCaptionWithIcon, with
the color passed in because a pixmap does not follow the palette. That
also closes a gap in the building multi-selection, whose "Total cost" was
specified to carry the building_block icon and showed the bare number.
ItemIconCache::getInlineIcon() packages the sizing rule of the bare inline
icon. It sits on the cache rather than in IconCaption so StatRow keeps
pulling in nothing but the caption helper: StatRow.cpp is compiled into
DotaFactory_balancing, which links neither the ui library nor QtSvg, and
that target now compiles IconCaption.cpp along with it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ne3mejABZoLWKLh8fgpM3x
The debris panel's stat row spelled out "Scrap remaining". It now reads
"Remaining" with the amount followed by the bare scrap icon -- the same
inline form the header bar uses for building blocks -- and falls back to
the old caption when no scrap icon file exists. The count summary's
indented sub-row states its sum the same way.
REQ-UI-ITEM-ICON named the bare inline icon as a building_block special
case; it now states the general rule (an icon standing in for the item's
name beside a number is drawn without its square) and keeps the boundary
explicit: a value shown as an item still takes the square.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ne3mejABZoLWKLh8fgpM3x
Recipe display names come from the id, so a recipe that shared its id with
its output item showed the item's name back to the player. Every recipe id
now names the process that runs it: iron_mining, quartz_reduction,
steel_rolling, chip_etching, the *_framing hulls, the *_kit module prefabs,
and the drop-only shortcuts as direct_rolling/-etching/-hardening.
The three shortcut unlock groups are renamed with their recipes, since a
group's name is derived from its id the same way.
Blueprints and replays store recipe ids, so files recorded before this
rename lose their miner and assembler recipe selections.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ne3mejABZoLWKLh8fgpM3x