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