give output-group eligibility one home
The rule that decides whether a group may be picked -- every item it yields unlocked, the group judged whole because its items are produced together (REQ-LOCK-OUTPUT-POOL) -- was written out inside rollOutputGroup, where nothing outside the sim could reach it. It moves next to producesItem and getProducibleItems in RecipesConfig.h, which is where the recipe-level queries already shared between lib and ui live. Deliberately narrow: the helper says whether a group is eligible, not whether eligibility is asked about at all. rollOutputGroup keeps its own reason for not asking when a recipe has a single group. No behaviour change; the pool's end-to-end test still covers the rule and a direct one now pins the judged-whole semantics. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ne3mejABZoLWKLh8fgpM3x
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <functional>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@@ -82,6 +83,24 @@ inline bool producesItem(const RecipeDef& recipe, const std::string& itemId)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// True when every item this group yields is unlocked. A group is judged whole -- its
|
||||||
|
// items are produced together, so one locked item disqualifies the group entirely, since
|
||||||
|
// taking it would hand the player a locked item (REQ-LOCK-OUTPUT-POOL).
|
||||||
|
//
|
||||||
|
// This says whether a group is eligible, not whether eligibility is asked about at all:
|
||||||
|
// that is the caller's, because the callers differ. A cycle picking its result does not
|
||||||
|
// ask where there is only one group to pick (see ProductionSystem::rollOutputGroup).
|
||||||
|
inline bool isOutputGroupUnlocked(
|
||||||
|
const RecipeOutputGroup& group,
|
||||||
|
const std::function<bool(const std::string&)>& isItemUnlocked)
|
||||||
|
{
|
||||||
|
for (const RecipeOutput& out : group.items)
|
||||||
|
{
|
||||||
|
if (!isItemUnlocked(out.item)) { return false; }
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
struct RecipesConfig
|
struct RecipesConfig
|
||||||
{
|
{
|
||||||
std::vector<RecipeDef> recipes;
|
std::vector<RecipeDef> recipes;
|
||||||
|
|||||||
@@ -67,20 +67,13 @@ std::vector<Item> ProductionSystem::rollOutputGroup(const RecipeDef& recipe)
|
|||||||
return itemsOf(recipe.outputGroups.front());
|
return itemsOf(recipe.outputGroups.front());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Several groups: only those whose items are all unlocked can be picked, and a group
|
// Several groups: only the unlocked ones can be picked (REQ-LOCK-OUTPUT-POOL), and
|
||||||
// holding any locked item is dropped whole, since its items come together
|
// weights are renormalized over what is left by discrete_distribution.
|
||||||
// (REQ-LOCK-OUTPUT-POOL). Weights are renormalized over what is left by
|
|
||||||
// discrete_distribution.
|
|
||||||
std::vector<const RecipeOutputGroup*> eligible;
|
std::vector<const RecipeOutputGroup*> eligible;
|
||||||
std::vector<double> weights;
|
std::vector<double> weights;
|
||||||
for (const RecipeOutputGroup& group : recipe.outputGroups)
|
for (const RecipeOutputGroup& group : recipe.outputGroups)
|
||||||
{
|
{
|
||||||
bool allUnlocked = true;
|
if (!isOutputGroupUnlocked(group, m_isItemUnlocked)) { continue; }
|
||||||
for (const RecipeOutput& out : group.items)
|
|
||||||
{
|
|
||||||
if (!m_isItemUnlocked(out.item)) { allUnlocked = false; break; }
|
|
||||||
}
|
|
||||||
if (!allUnlocked) { continue; }
|
|
||||||
eligible.push_back(&group);
|
eligible.push_back(&group);
|
||||||
weights.push_back(group.probability.value_or(1.0));
|
weights.push_back(group.probability.value_or(1.0));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1110,6 +1110,27 @@ TEST_CASE("BuildingSystem: a group with a locked item is never picked", "[buildi
|
|||||||
REQUIRE(produced > 0);
|
REQUIRE(produced > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("a group is judged whole against the unlock state", "[building][unlock]")
|
||||||
|
{
|
||||||
|
// The rule the pool applies, on its own (REQ-LOCK-OUTPUT-POOL): a group counts as
|
||||||
|
// unlocked only when every item it yields is, because its items are produced
|
||||||
|
// together and one of them being locked is enough to rule the group out.
|
||||||
|
RecipeOutputGroup group;
|
||||||
|
group.items.push_back(RecipeOutput{"voidsteel", 1});
|
||||||
|
group.items.push_back(RecipeOutput{"iron_ingot", 1});
|
||||||
|
|
||||||
|
const std::function<bool(const std::string&)> allUnlocked =
|
||||||
|
[](const std::string&) { return true; };
|
||||||
|
const std::function<bool(const std::string&)> voidsteelLocked =
|
||||||
|
[](const std::string& itemId) { return itemId != "voidsteel"; };
|
||||||
|
|
||||||
|
CHECK(isOutputGroupUnlocked(group, allUnlocked));
|
||||||
|
CHECK_FALSE(isOutputGroupUnlocked(group, voidsteelLocked));
|
||||||
|
|
||||||
|
// An empty group yields nothing to be locked out by, so nothing disqualifies it.
|
||||||
|
CHECK(isOutputGroupUnlocked(RecipeOutputGroup{}, voidsteelLocked));
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("BuildingSystem: reprocessing plant sizes one output buffer per possible roll",
|
TEST_CASE("BuildingSystem: reprocessing plant sizes one output buffer per possible roll",
|
||||||
"[building]")
|
"[building]")
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user