phase 6.8 checkpoint
This commit is contained in:
@@ -847,6 +847,25 @@ WeekScheduleRules, stats = {}, scoreHook = {}, warnings* )`:
|
||||
only) for the stat-driven choices (6.8.10).
|
||||
- Fallbacks warn once per distinct line per build.
|
||||
|
||||
**Runtime store (6.8.4a — runtime motion/traffic NOT flipped yet):**
|
||||
`NpcPopulationSystem::generateAreaPopulation` runs the weekly build
|
||||
alongside the daily one (seed `effSeed ^ kWeekScheduleSeedXor
|
||||
0x27d4eb2f`, default rules) and stores it on the area —
|
||||
`weekScheduleOf(registryId)` returns the `WeekSchedule`,
|
||||
`weekUsageOf(areaIndex)` the build's pick tally. The scoring flows
|
||||
through an adapter onto `systems/ScheduleScoring::score`, so
|
||||
`ecs.schedule.set_score_hook` steers weekly picks too; stats
|
||||
snapshots stay empty until 6.8.10. **Migration:** the
|
||||
`npcWork`/`npcInterest` registry columns rebind to the weekly
|
||||
resolves (the first week's sticky `work:<address>` and the first
|
||||
`leisure:<address>` pick; the old Interest placeholders remain only
|
||||
for tagless areas). Pure probes stay bit-identical; the runtime
|
||||
swap (motion `entryAt`/helpers + traffic legs) is 6.8.4b.
|
||||
`NpcScheduleDebugOverlay` (**F7**) gained a Week pane per selected
|
||||
NPC: Mon-Sun day selector (initial = the clock's day-of-week), a
|
||||
"today" line, From/To/Activity/**Query -> Location** rows and the
|
||||
usage tally (top-4).
|
||||
|
||||
Runtime is NOT wired yet (6.8.4): motion/traffic keep the daily
|
||||
`generateSchedules()` product; the week verifier
|
||||
`TownSchedule::weekEntryAt(week, day, hour)` exists for it.
|
||||
|
||||
@@ -4538,8 +4538,9 @@ feet sliding.
|
||||
### Phase 6.8 — DSL macros, symbolic schedules and POIs
|
||||
|
||||
**Status: IN PROGRESS — 6.8.9 stats foundation, 6.8.1 DSL macros,
|
||||
6.8.2 POI/tag model (a+b) and 6.8.3 weekly schedule generator DONE
|
||||
(2026-10-29); 6.8.4 runtime integration + migration next.** Rewritten
|
||||
6.8.2 POI/tag model (a+b), 6.8.3 weekly schedule generator and
|
||||
6.8.4a weekly store/F7/migration DONE (2026-10-29); 6.8.4b
|
||||
runtime day-tiling flip + week-rollover probe next.** Rewritten
|
||||
2026-10-28: the original bullet list mixed four independent features
|
||||
and contradicted the clock-pure motion core; it is now split into
|
||||
workstreams with explicit design decisions, task-sized pieces and
|
||||
@@ -4858,27 +4859,63 @@ symbolic schedule generator) next.**
|
||||
|
||||
**Task 6.8.4 — Runtime integration + migration (workstream B).**
|
||||
|
||||
**Status: 6.8.4a DONE (2026-10-29) — the weekly store, the registry
|
||||
rebind migration and the F7 week pane are in (runtime motion/traffic
|
||||
NOT flipped yet); the day-tiling flip + rollover probe are 6.8.4b.**
|
||||
|
||||
- `NpcPopulationSystem` stores weekly schedules per area and the
|
||||
resolved bindings; `NpcMotionSystem` reads the day-of-week tiling
|
||||
through the same `entryAt()` path (clock-pure, no latch);
|
||||
`NpcTrafficSystem` keeps deriving drive legs from the same
|
||||
entries, so walking and driving never disagree.
|
||||
- Indoor scheduling follows the entry contract (6.8.12): interior
|
||||
destinations approach via the building's entry POI; the symbolic
|
||||
tier completes interior entries entity-less there, the live tier
|
||||
despawns/respawns at the door — no behavior change vs the current
|
||||
`isIndoorKind` rules.
|
||||
- **Implemented (6.8.4a):** every area population build
|
||||
(`generateAreaPopulation`) now also runs the pure
|
||||
`generateWeekSchedules` (seed `effSeed ^ kWeekScheduleSeedXor
|
||||
0x27d4eb2f`, default rules) and stores it on the area:
|
||||
`weekSchedules` (registryId -> WeekSchedule) + `weekUsage` (the
|
||||
build's pick tally); accessors `weekScheduleOf(registryId)` /
|
||||
`weekUsageOf(areaIndex)`. The scoring flows through an adapter
|
||||
onto `systems/ScheduleScoring::score` — the Lua override
|
||||
(`ecs.schedule.set_score_hook`) steers weekly picks (exact hook
|
||||
contract: only the candidate + snapshot). Stats snapshots stay
|
||||
EMPTY this phase (6.8.10 samples them live at week start).
|
||||
**Migration:** the `npcWork`/`npcInterest` registry columns
|
||||
rebind to the weekly resolves — `npcWork` is now the first
|
||||
week's sticky job location (a real `work:<address>` when the
|
||||
area has Work locations, else the old Interest fallback) and
|
||||
`npcInterest` the week's first leisure pick (`leisure:<address>`,
|
||||
else curb/interest fallbacks) — the Interest placeholders are
|
||||
gone for tagged towns; the taxi "Send by taxi to interest/work"
|
||||
buttons and the F7 columns read the rebound values deterministically.
|
||||
- F7 overlay: **Week pane** (per selected NPC): Mon-Sun day
|
||||
selector (initial selection = the clock's day-of-week), a
|
||||
"today: <day> (clock day N)" line, per-entry rows
|
||||
From/To/Activity/**Query -> Location** (empty queries show the
|
||||
raw location) and the build's usage tally (top-4 in the pane;
|
||||
the full list lives in the location inspection).
|
||||
- Indoor scheduling follows the entry contract (6.8.12): unchanged
|
||||
this phase — the weekly entries resolve to approach-anchored
|
||||
LocationSet names like the daily ones do.
|
||||
- Migration: existing `npcWork` Interest placeholders rebind to the
|
||||
new Work locations once; schedules still recompute on load at this
|
||||
stage (6.8.10 turns the weekly store into saved state).
|
||||
- F7 overlay: week view per NPC (day selector), resolved location
|
||||
names next to their tag queries, per-location usage counts.
|
||||
- **Remaining (6.8.4b):** flip `NpcMotionSystem`'s active-entry
|
||||
evaluation and the shared `nextLocationChangeOf`/
|
||||
`walkTravelHours` helpers (and through them
|
||||
NpcTrafficSystem's drive-leg derivation + the claim replay) onto
|
||||
the weekly day-tiling (day-of-week = `(absoluteDay % 7)`, the
|
||||
daily schedule as the fallback), the week-rollover determinism
|
||||
probe, and the full `--test-*` battery re-run.
|
||||
- Done when: motion/traffic behave as before on a default town,
|
||||
weekly variety is visible in the overlay, and scrubbing the clock
|
||||
across a week boundary reproduces positions bit-exactly.
|
||||
- Verification: `--test-motion`, `--test-determinism`,
|
||||
`--test-traffic-schedule`, `--test-taxi`, `--test-parking` all
|
||||
pass; new week-rollover determinism probe.
|
||||
`--test-traffic-schedule`, `--test-taxi`, `--test-parking`,
|
||||
`--test-detail`, `--test-population-switch` all PASS unchanged
|
||||
(the runtime is untouched so far — weekly variety is visible in
|
||||
the overlay; the swap comes with 6.8.4b); the week probe in
|
||||
road_geometry_overlap_test grew the usage-tally identity check
|
||||
(picks == members-with-a-job + leisure picks).
|
||||
|
||||
**Task 6.8.5 — Schedule realism rules (workstream B, content).**
|
||||
|
||||
|
||||
@@ -258,7 +258,8 @@ std::vector<WeekSchedule> generateWeekSchedules(
|
||||
const TownLocations::LocationSet &locations, uint32_t seed,
|
||||
const WeekScheduleRules &rules,
|
||||
const std::unordered_map<uint64_t, StatsSnapshot> &stats,
|
||||
const ScoreHook &hook, std::vector<std::string> *warnings)
|
||||
const ScoreHook &hook, std::vector<std::string> *warnings,
|
||||
std::map<std::string, int> *usageOut)
|
||||
{
|
||||
const WeekIndex ix = buildWeekIndex(locations);
|
||||
|
||||
@@ -577,6 +578,11 @@ std::vector<WeekSchedule> generateWeekSchedules(
|
||||
|
||||
out.push_back(std::move(ws));
|
||||
}
|
||||
if (usageOut) {
|
||||
usageOut->clear();
|
||||
for (const auto &kv : usage.counts)
|
||||
(*usageOut)[kv.first] = kv.second;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
@@ -175,7 +176,11 @@ struct WeekScheduleRules {
|
||||
* failure is never fatal — every day tiles.
|
||||
*
|
||||
* @p stats maps a registry id to its week-start snapshot; only the
|
||||
* hook reads it (the default score ignores stats).*/
|
||||
* hook reads it (the default score ignores stats).
|
||||
*
|
||||
* @p usageOut, when set, receives the build's location-name ->
|
||||
* pick-count tally (decision 4 — the F7 week pane reports it; an
|
||||
* output of the build, never game state). */
|
||||
std::vector<WeekSchedule> generateWeekSchedules(
|
||||
const TownPopulation::Population &pop,
|
||||
const TownLocations::LocationSet &locations, uint32_t seed,
|
||||
@@ -183,7 +188,8 @@ std::vector<WeekSchedule> generateWeekSchedules(
|
||||
const std::unordered_map<uint64_t, StatsSnapshot> &stats =
|
||||
std::unordered_map<uint64_t, StatsSnapshot>(),
|
||||
const ScoreHook &hook = ScoreHook(),
|
||||
std::vector<std::string> *warnings = nullptr);
|
||||
std::vector<std::string> *warnings = nullptr,
|
||||
std::map<std::string, int> *usageOut = nullptr);
|
||||
|
||||
/** The active week entry at a day/hour; nullptr only for an empty
|
||||
* week (no tiling). */
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "CharacterSlotSystem.hpp"
|
||||
#include "RenderOriginSystem.hpp"
|
||||
#include "RoadSystem.hpp"
|
||||
#include "ScheduleScoring.hpp"
|
||||
#include "TerrainSystem.hpp"
|
||||
#include "TownGenSystem.hpp"
|
||||
#include "../GameMode.hpp"
|
||||
@@ -73,6 +74,8 @@ static const uint32_t kLocSeedXor = 0x85ebca6bu;
|
||||
|
||||
/* Schedule draws get their own decorrelation (phase 4.7.4). */
|
||||
static const uint32_t kScheduleSeedXor = 0xc2b2ae35u;
|
||||
/* 6.8.4a: the baseline week's seed discipline mirrors the others. */
|
||||
static const uint32_t kWeekScheduleSeedXor = 0x27d4eb2fu;
|
||||
|
||||
NpcPopulationSystem *NpcPopulationSystem::ms_instance = nullptr;
|
||||
|
||||
@@ -278,6 +281,62 @@ int NpcPopulationSystem::generateAreaPopulation(int areaIndex,
|
||||
ap.schedules = TownSchedule::generateSchedules(
|
||||
pop, ap.locations, effSeed ^ kScheduleSeedXor,
|
||||
TownSchedule::ScheduleRules());
|
||||
/* Phase 6.8.4a: the pure baseline WEEK builds alongside. The
|
||||
* stats snapshots stay empty until 6.8.10 samples them; the
|
||||
* scoring flows through systems/ScheduleScoring::score so the
|
||||
* Lua hook (ecs.schedule.set_score_hook) steers weekly picks.
|
||||
* Runtime motion/traffic keep the daily schedules this slice;
|
||||
* the F7 overlay previews the week. */
|
||||
{
|
||||
TownSchedule::ScoreHook hook =
|
||||
[](const TownSchedule::WeekCandidate &c,
|
||||
const TownSchedule::StatsSnapshot &snap) {
|
||||
ScheduleScoring::Candidate sc;
|
||||
sc.name = c.name;
|
||||
sc.tags = c.tags;
|
||||
sc.distanceMeters = c.distanceMeters;
|
||||
sc.usage = c.usage;
|
||||
sc.effects = c.effects;
|
||||
return ScheduleScoring::score(sc, snap);
|
||||
};
|
||||
std::vector<std::string> weekWarns;
|
||||
std::map<std::string, int> usage;
|
||||
std::vector<TownSchedule::WeekSchedule> weeks =
|
||||
TownSchedule::generateWeekSchedules(
|
||||
pop, ap.locations, effSeed ^ kWeekScheduleSeedXor,
|
||||
TownSchedule::WeekScheduleRules(), {}, hook,
|
||||
&weekWarns, &usage);
|
||||
for (const TownSchedule::WeekSchedule &w : weeks)
|
||||
ap.weekSchedules[w.registryId] = w;
|
||||
ap.weekUsage = usage;
|
||||
for (const std::string &w : weekWarns)
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"NpcPopulationSystem[" + area.name +
|
||||
"]: " + w);
|
||||
/* Migration (6.8.4a): the npcWork/npcInterest registry
|
||||
* columns rebind ONCE to the weekly resolves (real
|
||||
* Work locations + the week's first leisure pick)
|
||||
* instead of the Interest placeholders. */
|
||||
for (const TownSchedule::WeekSchedule &w : weeks) {
|
||||
CharacterRegistry::CharacterRecord *rec =
|
||||
reg.findCharacter(w.registryId);
|
||||
if (!rec)
|
||||
continue;
|
||||
std::string newWork, newInterest;
|
||||
for (const TownSchedule::WeekEntry &e : w.entries) {
|
||||
if (e.activity == "work" && newWork.empty())
|
||||
newWork = e.location;
|
||||
if (e.activity == "leisure" &&
|
||||
newInterest.empty())
|
||||
newInterest = e.location;
|
||||
}
|
||||
if (!newWork.empty())
|
||||
rec->stringColumns["npcWork"] = newWork;
|
||||
if (!newInterest.empty())
|
||||
rec->stringColumns["npcInterest"] =
|
||||
newInterest;
|
||||
}
|
||||
}
|
||||
m_areas[areaIndex] = ap;
|
||||
reg.setAutoSaveSuspended(false);
|
||||
|
||||
@@ -598,3 +657,23 @@ NpcPopulationSystem::scheduleOf(uint64_t registryId) const
|
||||
return &s;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const TownSchedule::WeekSchedule *
|
||||
NpcPopulationSystem::weekScheduleOf(uint64_t registryId) const
|
||||
{
|
||||
for (const auto &kv : m_areas) {
|
||||
std::unordered_map<uint64_t,
|
||||
TownSchedule::WeekSchedule>::const_iterator
|
||||
it = kv.second.weekSchedules.find(registryId);
|
||||
if (it != kv.second.weekSchedules.end())
|
||||
return &it->second;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const std::map<std::string, int> *
|
||||
NpcPopulationSystem::weekUsageOf(int areaIndex) const
|
||||
{
|
||||
auto it = m_areas.find(areaIndex);
|
||||
return it == m_areas.end() ? nullptr : &it->second.weekUsage;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include <flecs.h>
|
||||
@@ -97,6 +98,17 @@ public:
|
||||
* GameClockSystem::getTimeOfDayHours() for the current goal. */
|
||||
const TownSchedule::NpcSchedule *scheduleOf(uint64_t registryId) const;
|
||||
|
||||
/** The generated baseline WEEK of a population NPC (6.8.4a),
|
||||
* nullptr when unknown or the area carries no weekly build.
|
||||
* Runtime motion/traffic still consume the daily schedule;
|
||||
* the F7 overlay shows the week. */
|
||||
const TownSchedule::WeekSchedule *weekScheduleOf(
|
||||
uint64_t registryId) const;
|
||||
/** The weekly build's location usage tally (decision 4), null
|
||||
* when the area has no weekly build. Read-only — an output
|
||||
* of the generation, not game state. */
|
||||
const std::map<std::string, int> *weekUsageOf(int areaIndex) const;
|
||||
|
||||
/**
|
||||
* Per-frame update. In game mode the population is generated
|
||||
* automatically once the active terrain's road graph carries
|
||||
@@ -120,6 +132,15 @@ private:
|
||||
PoiDatabase::Set poiSet;
|
||||
TownLocations::LocationSet locations;
|
||||
std::vector<TownSchedule::NpcSchedule> schedules;
|
||||
/* The baseline WEEK per NPC (6.8.4a): the pure
|
||||
* generateWeekSchedules build. Keyed by registry id so
|
||||
* lookups per area hash. Runtime motion/traffic keep
|
||||
* the daily schedules for now (6.8.4b flips them). */
|
||||
std::unordered_map<uint64_t, TownSchedule::WeekSchedule>
|
||||
weekSchedules;
|
||||
/* The weekly build's location -> pick tally (decision
|
||||
* 4; F7 week pane). */
|
||||
std::map<std::string, int> weekUsage;
|
||||
/* Ids merged from authored components (markers/POIs) so
|
||||
* the periodic re-merge can replace exactly those —
|
||||
* runtime-added POIs appear within a second, no full
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "NpcScheduleDebugOverlay.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <imgui.h>
|
||||
|
||||
#include <map>
|
||||
@@ -397,6 +399,138 @@ void NpcScheduleDebugOverlay::render()
|
||||
}
|
||||
}
|
||||
ImGui::Separator();
|
||||
/* Phase 6.8.4a: the baseline WEEK view (the pure
|
||||
* generateWeekSchedules build; runtime motion
|
||||
* still consumes the daily schedule until 6.8.4b
|
||||
* flips it). */
|
||||
{
|
||||
const TownSchedule::WeekSchedule *wk =
|
||||
pop->weekScheduleOf(selId);
|
||||
if (wk && ImGui::CollapsingHeader(
|
||||
"Week",
|
||||
ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
static int weekDay = -1;
|
||||
if (weekDay < 0 && clock)
|
||||
weekDay =
|
||||
(int)std::floor(
|
||||
clock->getAbsoluteHours() /
|
||||
24.0) %
|
||||
7;
|
||||
static const char *dayNames[7] = {
|
||||
"Mon", "Tue", "Wed", "Thu",
|
||||
"Fri", "Sat", "Sun"
|
||||
};
|
||||
for (int d = 0; d < 7; ++d) {
|
||||
if (d)
|
||||
ImGui::SameLine();
|
||||
if (weekDay == d)
|
||||
ImGui::PushStyleColor(
|
||||
ImGuiCol_Button,
|
||||
IM_COL32(60, 90,
|
||||
60, 255));
|
||||
if (ImGui::SmallButton(
|
||||
dayNames[d]))
|
||||
weekDay = d;
|
||||
if (weekDay == d)
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
ImGui::Separator();
|
||||
if (clock)
|
||||
ImGui::TextDisabled(
|
||||
"today: %s (clock day %d)",
|
||||
dayNames[(int)std::floor(
|
||||
clock->getAbsoluteHours() /
|
||||
24.0) %
|
||||
7],
|
||||
(int)std::floor(
|
||||
clock->getAbsoluteHours() /
|
||||
24.0));
|
||||
if (ImGui::BeginTable(
|
||||
"weeksched", 4,
|
||||
ImGuiTableFlags_Borders |
|
||||
ImGuiTableFlags_RowBg)) {
|
||||
ImGui::TableSetupColumn(
|
||||
"From");
|
||||
ImGui::TableSetupColumn(
|
||||
"To");
|
||||
ImGui::TableSetupColumn(
|
||||
"Activity");
|
||||
ImGui::TableSetupColumn(
|
||||
"Query -> Location");
|
||||
ImGui::TableHeadersRow();
|
||||
for (const TownSchedule::
|
||||
WeekEntry &e :
|
||||
wk->entries) {
|
||||
if (e.day != weekDay)
|
||||
continue;
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%05.2f",
|
||||
e.startHour);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%05.2f",
|
||||
e.endHour);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::TextUnformatted(
|
||||
e.activity
|
||||
.c_str());
|
||||
ImGui::TableNextColumn();
|
||||
if (e.tagQuery.empty())
|
||||
ImGui::TextUnformatted(
|
||||
e.location
|
||||
.c_str());
|
||||
else
|
||||
ImGui::TextDisabled(
|
||||
"%s -> %s",
|
||||
e.tagQuery
|
||||
.c_str(),
|
||||
e.location
|
||||
.c_str());
|
||||
}
|
||||
ImGui::EndTable();
|
||||
}
|
||||
/* Decision 4: the build's usage
|
||||
* tally. */
|
||||
int areaIdxW =
|
||||
pop->areaIndexOf(selId);
|
||||
const std::map<std::string, int>
|
||||
*usage = pop->weekUsageOf(
|
||||
areaIdxW);
|
||||
if (usage && !usage->empty()) {
|
||||
ImGui::TextDisabled(
|
||||
"location usage: %zu distinct, top:",
|
||||
usage->size());
|
||||
std::vector<
|
||||
std::pair<std::string,
|
||||
int> >
|
||||
top(usage->begin(),
|
||||
usage->end());
|
||||
std::sort(
|
||||
top.begin(),
|
||||
top.end(),
|
||||
[](const std::pair
|
||||
<std::string, int>
|
||||
&a,
|
||||
const std::pair
|
||||
<std::string, int>
|
||||
&b) {
|
||||
return a
|
||||
.second >
|
||||
b.second;
|
||||
});
|
||||
for (size_t k = 0;
|
||||
k < top.size() && k < 4;
|
||||
++k)
|
||||
ImGui::TextDisabled(
|
||||
" %s: %d",
|
||||
top[k]
|
||||
.first
|
||||
.c_str(),
|
||||
top[k].second);
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui::Separator();
|
||||
if (ImGui::BeginTable("schedule", 4,
|
||||
ImGuiTableFlags_Borders |
|
||||
ImGuiTableFlags_RowBg)) {
|
||||
|
||||
@@ -7662,14 +7662,18 @@ static bool runTownWeekScheduleProbe()
|
||||
}
|
||||
|
||||
/* Usage spread report (the plan asks the probe to print max/
|
||||
* mean location usage). */
|
||||
* mean location usage). The tally equals the pick-bearing
|
||||
* entries: one per adult's job plus one per errand day. */
|
||||
{
|
||||
std::map<std::string, int> picks;
|
||||
int total = 0;
|
||||
int total = 0, pickEntries = 0;
|
||||
for (const TownSchedule::WeekSchedule &ws : week)
|
||||
for (const TownSchedule::WeekEntry &e : ws.entries) {
|
||||
++picks[e.location];
|
||||
++total;
|
||||
if (e.activity == "work" ||
|
||||
e.activity == "leisure")
|
||||
++pickEntries;
|
||||
}
|
||||
if (!picks.empty()) {
|
||||
size_t maxU = 0;
|
||||
@@ -7682,6 +7686,36 @@ static bool runTownWeekScheduleProbe()
|
||||
(double)maxU,
|
||||
(double)total / (double)picks.size());
|
||||
}
|
||||
/* The reported tally counts PICKS (decision 4): one per
|
||||
* member's sticky job (which then spans 5 work entries)
|
||||
* plus one per day's leisure pick. */
|
||||
std::map<std::string, int> usageOut;
|
||||
TownSchedule::generateWeekSchedules(pop, set, 42, rules,
|
||||
{}, {}, nullptr,
|
||||
&usageOut);
|
||||
int pickedTotal = 0;
|
||||
for (const auto &kv : usageOut)
|
||||
pickedTotal += kv.second;
|
||||
int membersWithWork = 0;
|
||||
int leisureEntries = 0;
|
||||
for (const TownSchedule::WeekSchedule &ws : week) {
|
||||
bool hasWork = false;
|
||||
for (const TownSchedule::WeekEntry &e :
|
||||
ws.entries) {
|
||||
if (e.activity == "work")
|
||||
hasWork = true;
|
||||
if (e.activity == "leisure")
|
||||
++leisureEntries;
|
||||
}
|
||||
if (hasWork)
|
||||
++membersWithWork;
|
||||
}
|
||||
if (pickedTotal != membersWithWork + leisureEntries) {
|
||||
printf(" *** usage tally %d != picks %d + %d "
|
||||
"***\n", pickedTotal, membersWithWork,
|
||||
leisureEntries);
|
||||
ok = false;
|
||||
}
|
||||
}
|
||||
|
||||
/* No-candidate fallbacks (decision 7): a tagless town with only
|
||||
|
||||
Reference in New Issue
Block a user