3 Commits

Author SHA1 Message Date
b1720dc2b3 drop the now-dead payloads from the sim-backed state-change events
TickAdvanced, BuildingBlocksChanged, ExpansionCostChanged, BossWaveUpdated and
ArtifactCountChanged all duplicated state the Simulation already owns. With
every subscriber re-reading from the sim, the fields had no readers left, so
the five events become payload-free refresh signals and the emitters keep the
values only as locals for change detection.

This makes the convention uniform: a state-change event backed by the
simulation carries nothing. Events whose state lives in the view (selection,
game speed, deconstruct and debug-draw modes) keep their payloads, since there
is no sim getter behind them.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YHcUerKAZKWNvSKJxYKbnG
2026-08-03 21:56:47 +02:00
099f0b55fc make HeaderBar read the tick, artifacts and boss wave from the simulation
The last three payloads HeaderBar still consumed as truth. All are backed by
Simulation getters (getCurrentTick, getArtifactCount, getBossWaveCounter,
getBossCountdownTicks) and the win count by world.artifacts.artifactWinCount,
so the handlers now re-read rather than trust the event.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YHcUerKAZKWNvSKJxYKbnG
2026-08-03 21:54:02 +02:00
b133a21914 make BlueprintPanel read the block stock from the simulation
BlueprintPanel was the second panel caching BuildingBlocksChangedEvent's
payload as truth; it already held a Simulation*, so refreshButtonStates()
now re-reads getBuildingBlocksStock() at the point of use, per the "events
are refresh signals" rule.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YHcUerKAZKWNvSKJxYKbnG
2026-08-03 21:53:19 +02:00
10 changed files with 37 additions and 47 deletions

View File

@@ -2,10 +2,9 @@
#include "Event.h"
// Fired when the collected artifact count changes. Carries no payload —
// subscribers re-read Simulation::getArtifactCount(), and the win count from
// world.artifacts.artifactWinCount.
class ArtifactCountChangedEvent : public Event
{
public:
ArtifactCountChangedEvent(int count, int winCount) : count(count), winCount(winCount) {}
const int count;
const int winCount;
};

View File

@@ -2,16 +2,11 @@
#define BOSS_WAVE_UPDATED_EVENT_H
#include "Event.h"
#include "Tick.h"
// Fired when the boss wave counter or its countdown changes. Carries no payload —
// subscribers re-read Simulation::getBossWaveCounter() and getBossCountdownTicks().
class BossWaveUpdatedEvent : public Event
{
public:
BossWaveUpdatedEvent(int counter, Tick countdownTicks)
: counter(counter), countdownTicks(countdownTicks) {}
const int counter;
const Tick countdownTicks;
};
#endif // BOSS_WAVE_UPDATED_EVENT_H

View File

@@ -3,12 +3,10 @@
#include "Event.h"
// Fired when the building block stock changes. Carries no payload — subscribers
// re-read Simulation::getBuildingBlocksStock().
class BuildingBlocksChangedEvent : public Event
{
public:
explicit BuildingBlocksChangedEvent(int blocks) : blocks(blocks) {}
const int blocks;
};
#endif // BUILDING_BLOCKS_CHANGED_EVENT_H

View File

@@ -4,14 +4,11 @@
#include "Event.h"
// Fired when the current asteroid-expansion cost changes (REQ-EXP-COST): once at
// startup and again after each expansion is purchased. Carries the cost in
// building blocks so the header Expand button can update its caption/enabled
// state (REQ-UI-EXPAND-BUTTON).
// startup and again after each expansion is purchased. Carries no payload — the
// header Expand button re-reads Simulation::getCurrentExpansionCost() to update
// its caption and enabled state (REQ-UI-EXPAND-BUTTON).
class ExpansionCostChangedEvent : public Event
{
public:
explicit ExpansionCostChangedEvent(int cost) : cost(cost) {}
const int cost;
};
#endif // EXPANSION_COST_CHANGED_EVENT_H

View File

@@ -2,14 +2,11 @@
#define TICK_ADVANCED_EVENT_H
#include "Event.h"
#include "Tick.h"
// Fired when the simulation tick advances. Carries no payload — subscribers
// re-read Simulation::getCurrentTick().
class TickAdvancedEvent : public Event
{
public:
explicit TickAdvancedEvent(Tick tick) : tick(tick) {}
const Tick tick;
};
#endif // TICK_ADVANCED_EVENT_H

View File

@@ -27,7 +27,6 @@ BlueprintPanel::BlueprintPanel(Simulation* sim, const GameConfig* config, QWidge
: QWidget(parent)
, m_sim(sim)
, m_config(config)
, m_currentBlocks(0)
{
QVBoxLayout* layout = new QVBoxLayout(this);
layout->setContentsMargins(4, 4, 4, 4);
@@ -71,9 +70,8 @@ void BlueprintPanel::onSelectionChanged(const std::vector<BuildingId>& ids)
refreshButtonStates();
}
void BlueprintPanel::handleEvent(std::shared_ptr<const BuildingBlocksChangedEvent> event)
void BlueprintPanel::handleEvent(std::shared_ptr<const BuildingBlocksChangedEvent> /*event*/)
{
m_currentBlocks = event->blocks;
refreshButtonStates();
}
@@ -247,10 +245,12 @@ void BlueprintPanel::refreshButtonStates()
// A construction site counts the same as an operational building (REQ-UI-BLUEPRINT-CREATE).
m_createBtn->setEnabled(selectionHasPlaceableBuilding(*m_sim, m_selectedBuildingIds));
const int blocks = m_sim->getBuildingBlocksStock();
for (int i = 0; i < static_cast<int>(m_blueprintButtons.size()); ++i)
{
const int cost = computeBlueprintCost(m_blueprints[static_cast<std::size_t>(i)]);
const bool canAfford = m_currentBlocks >= cost;
const bool canAfford = blocks >= cost;
m_blueprintButtons[static_cast<std::size_t>(i)]->setEnabled(
canAfford || m_activeIndex == i);
}

View File

@@ -53,10 +53,11 @@ private:
void loadFromDisk();
void saveToDisk() const;
// The simulation is the single source of truth for the block stock; the
// change event is only a refresh signal.
Simulation* m_sim;
const GameConfig* m_config;
std::vector<BuildingId> m_selectedBuildingIds;
int m_currentBlocks;
std::optional<int> m_activeIndex; // nullopt = no blueprint selected
std::vector<Blueprint> m_blueprints;
std::vector<QPushButton*> m_blueprintButtons;

View File

@@ -403,26 +403,26 @@ void GameWorldView::onFrame()
{
m_lastTick = newTick;
EventManager::getInstance()->sendEventImmediately(
std::make_shared<TickAdvancedEvent>(newTick));
std::make_shared<TickAdvancedEvent>());
}
if (newBlocks != m_lastBlocks)
{
m_lastBlocks = newBlocks;
EventManager::getInstance()->sendEventImmediately(
std::make_shared<BuildingBlocksChangedEvent>(newBlocks));
std::make_shared<BuildingBlocksChangedEvent>());
}
if (newExpCost != m_lastExpansionCost)
{
m_lastExpansionCost = newExpCost;
EventManager::getInstance()->sendEventImmediately(
std::make_shared<ExpansionCostChangedEvent>(newExpCost));
std::make_shared<ExpansionCostChangedEvent>());
}
if (newBoss != m_lastBossCounter || newCountdown != m_lastBossCountdown)
{
m_lastBossCounter = newBoss;
m_lastBossCountdown = newCountdown;
EventManager::getInstance()->sendEventImmediately(
std::make_shared<BossWaveUpdatedEvent>(newBoss, newCountdown));
std::make_shared<BossWaveUpdatedEvent>());
}
// Unlocked building set changes only after a drop is applied or on Restart
@@ -485,8 +485,7 @@ void GameWorldView::onFrame()
{
m_lastArtifactCount = currentArtifactCount;
EventManager::getInstance()->sendEventImmediately(
std::make_shared<ArtifactCountChangedEvent>(
currentArtifactCount, m_sim->getConfig().world.artifacts.artifactWinCount));
std::make_shared<ArtifactCountChangedEvent>());
}
update();

View File

@@ -107,9 +107,9 @@ HeaderBar::~HeaderBar()
unregisterForEvents();
}
void HeaderBar::handleEvent(std::shared_ptr<const TickAdvancedEvent> event)
void HeaderBar::handleEvent(std::shared_ptr<const TickAdvancedEvent> /*event*/)
{
const int totalSeconds = static_cast<int>(ticksToSeconds(event->tick));
const int totalSeconds = static_cast<int>(ticksToSeconds(m_sim->getCurrentTick()));
m_timeLabel->setText(
QString("%1:%2")
.arg(totalSeconds / 60, 2, 10, QChar('0'))
@@ -195,22 +195,26 @@ void HeaderBar::handleEvent(std::shared_ptr<const GameSpeedChangedEvent> event)
}
}
void HeaderBar::handleEvent(std::shared_ptr<const BossWaveUpdatedEvent> event)
void HeaderBar::handleEvent(std::shared_ptr<const BossWaveUpdatedEvent> /*event*/)
{
const int bossSeconds = static_cast<int>(
ticksToSeconds(event->countdownTicks > 0 ? event->countdownTicks : 0));
const Tick countdownTicks = m_sim->getBossCountdownTicks();
const int bossSeconds = static_cast<int>(
ticksToSeconds(countdownTicks > 0 ? countdownTicks : 0));
m_bossWaveLabel->setText(
tr("Boss Wave #%1")
.arg(event->counter));
.arg(m_sim->getBossWaveCounter()));
m_nextBossLabel->setText(
tr("Next boss: %1:%2")
.arg(bossSeconds / 60)
.arg(bossSeconds % 60, 2, 10, QChar('0')));
}
void HeaderBar::handleEvent(std::shared_ptr<const ArtifactCountChangedEvent> event)
void HeaderBar::handleEvent(std::shared_ptr<const ArtifactCountChangedEvent> /*event*/)
{
m_artifactsLabel->setText(tr("Artifacts: %1/%2").arg(event->count).arg(event->winCount));
m_artifactsLabel->setText(
tr("Artifacts: %1/%2")
.arg(m_sim->getArtifactCount())
.arg(m_sim->getConfig().world.artifacts.artifactWinCount));
}
void HeaderBar::onSpeedButton(int index)

View File

@@ -75,8 +75,8 @@ private:
ItemIconCache* m_itemIcons; // Not owned; lives in MainWindow.
// The simulation is the single source of truth for the block stock and the
// expansion cost; the change events are only refresh signals.
// The simulation is the single source of truth for everything the header
// displays; the change events are only refresh signals.
const Simulation* m_sim;
static const double kSpeeds[];