Bicameral Engine
GPU-resident engine for a magic-research open-world game
Loading...
Searching...
No Matches
lab_box.cpp
Go to the documentation of this file.
1// lab_box.cpp — 実験室の箱の CPU 側(lab_box.h)。コマンドを当てる関数は common/lab_box.hlsli(GPU と共通)。
2#include "sim/lab_box.h"
3
4#include <algorithm>
5#include <array>
6#include <cstring>
7#include <format>
8#include <type_traits>
9
11
12using namespace bicameral::multires;
13using namespace bicameral::reaction;
14
15namespace bicameral::sim {
16
17 namespace {
18
19 // --- 材料の量(1 セル = 0.5 m 角 = 0.125 m³。仮の世界の初めの世界と同じ値。sim/probe_sim.cpp)---
20 constexpr uint64_t AIR_OXYGEN_MICROMOLES = 1063800;
21 constexpr uint64_t AIR_NITROGEN_MICROMOLES = 4014200;
23 // 木: 体積の 1 割が木(密度 500 kg/m³ のセルロース)、残りは孔の中の空気
24 constexpr uint64_t WOOD_CELLULOSE_MICROMOLES = 38600000;
25 constexpr uint64_t PORE_OXYGEN_MICROMOLES = 983000;
26 constexpr uint64_t PORE_NITROGEN_MICROMOLES = 3697000;
27 // 木炭: 体積の 1 割が炭(密度 300 kg/m³ の炭素 = 3.75 kg ≒ 312 mol)、残りは孔の中の空気
28 constexpr uint64_t CHARCOAL_CARBON_MICROMOLES = 312000000;
29
30 constexpr uint32_t COMMAND_WORDS_PER_ENTRY = 3;
31
32 // --- 記録のファイルの形(リトルエンディアン。版を上げたら読む側も直す)---
33 constexpr std::array<char, 4> RECORDING_MAGIC = {'B', 'L', 'A', 'B'};
34 constexpr uint32_t RECORDING_VERSION = 1;
35
36 void SetPayload(Command& command, uint32_t word, uint32_t value) {
37 command.payload[word] = value;
38 }
39
40 Command MakeLabCommand(uint64_t targetTick, uint32_t sequence, uint32_t type, LabCellPosition cell,
41 uint32_t temperatureMilliKelvin) {
42 Command command;
43 command.targetTick = targetTick;
44 command.sequence = sequence;
45 command.type = static_cast<uint16_t>(type);
46 SetPayload(command, lab::LAB_PAYLOAD_CELL,
47 cell.x < LAB_BOX_EDGE && cell.y < LAB_BOX_EDGE && cell.z < LAB_BOX_EDGE ? cell.Index()
48 : lab::LAB_NO_CELL);
49 SetPayload(command, lab::LAB_PAYLOAD_TEMPERATURE, temperatureMilliKelvin);
50 command.size = static_cast<uint16_t>(sizeof(uint32_t) * 2);
51
52 return command;
53 }
54
55 template <typename T>
56 bool SameBytes(const std::vector<T>& a, const std::vector<T>& b) {
57 static_assert(std::is_trivially_copyable_v<T>);
58 return a.size() == b.size() && std::memcmp(a.data(), b.data(), a.size() * sizeof(T)) == 0;
59 }
60
61 // --- 記録の読み書きの小さな道具 ---
62 template <typename T>
63 void Append(std::vector<std::byte>& bytes, const T& value) {
64 static_assert(std::is_trivially_copyable_v<T>);
65 const auto* begin = reinterpret_cast<const std::byte*>(&value);
66 bytes.insert(bytes.end(), begin, begin + sizeof(T));
67 }
68
69 template <typename T>
70 bool Take(std::span<const std::byte>& bytes, T& value) {
71 static_assert(std::is_trivially_copyable_v<T>);
72 if (bytes.size() < sizeof(T))
73 return false;
74
75 std::memcpy(&value, bytes.data(), sizeof(T));
76 bytes = bytes.subspan(sizeof(T));
77
78 return true;
79 }
80
81 } // namespace
82
83 // --- コマンド ---
84
85 Command MakeLabFillCommand(uint64_t targetTick, uint32_t sequence, LabCellPosition cell,
86 std::span<const SpeciesAmount> contents, uint32_t temperatureMilliKelvin) {
87 Command command = MakeLabCommand(targetTick, sequence, lab::LAB_COMMAND_TYPE_FILL, cell,
88 temperatureMilliKelvin);
89
90 // 多すぎる材料は数だけ書く(LabCommandCell が当てないコマンドにする)
91 const auto count = static_cast<uint32_t>(contents.size());
92 SetPayload(command, lab::LAB_PAYLOAD_COUNT, count);
93 const uint32_t written = std::min(count, lab::LAB_MAX_FILL_SPECIES);
94 for (uint32_t i = 0; i < written; ++i) {
95 const uint32_t word = lab::LAB_PAYLOAD_ENTRIES + (COMMAND_WORDS_PER_ENTRY * i);
96 SetPayload(command, word, contents[i].species);
97 SetPayload(command, word + 1, static_cast<uint32_t>(contents[i].amount));
98 SetPayload(command, word + 2, static_cast<uint32_t>(contents[i].amount >> 32));
99 }
100
101 command.size = static_cast<uint16_t>(sizeof(uint32_t) *
102 (lab::LAB_PAYLOAD_ENTRIES + (COMMAND_WORDS_PER_ENTRY * written)));
103
104 return command;
105 }
106
107 Command MakeLabTemperatureCommand(uint64_t targetTick, uint32_t sequence, LabCellPosition cell,
108 uint32_t temperatureMilliKelvin) {
109 return MakeLabCommand(targetTick, sequence, lab::LAB_COMMAND_TYPE_TEMPERATURE, cell, temperatureMilliKelvin);
110 }
111
112 lab::LabCommand ToLabCommand(const Command& command) {
113 static_assert(sizeof(lab::LabCommand) == COMMAND_BYTES);
114 lab::LabCommand words{};
115 std::memcpy(&words, &command, sizeof(words));
116
117 return words;
118 }
119
120 // --- 材料 ---
121
122 std::vector<LabMaterial> MakeLabMaterials(const BakedReactionTable& table) {
123 const uint32_t oxygen = table.SpeciesId("oxygen");
124 const uint32_t nitrogen = table.SpeciesId("nitrogen");
125 const uint32_t cellulose = table.SpeciesId("cellulose");
126 const uint32_t carbon = table.SpeciesId("carbon");
127 const uint32_t carbonDioxide = table.SpeciesId("carbon_dioxide");
128
129 std::vector<LabMaterial> materials;
130 const auto add = [&](std::string_view name, std::vector<SpeciesAmount> contents) {
131 const bool known = std::ranges::all_of(contents,
132 [](const SpeciesAmount& entry) { return entry.species != 0; });
133 if (known)
134 materials.push_back({.name = name, .contents = std::move(contents)});
135 };
136
137 add("空気", {{oxygen, AIR_OXYGEN_MICROMOLES}, {nitrogen, AIR_NITROGEN_MICROMOLES}});
138 add("木", {{cellulose, WOOD_CELLULOSE_MICROMOLES},
139 {oxygen, PORE_OXYGEN_MICROMOLES},
140 {nitrogen, PORE_NITROGEN_MICROMOLES}});
141 add("木炭", {{carbon, CHARCOAL_CARBON_MICROMOLES},
142 {oxygen, PORE_OXYGEN_MICROMOLES},
143 {nitrogen, PORE_NITROGEN_MICROMOLES}});
144 add("二酸化炭素", {{carbonDioxide, AIR_TOTAL_MICROMOLES}});
145 add("窒素", {{nitrogen, AIR_TOTAL_MICROMOLES}});
146
147 return materials;
148 }
149
150 // --- 箱 ---
151
153 return {.worldBlocks = 1,
154 .observerBlocks = 0,
155 .fractions = 1,
156 .pages = 1,
157 .indexEntries = 2,
158 .ledgerColumns = 1 + static_cast<uint32_t>(table.species.size()),
159 .rootLevel = 0};
160 }
161
163 const std::array<SpeciesAmount, 2> air = {
164 SpeciesAmount{.species = table.SpeciesId("oxygen"), .amount = AIR_OXYGEN_MICROMOLES},
165 SpeciesAmount{.species = table.SpeciesId("nitrogen"), .amount = AIR_NITROGEN_MICROMOLES}};
166 const RxCell fill = MakeReactionCell(table, air, LAB_INITIAL_TEMPERATURE_MILLIKELVIN);
167
168 // --- 頁を持たせるため、1 つだけ違うセルで根を置き(PlaceRootBlock は全部同じなら一様にする)、すぐ戻す ---
170 std::vector<RxCell> cells(MR_BLOCK_CELLS, fill);
171 cells[0] = RxMakeEmptyCell(0);
172 const uint32_t slot = PlaceRootBlock(nest, 0, 0, 0, cells);
173 FX_ASSERT(slot == LAB_BOX_SLOT && !MrIsUniform(nest.blocks[slot]));
174 nest_detail::CellAt(nest, slot, 0) = fill;
175
176 return nest;
177 }
178
179 RxCell LabBoxCell(const MultiresNest& nest, uint32_t index) {
180 return LoadNestCell(nest, LAB_BOX_SLOT, index);
181 }
182
183 uint32_t ApplyLabCommands(MultiresNest& nest, const BakedReactionTable& table, std::span<const Command> commands) {
184 if (MrIsUniform(nest.blocks[LAB_BOX_SLOT]))
185 return 0; // shaders/sim/lab_box.hlsl と同じ(箱はいつも頁を持つ)
186
187 const ReactionTableView view = table.View();
188 const auto speciesCount = static_cast<uint32_t>(table.species.size());
189 uint32_t applied = 0;
190 for (const Command& command : commands) {
191 const lab::LabCommand words = ToLabCommand(command);
192 const uint32_t cell = lab::LabCommandCell(words, speciesCount);
193 if (cell == lab::LAB_NO_CELL)
194 continue;
195
196 RxCell& target = nest_detail::CellAt(nest, LAB_BOX_SLOT, cell);
197 target = lab::LabApplyCommand(view, target, words);
198 applied += 1;
199 }
200
201 // --- 変えたブロックは「つつかれた」(lab_box.hlsl と同じ。種の印は状態の要約に入らない)---
202 if (applied > 0)
204
205 return applied;
206 }
207
208 void StepLabBox(MultiresNest& nest, const BakedReactionTable& table, uint64_t tick,
209 std::span<const Command> commands) {
210 ApplyLabCommands(nest, table, commands);
211 StepNest(nest, table, LAB_WORLD_SEED, tick, LAB_STEP_OPTIONS);
212 }
213
214 // --- 比べる ---
215
216 std::optional<LabMismatch> FindLabMismatch(const MultiresNest& cpu, const MultiresNest& gpu, uint64_t tick) {
217 if (HashWholeNest(cpu) == HashWholeNest(gpu))
218 return std::nullopt;
219
220 LabMismatch mismatch{.tick = tick};
221 for (uint32_t index = 0; index < MR_BLOCK_CELLS; ++index) {
222 const RxCell a = LabBoxCell(cpu, index);
223 const RxCell b = LabBoxCell(gpu, index);
224 if (RxSameCell(a, b))
225 continue;
226
227 mismatch.cell = index;
228 mismatch.cpu = a;
229 mismatch.gpu = b;
230 mismatch.what = std::format("刻み {} のセル ({}, {}, {}) が違う", tick, MrCellX(index), MrCellY(index),
231 MrCellZ(index));
232 return mismatch;
233 }
234
235 // --- セルは同じ: 違う欄の名前 ---
236 std::string_view field = "その他";
237 if (!SameBytes(cpu.blocks, gpu.blocks))
238 field = "ブロックの見出し";
239 else if (!SameBytes(cpu.fractions, gpu.fractions))
240 field = "端数";
241 else if (!SameBytes(cpu.ledger, gpu.ledger))
242 field = "世界の帳簿";
243 else if (cpu.counters != gpu.counters)
244 field = "数える欄";
245 else if (!SameBytes(cpu.freeBlocks, gpu.freeBlocks) || !SameBytes(cpu.freeFractions, gpu.freeFractions))
246 field = "空きのスタック";
247
248 mismatch.what = std::format("刻み {} の{}が違う(セルは全部同じ)", tick, field);
249 return mismatch;
250 }
251
252 // --- 記録 ---
253
254 std::vector<std::byte> SerializeLabRecording(const LabRecording& recording) {
255 std::vector<std::byte> bytes;
256 Append(bytes, RECORDING_MAGIC);
257 Append(bytes, RECORDING_VERSION);
258 Append(bytes, LAB_WORLD_SEED);
259 Append(bytes, recording.tickCount);
260 Append(bytes, static_cast<uint64_t>(recording.commands.size()));
261 Append(bytes, static_cast<uint64_t>(recording.hashes.size()));
262 for (const Command& command : recording.commands)
263 Append(bytes, command);
264
265 for (const uint64_t hash : recording.hashes)
266 Append(bytes, hash);
267
268 return bytes;
269 }
270
271 std::expected<LabRecording, std::string> ParseLabRecording(std::span<const std::byte> bytes) {
272 std::array<char, 4> magic{};
273 uint32_t version = 0;
274 uint64_t seed = 0;
275 uint64_t commandCount = 0;
276 uint64_t hashCount = 0;
277 LabRecording recording;
278 if (!Take(bytes, magic) || magic != RECORDING_MAGIC)
279 return std::unexpected("実験の記録ではない");
280
281 if (!Take(bytes, version) || version != RECORDING_VERSION)
282 return std::unexpected(std::format("記録の版 {} は読めない", version));
283
284 if (!Take(bytes, seed) || seed != LAB_WORLD_SEED || !Take(bytes, recording.tickCount) ||
285 !Take(bytes, commandCount) || !Take(bytes, hashCount))
286 return std::unexpected("記録の見出しが壊れている");
287
288 if (bytes.size() != (commandCount * COMMAND_BYTES) + (hashCount * sizeof(uint64_t)) ||
289 hashCount != recording.tickCount)
290 return std::unexpected("記録の大きさが合わない");
291
292 recording.commands.resize(commandCount);
293 for (Command& command : recording.commands)
294 (void)Take(bytes, command);
295
296 recording.hashes.resize(hashCount);
297 for (uint64_t& hash : recording.hashes)
298 (void)Take(bytes, hash);
299
300 const bool sorted = std::ranges::is_sorted(recording.commands, CommandPrecedes);
301 if (!sorted || (!recording.commands.empty() && recording.commands.back().targetTick >= recording.tickCount))
302 return std::unexpected("記録のコマンドの並びが壊れている");
303
304 return recording;
305 }
306
307} // namespace bicameral::sim
bool RxSameCell(const RxWideCell &a, const RxWideCell &b)
Command MakeLabCommand(uint64_t targetTick, uint32_t sequence, uint32_t type, LabCellPosition cell, uint32_t temperatureMilliKelvin)
Definition lab_box.cpp:40
bool SameBytes(const std::vector< T > &a, const std::vector< T > &b)
Definition lab_box.cpp:56
bool Take(std::span< const std::byte > &bytes, T &value)
Definition lab_box.cpp:70
constexpr std::array< char, 4 > RECORDING_MAGIC
Definition lab_box.cpp:33
void Append(std::vector< std::byte > &bytes, const T &value)
Definition lab_box.cpp:63
void SetPayload(Command &command, uint32_t word, uint32_t value)
Definition lab_box.cpp:36
void PokeBlock(MultiresNest &nest, uint32_t slot)
reaction::RxCell & CellAt(MultiresNest &nest, uint32_t slot, uint32_t index)
std::expected< LabRecording, std::string > ParseLabRecording(std::span< const std::byte > bytes)
Definition lab_box.cpp:271
constexpr bool CommandPrecedes(const Command &a, const Command &b)
Definition command.h:32
constexpr MultiresStepOptions LAB_STEP_OPTIONS
Definition lab_box.h:36
RxCell LabBoxCell(const MultiresNest &nest, uint32_t index)
Definition lab_box.cpp:179
constexpr uint32_t LAB_BOX_SLOT
Definition lab_box.h:29
Command MakeLabFillCommand(uint64_t targetTick, uint32_t sequence, LabCellPosition cell, std::span< const SpeciesAmount > contents, uint32_t temperatureMilliKelvin)
Definition lab_box.cpp:85
MultiresNest MakeMultiresNest(const MultiresCapacity &capacity)
uint64_t HashWholeNest(const MultiresNest &nest)
std::vector< LabMaterial > MakeLabMaterials(const BakedReactionTable &table)
Definition lab_box.cpp:122
void StepLabBox(MultiresNest &nest, const BakedReactionTable &table, uint64_t tick, std::span< const Command > commands)
Definition lab_box.cpp:208
RxCell MakeReactionCell(const BakedReactionTable &table, std::span< const SpeciesAmount > amounts, int32_t temperatureMilliKelvin)
constexpr uint32_t COMMAND_BYTES
Definition command.h:16
constexpr uint32_t LAB_BOX_EDGE
Definition lab_box.h:30
RxCell LoadNestCell(const MultiresNest &nest, uint32_t slot, uint32_t index)
Command MakeLabTemperatureCommand(uint64_t targetTick, uint32_t sequence, LabCellPosition cell, uint32_t temperatureMilliKelvin)
Definition lab_box.cpp:107
uint32_t ApplyLabCommands(MultiresNest &nest, const BakedReactionTable &table, std::span< const Command > commands)
Definition lab_box.cpp:183
MultiresNest MakeLabBoxNest(const BakedReactionTable &table)
Definition lab_box.cpp:162
constexpr uint64_t LAB_WORLD_SEED
Definition lab_box.h:32
uint32_t PlaceRootBlock(MultiresNest &nest, int64_t originX, int64_t originY, int64_t originZ, std::span< const reaction::RxCell > cells)
std::vector< std::byte > SerializeLabRecording(const LabRecording &recording)
Definition lab_box.cpp:254
std::optional< LabMismatch > FindLabMismatch(const MultiresNest &cpu, const MultiresNest &gpu, uint64_t tick)
Definition lab_box.cpp:216
constexpr int32_t LAB_INITIAL_TEMPERATURE_MILLIKELVIN
Definition lab_box.h:33
void StepNest(MultiresNest &nest, const BakedReactionTable &table, uint64_t worldSeed, uint64_t tick, const MultiresStepOptions &options)
lab::LabCommand ToLabCommand(const Command &command)
Definition lab_box.cpp:112
MultiresCapacity LabBoxCapacity(const BakedReactionTable &table)
Definition lab_box.cpp:152
uint32_t SpeciesId(std::string_view name) const
std::vector< reaction::RxSpecies > species
ReactionTableView View() const
std::array< uint32_t, COMMAND_PAYLOAD_WORDS > payload
Definition command.h:24
reaction::RxCell gpu
Definition lab_box.h:94
reaction::RxCell cpu
Definition lab_box.h:93
std::vector< uint64_t > hashes
Definition lab_box.h:107
std::vector< Command > commands
Definition lab_box.h:106
std::vector< multires::MrBlock > blocks
std::vector< multires::MrFraction > fractions
std::vector< uint32_t > freeFractions
std::vector< uint64_t > ledger
std::vector< uint32_t > freeBlocks
std::array< uint32_t, multires::MR_COUNTER_COUNT > counters