stop a building drifting into a recipe the player could not pick
An auto-recipe building adopted whatever recipe of its type consumed the material offered to it, without asking the unlock state. That was the last way a building could come to run a recipe the player could not have selected: the selection dialog hides those and the blueprint gate discards them, but a belt delivering the right material installed one regardless. Automatic selection now asks isRecipeUnlocked, the same question the dialog asks, plumbed in beside the isItemUnlocked the output pool already uses. Two callbacks, two questions, one unlock state -- rather than a second definition of "a recipe the player may run" written out inside the sim. A material whose only recipes are locked now selects nothing, and an unconfigured building has no input buffer, so that material is refused rather than swallowed: it stays on the belt and the line backs up behind an idle building. That is the intended failure -- a stalled belt is visible, a building quietly eating a material the player cannot use is not. Selection only. A recipe already set goes on producing under REQ-LOCK-OUTPUT-POOL, which deliberately never tests a single-group recipe. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ne3mejABZoLWKLh8fgpM3x
This commit is contained in:
@@ -16,10 +16,12 @@ ProductionSystem::ProductionSystem(const GameConfig& config,
|
||||
std::function<void(const std::string&, QVector2D,
|
||||
const std::optional<ShipLayoutConfig>&)> spawnShip,
|
||||
std::function<bool(const std::string&)> isItemUnlocked,
|
||||
std::function<bool(const std::string&)> isRecipeUnlocked,
|
||||
std::mt19937& rng)
|
||||
: m_config(config)
|
||||
, m_spawnShip(std::move(spawnShip))
|
||||
, m_isItemUnlocked(std::move(isItemUnlocked))
|
||||
, m_isRecipeUnlocked(std::move(isRecipeUnlocked))
|
||||
, m_rng(rng)
|
||||
{
|
||||
}
|
||||
@@ -157,6 +159,15 @@ void ProductionSystem::selectAutoRecipeIfUnset(Building& building, const ItemTyp
|
||||
{
|
||||
return;
|
||||
}
|
||||
// Only a recipe the player could have selected themselves (REQ-LOCK-UI-RECIPE): a
|
||||
// building must not drift into running one that can yield them nothing. Refusing it
|
||||
// leaves the building unconfigured and so without an input buffer, which is what
|
||||
// keeps the offered item on the belt rather than swallowing it into a building that
|
||||
// has no use for it (REQ-MAT-INPUT-INTAKE).
|
||||
if (!m_isRecipeUnlocked(recipe->id))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
building.recipeId = recipe->id;
|
||||
initBuffers(building, *recipe);
|
||||
|
||||
@@ -44,10 +44,14 @@ class BeltSystem;
|
||||
class ProductionSystem
|
||||
{
|
||||
public:
|
||||
// isItemUnlocked decides which output groups a cycle may pick (REQ-LOCK-OUTPUT-POOL);
|
||||
// isRecipeUnlocked decides which recipe an auto-recipe building may adopt
|
||||
// (REQ-BLD-AUTO-RECIPE). Two questions, asked of the same unlock state.
|
||||
ProductionSystem(const GameConfig& config,
|
||||
std::function<void(const std::string&, QVector2D,
|
||||
const std::optional<ShipLayoutConfig>&)> spawnShip,
|
||||
std::function<bool(const std::string&)> isItemUnlocked,
|
||||
std::function<bool(const std::string&)> isRecipeUnlocked,
|
||||
std::mt19937& rng);
|
||||
|
||||
// Advances every building's virtual input belts, delivers what arrives into the input
|
||||
@@ -93,12 +97,13 @@ private:
|
||||
std::vector<Item> rollOutputGroup(const RecipeDef& recipe);
|
||||
|
||||
const GameConfig& m_config;
|
||||
// Spawning a finished ship reaches into the entity model, and an output group's
|
||||
// eligibility into the unlock state; neither is factory data, so both arrive as
|
||||
// Spawning a finished ship reaches into the entity model, and the two unlock
|
||||
// questions into the unlock state; neither is factory data, so all three arrive as
|
||||
// callbacks rather than living in FactoryState.
|
||||
std::function<void(const std::string&, QVector2D,
|
||||
const std::optional<ShipLayoutConfig>&)> m_spawnShip;
|
||||
std::function<bool(const std::string&)> m_isItemUnlocked;
|
||||
std::function<bool(const std::string&)> m_isRecipeUnlocked;
|
||||
// The simulation's one RNG, by reference: a draw here shares the stream with every
|
||||
// other draw in the run, which is what makes a replay reproducible (docs/replay_design.md).
|
||||
std::mt19937& m_rng;
|
||||
|
||||
@@ -120,6 +120,7 @@ void Simulation::initializeSubsystems()
|
||||
m_shipSystem->spawn(id, pos, /*isEnemy=*/false, layout);
|
||||
},
|
||||
[this](const std::string& itemId) -> bool { return isItemUnlocked(itemId); },
|
||||
[this](const std::string& recipeId) -> bool { return isRecipeUnlocked(recipeId); },
|
||||
m_rng);
|
||||
m_constructionSystem = std::make_unique<ConstructionSystem>(m_config);
|
||||
m_deconstructionSystem = std::make_unique<DeconstructionSystem>(m_config);
|
||||
|
||||
Reference in New Issue
Block a user