Skip to content

Commit 7e83267

Browse files
committed
worldline: make stockoutShare reflect interaction ratio
1 parent 29a911f commit 7e83267

2 files changed

Lines changed: 97 additions & 78 deletions

File tree

docs/architecture/foundation/worldline-chronicle.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ powershell -NoProfile -ExecutionPolicy Bypass -File scripts\run_soak.ps1 -UseMsv
2121

2222
包含:
2323
- `schema_version`:Worldline 自身 schema 版本(独立于 Telemetry schema)
24+
- 备注:当前实现已升级到 `schema_version=2``stockoutShare*` 口径更新,见下)。
2425
- `worldFolder``windowSteps``stepsRequested``wallSecondsLimit?`
2526
- `agentCountRequested``agentCountInitial`
2627
- `worldVersion``lastSeed?`
@@ -39,7 +40,7 @@ powershell -NoProfile -ExecutionPolicy Bypass -File scripts\run_soak.ps1 -UseMsv
3940

4041
当前 v0 输出的 `metrics` 包括:
4142
- `criticalNeedRate`:窗口内 need 样本中 critical 的比例
42-
- `stockoutShareAny/Sources/Workshops`窗口内出现资源=0 的步占比(总/来源/工坊)
43+
- `stockoutShareAny/Sources/Workshops`窗口内“资源交互点缺货占比”的平均值(总/来源/工坊),计算方式:对每一步求 `(#current==0)/(#total)`,再在窗口内取平均。
4344
- `stepsWithAnyConsumption/Regen/Decay`:窗口内出现对应事件的步数
4445
- `actionEntropyBits`:行动类型分布熵(bit)
4546
- `plannerTargetEntropyBits`:Planner 目标交互点分布熵(bit)

src/apps/runtime_cli/Main.cpp

Lines changed: 95 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -774,9 +774,12 @@ struct ResourceEconomy {
774774
std::unordered_map<std::uint32_t, std::uint64_t> plannerTargetCounts;
775775
std::uint64_t socializeEvents{0};
776776
std::unordered_map<std::uint32_t, std::uint64_t> socializePartnerCounts;
777-
std::uint64_t stockoutStepsAny{0};
778-
std::uint64_t stockoutStepsSources{0};
779-
std::uint64_t stockoutStepsWorkshops{0};
777+
std::uint64_t stockoutInteractionSamplesAnyTotal{0};
778+
std::uint64_t stockoutInteractionSamplesAnyZero{0};
779+
std::uint64_t stockoutInteractionSamplesSourcesTotal{0};
780+
std::uint64_t stockoutInteractionSamplesSourcesZero{0};
781+
std::uint64_t stockoutInteractionSamplesWorkshopsTotal{0};
782+
std::uint64_t stockoutInteractionSamplesWorkshopsZero{0};
780783
std::uint64_t stepsWithAnyConsumption{0};
781784
std::uint64_t stepsWithAnyRegen{0};
782785
std::uint64_t stepsWithAnyDecay{0};
@@ -799,9 +802,12 @@ struct ResourceEconomy {
799802
plannerTargetCounts.clear();
800803
socializeEvents = 0;
801804
socializePartnerCounts.clear();
802-
stockoutStepsAny = 0;
803-
stockoutStepsSources = 0;
804-
stockoutStepsWorkshops = 0;
805+
stockoutInteractionSamplesAnyTotal = 0;
806+
stockoutInteractionSamplesAnyZero = 0;
807+
stockoutInteractionSamplesSourcesTotal = 0;
808+
stockoutInteractionSamplesSourcesZero = 0;
809+
stockoutInteractionSamplesWorkshopsTotal = 0;
810+
stockoutInteractionSamplesWorkshopsZero = 0;
805811
stepsWithAnyConsumption = 0;
806812
stepsWithAnyRegen = 0;
807813
stepsWithAnyDecay = 0;
@@ -851,23 +857,28 @@ struct ResourceEconomy {
851857
};
852858
};
853859

854-
auto buildWorldlineMetricsJson = [&](const WorldlineWindowAgg& agg) {
855-
const double stepCount = static_cast<double>(std::max<std::uint64_t>(1, agg.steps));
856-
const double criticalNeedRate = agg.needSamples > 0 ? static_cast<double>(agg.criticalNeedSamples) / static_cast<double>(agg.needSamples) : 0.0;
857-
const double stockoutShareAny = agg.steps > 0 ? static_cast<double>(agg.stockoutStepsAny) / stepCount : 0.0;
858-
const double actionEntropy = entropyBitsFromCounts(agg.actionTypeCounts);
859-
const double plannerTargetEntropy = entropyBitsFromCounts(agg.plannerTargetCounts);
860-
const double plannerTravelCostMean = agg.plannerTravelCostSamples > 0 ? (agg.plannerTravelCostSum / static_cast<double>(agg.plannerTravelCostSamples)) : 0.0;
861-
862-
return json{
863-
{"criticalNeedRate", criticalNeedRate},
864-
{"stockoutShareAny", stockoutShareAny},
865-
{"stockoutShareSources", agg.steps > 0 ? static_cast<double>(agg.stockoutStepsSources) / stepCount : 0.0},
866-
{"stockoutShareWorkshops", agg.steps > 0 ? static_cast<double>(agg.stockoutStepsWorkshops) / stepCount : 0.0},
867-
{"stepsWithAnyConsumption", agg.stepsWithAnyConsumption},
868-
{"stepsWithAnyRegen", agg.stepsWithAnyRegen},
869-
{"stepsWithAnyDecay", agg.stepsWithAnyDecay},
870-
{"actionEntropyBits", actionEntropy},
860+
auto buildWorldlineMetricsJson = [&](const WorldlineWindowAgg& agg) {
861+
const double criticalNeedRate = agg.needSamples > 0 ? static_cast<double>(agg.criticalNeedSamples) / static_cast<double>(agg.needSamples) : 0.0;
862+
const double stockoutShareAny = agg.stockoutInteractionSamplesAnyTotal > 0
863+
? static_cast<double>(agg.stockoutInteractionSamplesAnyZero) / static_cast<double>(agg.stockoutInteractionSamplesAnyTotal)
864+
: 0.0;
865+
const double actionEntropy = entropyBitsFromCounts(agg.actionTypeCounts);
866+
const double plannerTargetEntropy = entropyBitsFromCounts(agg.plannerTargetCounts);
867+
const double plannerTravelCostMean = agg.plannerTravelCostSamples > 0 ? (agg.plannerTravelCostSum / static_cast<double>(agg.plannerTravelCostSamples)) : 0.0;
868+
869+
return json{
870+
{"criticalNeedRate", criticalNeedRate},
871+
{"stockoutShareAny", stockoutShareAny},
872+
{"stockoutShareSources", agg.stockoutInteractionSamplesSourcesTotal > 0
873+
? static_cast<double>(agg.stockoutInteractionSamplesSourcesZero) / static_cast<double>(agg.stockoutInteractionSamplesSourcesTotal)
874+
: 0.0},
875+
{"stockoutShareWorkshops", agg.stockoutInteractionSamplesWorkshopsTotal > 0
876+
? static_cast<double>(agg.stockoutInteractionSamplesWorkshopsZero) / static_cast<double>(agg.stockoutInteractionSamplesWorkshopsTotal)
877+
: 0.0},
878+
{"stepsWithAnyConsumption", agg.stepsWithAnyConsumption},
879+
{"stepsWithAnyRegen", agg.stepsWithAnyRegen},
880+
{"stepsWithAnyDecay", agg.stepsWithAnyDecay},
881+
{"actionEntropyBits", actionEntropy},
871882
{"plannerTargetEntropyBits", plannerTargetEntropy},
872883
{"plannerTravelCostMean", plannerTravelCostMean},
873884
{"resourceAttempts", agg.resourceAttempts},
@@ -886,13 +897,13 @@ struct ResourceEconomy {
886897
return 2;
887898
}
888899

889-
json meta;
890-
meta["kind"] = "runtime_worldline_meta";
891-
meta["schema_version"] = 1;
892-
meta["worldFolder"] = loadedWorldFolder.empty() ? std::string{} : std::filesystem::absolute(loadedWorldFolder).string();
893-
if (opts.worldgenConfigPath) {
894-
meta["worldgenConfig"] = resolvePathIfRelative(opts.rootPath, *opts.worldgenConfigPath).string();
895-
}
900+
json meta;
901+
meta["kind"] = "runtime_worldline_meta";
902+
meta["schema_version"] = 2;
903+
meta["worldFolder"] = loadedWorldFolder.empty() ? std::string{} : std::filesystem::absolute(loadedWorldFolder).string();
904+
if (opts.worldgenConfigPath) {
905+
meta["worldgenConfig"] = resolvePathIfRelative(opts.rootPath, *opts.worldgenConfigPath).string();
906+
}
896907
if (opts.worldgenSeed) {
897908
meta["worldSeed"] = *opts.worldgenSeed;
898909
} else if (runtime.lastSeed().has_value()) {
@@ -947,26 +958,35 @@ struct ResourceEconomy {
947958
}
948959
}
949960

950-
bool anyStockout = false;
951-
bool anyStockoutSource = false;
952-
bool anyStockoutWorkshop = false;
953-
bool anyConsumption = false;
954-
bool anyRegen = false;
955-
bool anyDecay = false;
956-
957-
for (const auto& resource : telemetry.resources) {
958-
if (resource.current == 0U) {
959-
anyStockout = true;
960-
const bool isWorkshop = workshopByInteraction.contains(resource.interactionId) ? workshopByInteraction.at(resource.interactionId) : false;
961-
if (isWorkshop) {
962-
anyStockoutWorkshop = true;
963-
} else {
964-
anyStockoutSource = true;
965-
}
966-
}
967-
if (resource.consumed > 0U) {
968-
anyConsumption = true;
969-
}
961+
bool anyConsumption = false;
962+
bool anyRegen = false;
963+
bool anyDecay = false;
964+
std::uint64_t stockoutAnyTotal = 0;
965+
std::uint64_t stockoutAnyZero = 0;
966+
std::uint64_t stockoutSourcesTotal = 0;
967+
std::uint64_t stockoutSourcesZero = 0;
968+
std::uint64_t stockoutWorkshopsTotal = 0;
969+
std::uint64_t stockoutWorkshopsZero = 0;
970+
971+
for (const auto& resource : telemetry.resources) {
972+
const bool isWorkshop = workshopByInteraction.contains(resource.interactionId) ? workshopByInteraction.at(resource.interactionId) : false;
973+
stockoutAnyTotal++;
974+
if (isWorkshop) {
975+
stockoutWorkshopsTotal++;
976+
} else {
977+
stockoutSourcesTotal++;
978+
}
979+
if (resource.current == 0U) {
980+
stockoutAnyZero++;
981+
if (isWorkshop) {
982+
stockoutWorkshopsZero++;
983+
} else {
984+
stockoutSourcesZero++;
985+
}
986+
}
987+
if (resource.consumed > 0U) {
988+
anyConsumption = true;
989+
}
970990
if (resource.produced > 0U) {
971991
anyRegen = true;
972992
}
@@ -975,28 +995,26 @@ struct ResourceEconomy {
975995
}
976996
}
977997

978-
if (anyStockout) {
979-
windowAgg.stockoutStepsAny++;
980-
}
981-
if (anyStockoutSource) {
982-
windowAgg.stockoutStepsSources++;
983-
}
984-
if (anyStockoutWorkshop) {
985-
windowAgg.stockoutStepsWorkshops++;
986-
}
987-
if (anyConsumption) {
988-
windowAgg.stepsWithAnyConsumption++;
989-
}
990-
if (anyRegen) {
998+
if (anyConsumption) {
999+
windowAgg.stepsWithAnyConsumption++;
1000+
}
1001+
if (anyRegen) {
9911002
windowAgg.stepsWithAnyRegen++;
9921003
}
993-
if (anyDecay) {
994-
windowAgg.stepsWithAnyDecay++;
995-
}
1004+
if (anyDecay) {
1005+
windowAgg.stepsWithAnyDecay++;
1006+
}
9961007

997-
for (const auto& attempt : telemetry.workshopAttempts) {
998-
windowAgg.productionAttempts++;
999-
const bool ok = attempt.failureReason.empty() && attempt.producedUnits > 0U;
1008+
windowAgg.stockoutInteractionSamplesAnyTotal += stockoutAnyTotal;
1009+
windowAgg.stockoutInteractionSamplesAnyZero += stockoutAnyZero;
1010+
windowAgg.stockoutInteractionSamplesSourcesTotal += stockoutSourcesTotal;
1011+
windowAgg.stockoutInteractionSamplesSourcesZero += stockoutSourcesZero;
1012+
windowAgg.stockoutInteractionSamplesWorkshopsTotal += stockoutWorkshopsTotal;
1013+
windowAgg.stockoutInteractionSamplesWorkshopsZero += stockoutWorkshopsZero;
1014+
1015+
for (const auto& attempt : telemetry.workshopAttempts) {
1016+
windowAgg.productionAttempts++;
1017+
const bool ok = attempt.failureReason.empty() && attempt.producedUnits > 0U;
10001018
if (ok) {
10011019
windowAgg.productionSucceeded++;
10021020
} else {
@@ -1208,14 +1226,14 @@ struct ResourceEconomy {
12081226
std::uint64_t stepsExecuted = 0;
12091227
bool endedByWallTime = false;
12101228

1211-
auto flushWorldlineWindow = [&](std::uint64_t windowEndStep) {
1212-
if (worldlineOut.has_value()) {
1213-
json window;
1214-
window["kind"] = "runtime_worldline_window";
1215-
window["schema_version"] = 1;
1216-
window["index"] = worldlineWindowIndex;
1217-
window["stepStart"] = worldlineWindowStartStep;
1218-
window["stepEnd"] = windowEndStep;
1229+
auto flushWorldlineWindow = [&](std::uint64_t windowEndStep) {
1230+
if (worldlineOut.has_value()) {
1231+
json window;
1232+
window["kind"] = "runtime_worldline_window";
1233+
window["schema_version"] = 2;
1234+
window["index"] = worldlineWindowIndex;
1235+
window["stepStart"] = worldlineWindowStartStep;
1236+
window["stepEnd"] = windowEndStep;
12191237
window["steps"] = (windowEndStep >= worldlineWindowStartStep) ? (windowEndStep - worldlineWindowStartStep + 1) : 0;
12201238
window["elapsedSeconds"] = std::chrono::duration_cast<std::chrono::duration<double>>(std::chrono::steady_clock::now() - worldlineStartedAt).count();
12211239
window["metrics"] = buildWorldlineMetricsJson(windowAgg);

0 commit comments

Comments
 (0)