Skip to content

Commit a16ab2d

Browse files
committed
telemetry: separate social targets from plannerTargets
1 parent 6487689 commit a16ab2d

9 files changed

Lines changed: 144 additions & 93 deletions

File tree

docs/architecture/foundation/runtime-api.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ Headless 工具:仓库提供 `genesis-runtime-cli`(见 `docs/guides/headless
5050
- 变更集合(按需扩展):
5151
- `resourceChanges[]` / `needChanges[]` / `plannerChanges[]` / `actionChanges[]` / `agentChanges[]` / `movementChanges[]`
5252

53-
## TickTelemetry 契约(v5
53+
## TickTelemetry 契约(v6
5454
最小字段集合,按需扩展;变更需提升 `schema_version` 并记录(GUI 以 schema 做兼容)。
5555

5656
完整字段字典与变更记录见:`docs/architecture/foundation/telemetry-schema.md`

docs/architecture/foundation/telemetry-schema.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Telemetry Schema(TickTelemetry v5
1+
# Telemetry Schema(TickTelemetry v6
22

33
本文档定义运行时对外暴露的 **TickTelemetry** 数据结构(GUI/工具链消费的只读快照内容之一),并提供最小的版本演进记录,作为“可观测性闭环”的协议基线。
44

@@ -20,13 +20,13 @@
2020
- 推荐约定(当前仓库采用偏保守策略):
2121
- **新增字段也提升版本**(即使是可选字段),用版本号显式驱动消费端适配。
2222

23-
## 2. TickTelemetry(v3)字段字典
23+
## 2. TickTelemetry(v6)字段字典
2424

2525
代码定义:`include/genesis/telemetry/TelemetryBuffer.hpp`
2626

2727
### 2.1 顶层字段
2828

29-
- `schema_version:uint32`:Telemetry 协议版本(当前为 5)。
29+
- `schema_version:uint32`:Telemetry 协议版本(当前为 6)。
3030
- `step:uint64`:模拟步号(离散时间)。
3131
- `stepSeconds:float`:单步的时间长度(秒),由 `SimulationClock::stepDuration()` 推导;用于把“每步统计”换算为“每秒速率”。
3232
- `agents:AgentSnapshot[]`:代理位置与身份摘要。
@@ -67,6 +67,7 @@
6767
- `currentAction:string`:当前动作类型(例如 `MoveToInteraction/ConsumeResource/TakeResource/ProduceResource/SocializeWithAgent/Idle` 等)。
6868
- `queueLength:uint32`:行动队列长度。
6969
- `target:InteractionId(uint32)`:当前动作目标(如移动/消耗/生产的交互点)。
70+
- `targetEntityId:uint32`:当前动作的实体目标(仅 `SocializeWithAgent` 有意义;否则为 0)。
7071
- `speed:float`:移动速度(仅在移动相关动作时有意义)。
7172
- `resource:ResourceType(enum)`:与当前动作相关的资源类型(例如消费/取货/生产的资源)。
7273
- `amount:uint32`:与当前动作相关的数量(例如本次想消耗/取货的单位数)。
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#pragma once
2+
3+
#include <cstdint>
4+
5+
namespace genesis::agents {
6+
7+
// PlannerDecision.target 编码约定:
8+
// - 真实世界交互点:InteractionId(最高位为 0)
9+
// - 代理实体目标(例如社交伙伴):最高位为 1,低 31 位存 entityId
10+
inline constexpr std::uint32_t kAgentPlannerTargetMask = 0x80000000u;
11+
12+
[[nodiscard]] inline bool isAgentPlannerTarget(std::uint32_t target) noexcept {
13+
return (target & kAgentPlannerTargetMask) != 0U;
14+
}
15+
16+
[[nodiscard]] inline std::uint32_t encodeAgentPlannerTargetEntityId(std::uint32_t entityId) noexcept {
17+
return kAgentPlannerTargetMask | (entityId & ~kAgentPlannerTargetMask);
18+
}
19+
20+
[[nodiscard]] inline std::uint32_t decodeAgentPlannerTargetEntityId(std::uint32_t target) noexcept {
21+
return (target & ~kAgentPlannerTargetMask);
22+
}
23+
24+
} // namespace genesis::agents
25+

include/genesis/telemetry/SchemaVersions.hpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
namespace genesis::telemetry {
66

7-
inline constexpr std::uint32_t kTickTelemetrySchemaVersion = 5;
7+
inline constexpr std::uint32_t kTickTelemetrySchemaVersion = 6;
88

99
} // namespace genesis::telemetry
10-

include/genesis/telemetry/TelemetryBuffer.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ struct ActionSnapshot {
4949
std::string currentAction;
5050
std::uint32_t queueLength{0};
5151
genesis::world::InteractionId target{0};
52+
std::uint32_t targetEntityId{0}; // 仅对 SocializeWithAgent 有意义
5253
float speed{0.0f};
5354
genesis::world::ResourceType resource{genesis::world::ResourceType::Food};
5455
std::uint32_t amount{0};

src/agents/NeedSatisfier.cpp

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <utility>
1010

1111
#include "genesis/agents/ActionSystem.hpp"
12+
#include "genesis/agents/PlannerTargetEncoding.hpp"
1213
#include "genesis/agents/Beliefs.hpp"
1314
#include "genesis/agents/Experience.hpp"
1415
#include "genesis/agents/Needs.hpp"
@@ -21,18 +22,16 @@ namespace genesis::agents {
2122

2223
namespace {
2324

24-
constexpr std::uint32_t kAgentTargetMask = 0x80000000u;
25-
2625
[[nodiscard]] std::uint32_t encodeAgentTarget(entt::entity entity) noexcept {
27-
return kAgentTargetMask | static_cast<std::uint32_t>(entt::to_integral(entity));
26+
return encodeAgentPlannerTargetEntityId(static_cast<std::uint32_t>(entt::to_integral(entity)));
2827
}
2928

3029
[[nodiscard]] bool isAgentTarget(std::uint32_t target) noexcept {
31-
return (target & kAgentTargetMask) != 0U;
30+
return isAgentPlannerTarget(target);
3231
}
3332

3433
[[nodiscard]] entt::entity decodeAgentTarget(std::uint32_t target) noexcept {
35-
return static_cast<entt::entity>(target & ~kAgentTargetMask);
34+
return static_cast<entt::entity>(decodeAgentPlannerTargetEntityId(target));
3635
}
3736

3837
[[nodiscard]] std::uint64_t mix_u64(std::uint64_t x) noexcept {

src/apps/runtime_cli/Main.cpp

Lines changed: 102 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include <nlohmann/json.hpp>
1818

1919
#include "genesis/agents/Personality.hpp"
20+
#include "genesis/agents/PlannerTargetEncoding.hpp"
2021
#include "genesis/runtime/Runtime.hpp"
2122
#include "genesis/runtime/SchemaVersions.hpp"
2223
#include "genesis/telemetry/SchemaVersions.hpp"
@@ -512,14 +513,15 @@ void normalizeScriptPaths(json& script, const std::filesystem::path& root) {
512513
out << "\n";
513514

514515
out << "## 动作与行为\n\n";
515-
if (summary.contains("actions") && summary.at("actions").is_object() && summary.at("actions").contains("counts")) {
516-
const auto& actions = summary.at("actions").at("counts");
517-
out << "- ConsumeResource: `" << actions.value("ConsumeResource", 0ULL) << "`\n";
518-
out << "- TakeResource: `" << actions.value("TakeResource", 0ULL) << "`\n";
519-
out << "- ProduceResource: `" << actions.value("ProduceResource", 0ULL) << "`\n";
520-
out << "- MoveToInteraction: `" << actions.value("MoveToInteraction", 0ULL) << "`\n";
521-
}
522-
out << "\n";
516+
if (summary.contains("actions") && summary.at("actions").is_object() && summary.at("actions").contains("counts")) {
517+
const auto& actions = summary.at("actions").at("counts");
518+
out << "- ConsumeResource: `" << actions.value("ConsumeResource", 0ULL) << "`\n";
519+
out << "- TakeResource: `" << actions.value("TakeResource", 0ULL) << "`\n";
520+
out << "- ProduceResource: `" << actions.value("ProduceResource", 0ULL) << "`\n";
521+
out << "- MoveToInteraction: `" << actions.value("MoveToInteraction", 0ULL) << "`\n";
522+
out << "- SocializeWithAgent: `" << actions.value("SocializeWithAgent", 0ULL) << "`\n";
523+
}
524+
out << "\n";
523525

524526
if (summary.contains("switching")) {
525527
const auto& switching = summary.at("switching");
@@ -762,19 +764,21 @@ struct ResourceEconomy {
762764
double utilizationSum = 0.0;
763765
std::uint64_t utilizationSamples = 0;
764766

765-
struct WorldlineWindowAgg {
766-
std::uint64_t steps{0};
767-
std::uint64_t needSamples{0};
768-
std::uint64_t criticalNeedSamples{0};
769-
double plannerTravelCostSum{0.0};
770-
std::uint64_t plannerTravelCostSamples{0};
771-
std::unordered_map<std::string, std::uint64_t> actionTypeCounts;
772-
std::unordered_map<std::uint32_t, std::uint64_t> plannerTargetCounts;
773-
std::uint64_t stockoutStepsAny{0};
774-
std::uint64_t stockoutStepsSources{0};
775-
std::uint64_t stockoutStepsWorkshops{0};
776-
std::uint64_t stepsWithAnyConsumption{0};
777-
std::uint64_t stepsWithAnyRegen{0};
767+
struct WorldlineWindowAgg {
768+
std::uint64_t steps{0};
769+
std::uint64_t needSamples{0};
770+
std::uint64_t criticalNeedSamples{0};
771+
double plannerTravelCostSum{0.0};
772+
std::uint64_t plannerTravelCostSamples{0};
773+
std::unordered_map<std::string, std::uint64_t> actionTypeCounts;
774+
std::unordered_map<std::uint32_t, std::uint64_t> plannerTargetCounts;
775+
std::uint64_t socializeEvents{0};
776+
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};
780+
std::uint64_t stepsWithAnyConsumption{0};
781+
std::uint64_t stepsWithAnyRegen{0};
778782
std::uint64_t stepsWithAnyDecay{0};
779783
std::uint64_t productionAttempts{0};
780784
std::uint64_t productionSucceeded{0};
@@ -785,19 +789,21 @@ struct ResourceEconomy {
785789
std::uint64_t resourceFailed{0};
786790
std::unordered_map<std::string, std::uint64_t> resourceFailureReasons;
787791

788-
void clear() {
789-
steps = 0;
790-
needSamples = 0;
791-
criticalNeedSamples = 0;
792-
plannerTravelCostSum = 0.0;
793-
plannerTravelCostSamples = 0;
794-
actionTypeCounts.clear();
795-
plannerTargetCounts.clear();
796-
stockoutStepsAny = 0;
797-
stockoutStepsSources = 0;
798-
stockoutStepsWorkshops = 0;
799-
stepsWithAnyConsumption = 0;
800-
stepsWithAnyRegen = 0;
792+
void clear() {
793+
steps = 0;
794+
needSamples = 0;
795+
criticalNeedSamples = 0;
796+
plannerTravelCostSum = 0.0;
797+
plannerTravelCostSamples = 0;
798+
actionTypeCounts.clear();
799+
plannerTargetCounts.clear();
800+
socializeEvents = 0;
801+
socializePartnerCounts.clear();
802+
stockoutStepsAny = 0;
803+
stockoutStepsSources = 0;
804+
stockoutStepsWorkshops = 0;
805+
stepsWithAnyConsumption = 0;
806+
stepsWithAnyRegen = 0;
801807
stepsWithAnyDecay = 0;
802808
productionAttempts = 0;
803809
productionSucceeded = 0;
@@ -810,34 +816,40 @@ struct ResourceEconomy {
810816
}
811817
};
812818

813-
auto buildWorldlineCountsJson = [](const WorldlineWindowAgg& agg) {
814-
json actionCountsJson = json::object();
815-
for (const auto& [k, v] : agg.actionTypeCounts) {
816-
actionCountsJson[k] = v;
817-
}
819+
auto buildWorldlineCountsJson = [](const WorldlineWindowAgg& agg) {
820+
json actionCountsJson = json::object();
821+
for (const auto& [k, v] : agg.actionTypeCounts) {
822+
actionCountsJson[k] = v;
823+
}
818824

819-
json plannerCountsJson = json::object();
820-
for (const auto& [k, v] : agg.plannerTargetCounts) {
821-
plannerCountsJson[std::to_string(k)] = v;
822-
}
825+
json plannerCountsJson = json::object();
826+
for (const auto& [k, v] : agg.plannerTargetCounts) {
827+
plannerCountsJson[std::to_string(k)] = v;
828+
}
823829

824-
json resourceFailureReasonsJson = json::object();
825-
for (const auto& [k, v] : agg.resourceFailureReasons) {
826-
resourceFailureReasonsJson[k] = v;
827-
}
830+
json socialJson = json::object();
831+
socialJson["events"] = agg.socializeEvents;
832+
socialJson["uniquePartners"] = agg.socializePartnerCounts.size();
833+
socialJson["partnerEntropyBits"] = entropyBitsFromCounts(agg.socializePartnerCounts);
834+
835+
json resourceFailureReasonsJson = json::object();
836+
for (const auto& [k, v] : agg.resourceFailureReasons) {
837+
resourceFailureReasonsJson[k] = v;
838+
}
828839

829840
json productionFailureReasonsJson = json::object();
830841
for (const auto& [k, v] : agg.productionFailureReasons) {
831842
productionFailureReasonsJson[k] = v;
832843
}
833844

834-
return json{
835-
{"actionTypes", std::move(actionCountsJson)},
836-
{"plannerTargets", std::move(plannerCountsJson)},
837-
{"resourceFailureReasons", std::move(resourceFailureReasonsJson)},
838-
{"productionFailureReasons", std::move(productionFailureReasonsJson)},
839-
};
840-
};
845+
return json{
846+
{"actionTypes", std::move(actionCountsJson)},
847+
{"plannerTargets", std::move(plannerCountsJson)},
848+
{"social", std::move(socialJson)},
849+
{"resourceFailureReasons", std::move(resourceFailureReasonsJson)},
850+
{"productionFailureReasons", std::move(productionFailureReasonsJson)},
851+
};
852+
};
841853

842854
auto buildWorldlineMetricsJson = [&](const WorldlineWindowAgg& agg) {
843855
const double stepCount = static_cast<double>(std::max<std::uint64_t>(1, agg.steps));
@@ -907,24 +919,33 @@ struct ResourceEconomy {
907919
std::uint64_t worldlineWindowStartStep = 0;
908920
std::uint64_t worldlineStep = 0;
909921

910-
auto ingestWorldlineWindow = [&](const genesis::telemetry::TickTelemetry& telemetry) {
911-
windowAgg.steps++;
912-
windowAgg.needSamples += telemetry.needs.size();
913-
for (const auto& need : telemetry.needs) {
922+
auto ingestWorldlineWindow = [&](const genesis::telemetry::TickTelemetry& telemetry) {
923+
windowAgg.steps++;
924+
windowAgg.needSamples += telemetry.needs.size();
925+
for (const auto& need : telemetry.needs) {
914926
if (need.critical) {
915927
windowAgg.criticalNeedSamples++;
916928
}
917929
}
918-
for (const auto& action : telemetry.actions) {
919-
windowAgg.actionTypeCounts[action.currentAction]++;
920-
}
921-
for (const auto& decision : telemetry.plannerDecisions) {
922-
windowAgg.plannerTargetCounts[decision.target]++;
923-
if (std::isfinite(decision.travelCost)) {
924-
windowAgg.plannerTravelCostSum += static_cast<double>(decision.travelCost);
925-
windowAgg.plannerTravelCostSamples++;
926-
}
927-
}
930+
for (const auto& action : telemetry.actions) {
931+
windowAgg.actionTypeCounts[action.currentAction]++;
932+
if (action.currentAction == "SocializeWithAgent") {
933+
windowAgg.socializeEvents++;
934+
if (action.targetEntityId != 0U) {
935+
windowAgg.socializePartnerCounts[action.targetEntityId]++;
936+
}
937+
}
938+
}
939+
for (const auto& decision : telemetry.plannerDecisions) {
940+
if (decision.target == 0U || genesis::agents::isAgentPlannerTarget(static_cast<std::uint32_t>(decision.target))) {
941+
continue;
942+
}
943+
windowAgg.plannerTargetCounts[decision.target]++;
944+
if (std::isfinite(decision.travelCost)) {
945+
windowAgg.plannerTravelCostSum += static_cast<double>(decision.travelCost);
946+
windowAgg.plannerTravelCostSamples++;
947+
}
948+
}
928949

929950
bool anyStockout = false;
930951
bool anyStockoutSource = false;
@@ -1027,16 +1048,19 @@ struct ResourceEconomy {
10271048
takeTicksByAgent[action.entityId]++;
10281049
}
10291050
}
1030-
for (const auto& decision : telemetry.plannerDecisions) {
1031-
plannerTargetCounts[decision.target]++;
1032-
if (decision.target != 0) {
1033-
const auto prev = lastPlannerTargetByAgent.contains(decision.entityId) ? lastPlannerTargetByAgent[decision.entityId] : 0U;
1034-
if (prev != 0 && prev != decision.target) {
1035-
plannerTargetSwitchesTotal++;
1036-
}
1037-
lastPlannerTargetByAgent[decision.entityId] = decision.target;
1038-
}
1039-
}
1051+
for (const auto& decision : telemetry.plannerDecisions) {
1052+
if (decision.target == 0U || genesis::agents::isAgentPlannerTarget(static_cast<std::uint32_t>(decision.target))) {
1053+
continue;
1054+
}
1055+
plannerTargetCounts[decision.target]++;
1056+
if (decision.target != 0U) {
1057+
const auto prev = lastPlannerTargetByAgent.contains(decision.entityId) ? lastPlannerTargetByAgent[decision.entityId] : 0U;
1058+
if (prev != 0 && prev != decision.target) {
1059+
plannerTargetSwitchesTotal++;
1060+
}
1061+
lastPlannerTargetByAgent[decision.entityId] = decision.target;
1062+
}
1063+
}
10401064

10411065
bool anyStockout = false;
10421066
bool anyStockoutSource = false;

src/engine/runtime/SnapshotDiff.cpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,10 +92,11 @@ std::string actionKey(const telemetry::ActionSnapshot& snapshot) {
9292
return std::to_string(snapshot.entityId);
9393
}
9494

95-
bool actionEqual(const telemetry::ActionSnapshot& lhs, const telemetry::ActionSnapshot& rhs) {
96-
return lhs.entityId == rhs.entityId && lhs.currentAction == rhs.currentAction && lhs.queueLength == rhs.queueLength && lhs.target == rhs.target &&
97-
lhs.speed == rhs.speed && lhs.resource == rhs.resource && lhs.amount == rhs.amount && lhs.reliefPerUnit == rhs.reliefPerUnit;
98-
}
95+
bool actionEqual(const telemetry::ActionSnapshot& lhs, const telemetry::ActionSnapshot& rhs) {
96+
return lhs.entityId == rhs.entityId && lhs.currentAction == rhs.currentAction && lhs.queueLength == rhs.queueLength && lhs.target == rhs.target &&
97+
lhs.targetEntityId == rhs.targetEntityId && lhs.speed == rhs.speed && lhs.resource == rhs.resource && lhs.amount == rhs.amount &&
98+
lhs.reliefPerUnit == rhs.reliefPerUnit;
99+
}
99100

100101
std::string agentKey(const telemetry::AgentSnapshot& snapshot) {
101102
return std::to_string(snapshot.entityId);

src/simulation/SimulationContext.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,7 @@ void SimulationContext::collectActionSnapshots(std::vector<telemetry::ActionSnap
347347
if (!queue.tasks.empty()) {
348348
const auto& task = queue.tasks.front();
349349
snap.target = task.interaction;
350+
snap.targetEntityId = task.targetEntityId;
350351
snap.speed = task.speed;
351352
snap.resource = task.resource;
352353
snap.amount = task.amount;

0 commit comments

Comments
 (0)