Replay: deterministic record & playback (#4)
Add deterministic record/playback for a run. Recording captures `(seed, config hash, ordered tick-tagged commands)` and re-simulates on playback — no state snapshots. `DotaFactory.exe --replay <file>` re-plays a recorded run view-only with manual speed/pause. Reviewed-on: #4 Co-authored-by: Malte Langkabel <malte.langkabel@gmail.com> Co-committed-by: Malte Langkabel <malte.langkabel@gmail.com>
This commit was merged in pull request #4.
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "StateChecksum.h"
|
||||
#include "Tick.h"
|
||||
#include "tracing.h"
|
||||
|
||||
@@ -970,4 +971,91 @@ void BeltSystem::forEachVisualItem(QRect viewportTiles,
|
||||
}
|
||||
}
|
||||
|
||||
void BeltSystem::appendItemSlots(Hasher& hasher, const std::vector<BeltItemSlot>& slotRun)
|
||||
{
|
||||
hasher.append(slotRun.size());
|
||||
for (const BeltItemSlot& slot : slotRun)
|
||||
{
|
||||
hasher.append(slot.item.type.id);
|
||||
hasher.append(slot.progress);
|
||||
}
|
||||
}
|
||||
|
||||
void BeltSystem::appendChecksum(Hasher& hasher) const
|
||||
{
|
||||
// std::map iterates in sorted key order, so all tile loops are deterministic.
|
||||
hasher.append(m_belts.size());
|
||||
for (const std::pair<const std::pair<int, int>, BeltTile>& entry : m_belts)
|
||||
{
|
||||
hasher.append(entry.first.first);
|
||||
hasher.append(entry.first.second);
|
||||
hasher.append(entry.second.direction);
|
||||
appendItemSlots(hasher, entry.second.itemSlots);
|
||||
}
|
||||
|
||||
hasher.append(m_splitters.size());
|
||||
for (const std::pair<const std::pair<int, int>, SplitterTile>& entry : m_splitters)
|
||||
{
|
||||
hasher.append(entry.first.first);
|
||||
hasher.append(entry.first.second);
|
||||
const SplitterTile& s = entry.second;
|
||||
hasher.append(s.outputA);
|
||||
hasher.append(s.outputB);
|
||||
hasher.append(s.filterA.size());
|
||||
for (const ItemType& type : s.filterA) { hasher.append(type.id); }
|
||||
hasher.append(s.filterB.size());
|
||||
for (const ItemType& type : s.filterB) { hasher.append(type.id); }
|
||||
hasher.append(s.nextOutputIsA);
|
||||
appendItemSlots(hasher, s.back);
|
||||
hasher.append(s.backDir.size());
|
||||
for (Rotation dir : s.backDir) { hasher.append(dir); }
|
||||
hasher.append(s.frontA.has_value());
|
||||
if (s.frontA.has_value())
|
||||
{
|
||||
hasher.append(s.frontA->item.type.id);
|
||||
hasher.append(s.frontA->progress);
|
||||
}
|
||||
hasher.append(s.frontB.has_value());
|
||||
if (s.frontB.has_value())
|
||||
{
|
||||
hasher.append(s.frontB->item.type.id);
|
||||
hasher.append(s.frontB->progress);
|
||||
}
|
||||
}
|
||||
|
||||
hasher.append(m_tunnelEntries.size());
|
||||
for (const std::pair<const std::pair<int, int>, TunnelEntryTile>& entry : m_tunnelEntries)
|
||||
{
|
||||
hasher.append(entry.first.first);
|
||||
hasher.append(entry.first.second);
|
||||
hasher.append(entry.second.direction);
|
||||
hasher.append(entry.second.maxDistance);
|
||||
appendItemSlots(hasher, entry.second.itemSlots);
|
||||
}
|
||||
|
||||
hasher.append(m_tunnelExits.size());
|
||||
for (const std::pair<const std::pair<int, int>, TunnelExitTile>& entry : m_tunnelExits)
|
||||
{
|
||||
hasher.append(entry.first.first);
|
||||
hasher.append(entry.first.second);
|
||||
hasher.append(entry.second.direction);
|
||||
appendItemSlots(hasher, entry.second.itemSlots);
|
||||
}
|
||||
|
||||
// m_tunnelLinks preserves insertion order, which is itself deterministic.
|
||||
hasher.append(m_tunnelLinks.size());
|
||||
for (const TunnelLink& link : m_tunnelLinks)
|
||||
{
|
||||
hasher.append(link.entryTile);
|
||||
hasher.append(link.exitTile);
|
||||
hasher.append(link.length);
|
||||
hasher.append(link.items.size());
|
||||
for (const TunnelTransitItem& item : link.items)
|
||||
{
|
||||
hasher.append(item.item.type.id);
|
||||
hasher.append(item.progress);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user