Bicameral Engine
GPU-resident engine for a magic-research open-world game
Loading...
Searching...
No Matches
multires_nest.cpp
Go to the documentation of this file.
1// multires_nest.cpp — 多重解像度の木の CPU リファレンス(multires_nest.h)のうち、観察の枠(影・写し)・刻み・要約・保存量の合計。
2// 世界の木の管理(索引・空きのスタック・要求・帳簿)は multires_tree.cpp。
3// 1 段ぶんの操作(RefineShadowLevel・PullBackLevel)は、GPU のノード(shaders/sim/multires_graph.hlsl)の 1 グループと同じ順。
4#include "sim/multires_nest.h"
5
6#include <algorithm>
7#include <span>
8
10
11using namespace bicameral::multires;
12using namespace bicameral::reaction;
13
14namespace bicameral::sim {
15
16 namespace {
17
22
23 // --- 1 段ぶんの操作 ---
24
25 bool IsObserverSlot(const MultiresNest& nest, uint32_t slot) {
26 return slot >= nest.capacity.worldBlocks && slot < nest.blocks.size();
27 }
28
29 void RefineShadowLevel(MultiresNest& nest, uint32_t parentSlot, uint32_t childSlot,
30 const MultiresPoint& point) {
31 FX_ASSERT(IsObserverSlot(nest, childSlot));
32 const MrBlock parent = nest.blocks[parentSlot];
33 const uint32_t octant = MrOctantOfPoint(parent, point.x, point.y, point.z, point.level);
34
35 // --- セル: 子は親と同じ数(影は端数を持たず、親を覆わない。影はいつも頁を持つ)---
36 const uint32_t page = MrObserverPage(childSlot, nest.capacity.worldBlocks);
37 for (uint32_t index = 0; index < MR_BLOCK_CELLS; ++index)
38 PageCellAt(nest, page, index) = LoadNestCell(nest, parentSlot, MrParentCellOfChild(octant, index));
39
40 nest.blocks[childSlot] = MrMakeChildBlock(parent, parentSlot, octant, MR_BLOCK_SHADOW, MR_NO_FRACTION,
41 page);
42 }
43
44 void PullBackLevel(MultiresNest& nest, const ReactionTableView& table, uint32_t shadowSlot) {
45 const MrBlock shadow = nest.blocks[shadowSlot];
46 FX_ASSERT(shadow.kind == MR_BLOCK_SHADOW);
47 for (uint32_t local = 0; local < MR_OCTANT_CELLS; ++local) {
48 const RxCell parentCell = LoadNestCell(nest, shadow.parent, MrOctantCell(shadow.parentOctant, local));
49 MrShadowFamily family{};
50 for (uint32_t j = 0; j < MR_CHILDREN_PER_CELL; ++j)
51 family.cells[j] = CellAt(nest, shadowSlot, MrChildCell(local, j));
52
53 const MrShadowFamily pulled = MrPullBackShadow(table, parentCell, family);
54 nest.counters[MR_COUNTER_SHADOW_CLAMPED] += pulled.clamped;
55 for (uint32_t j = 0; j < MR_CHILDREN_PER_CELL; ++j)
56 CellAt(nest, shadowSlot, MrChildCell(local, j)) = pulled.cells[j];
57 }
58
59 // --- セルの値が変わりうるので、つついた印にして次の刻みに tc を書き直す(ADR-0018 追記 T-0125)。
60 // 観察の枠は種の一覧に入れない(活性でも全部刻む。D-403)---
61 nest.blocks[shadowSlot].busyTick = MR_BUSY_POKED;
62 }
63
64 // --- 256bit の足し算(保存量の合計)---
65
66 // 128bit の値(上, 下)を符号拡張して 256bit に
67 Wide256 MakeWide256(uint64_t high, uint64_t low, bool negative) {
68 const uint64_t extension = negative ? ~uint64_t{0} : 0;
69
70 return {low, high, extension, extension};
71 }
72
73 // 左へずらす(2 の補数のまま。はみ出す上位は捨てる)
74 Wide256 ShiftLeft(const Wide256& value, uint32_t bits) {
75 FX_ASSERT(bits < 256);
76 const uint32_t words = bits / 64;
77 const uint32_t rest = bits % 64;
78 Wide256 result{};
79 for (size_t i = value.size(); i-- > words;) {
80 const size_t source = i - words;
81 result[i] = value[source] << rest;
82 if (rest != 0 && source > 0)
83 result[i] |= value[source - 1] >> (64 - rest);
84 }
85
86 return result;
87 }
88
89 // 符号なしの値に小さな数を掛ける(元素の係数)
90 Wide256 MultiplySmall(Wide256 value, uint32_t factor) {
91 Wide256 result{};
92 uint64_t carry = 0;
93 for (size_t i = 0; i < value.size(); ++i) {
94 const FxU128 product = FxMulU64Full(value[i], factor);
95 result[i] = product.lo + carry;
96 carry = product.hi + (result[i] < carry ? 1u : 0u);
97 }
98
99 return result;
100 }
101
102 void AddTo(Wide256& sum, const Wide256& value) {
103 uint64_t carry = 0;
104 for (size_t i = 0; i < sum.size(); ++i) {
105 const uint64_t partial = sum[i] + value[i];
106 const uint64_t carryPartial = partial < sum[i] ? 1u : 0u;
107 sum[i] = partial + carry;
108 carry = carryPartial + (sum[i] < carry ? 1u : 0u);
109 }
110 }
111
112 void AddCellTotals(ConservedTotals& totals, const BakedReactionTable& table, const RxCell& cell,
113 const MrFraction& fraction, uint32_t shiftBits) {
114 AddTo(totals.energy,
115 ShiftLeft(MakeWide256(static_cast<uint64_t>(cell.energy), fraction.energy, cell.energy < 0),
116 shiftBits));
117
118 const size_t elementCount = totals.elements.size();
119 for (uint32_t species = MrNextSpecies(cell, fraction, 0); species != MR_NO_SPECIES;
120 species = MrNextSpecies(cell, fraction, species)) {
121 const Wide256 amount = MakeWide256(MrAmountOf(cell, species), MrFractionOf(fraction, species), false);
122 for (size_t element = 0; element < elementCount; ++element) {
123 const uint32_t atoms = table.speciesElements[(species * elementCount) + element];
124 if (atoms != 0)
125 AddTo(totals.elements[element], ShiftLeft(MultiplySmall(amount, atoms), shiftBits));
126 }
127 }
128 }
129
130 // 帳簿の 1 つの値(レベル level の単位 × 2^-64)を finestLevel の単位に
131 Wide256 LedgerValue(uint64_t value, int32_t level, int32_t finestLevel) {
132 FX_ASSERT(level <= finestLevel);
133
134 return ShiftLeft(Wide256{value, 0, 0, 0}, static_cast<uint32_t>(finestLevel - level) * MR_LEVEL_SHIFT);
135 }
136
137 // 帳簿の 1 つの列(0 = エネルギー、1 + 物質 ID)の量を合計に足す
138 void AddLedgerColumn(ConservedTotals& totals, const BakedReactionTable& table, uint32_t column,
139 const Wide256& amount) {
140 if (column == 0) {
141 AddTo(totals.energy, amount);
142 return;
143 }
144
145 const uint32_t species = column - 1;
146 const size_t elementCount = totals.elements.size();
147 for (size_t element = 0; element < elementCount; ++element) {
148 const uint32_t atoms = table.speciesElements[(species * elementCount) + element];
149 if (atoms != 0)
150 AddTo(totals.elements[element], MultiplySmall(amount, atoms));
151 }
152 }
153
154 void AddLedgerTotals(ConservedTotals& totals, const MultiresNest& nest, const BakedReactionTable& table,
155 int32_t finestLevel) {
156 const uint32_t columns = nest.capacity.ledgerColumns;
157 for (uint32_t row = 0; row < MR_LEDGER_LEVELS; ++row) {
158 const int32_t level = MR_LEDGER_LEVEL_MIN + static_cast<int32_t>(row);
159 for (uint32_t column = 0; column < columns; ++column) {
160 const uint64_t value = nest.ledger[(size_t{row} * columns) + column];
161 if (value != 0)
162 AddLedgerColumn(totals, table, column, LedgerValue(value, level, finestLevel));
163 }
164 }
165 }
166
167 uint64_t HashFraction(const MrFraction& fraction) {
168 uint64_t hash = FxHashCombine(0, fraction.energy);
169 hash = FxHashCombine(hash, fraction.speciesCount);
170 for (uint32_t i = 0; i < fraction.speciesCount; ++i) {
171 hash = FxHashCombine(hash, fraction.species[i]);
172 hash = FxHashCombine(hash, fraction.amounts[i]);
173 }
174
175 return hash;
176 }
177
178 uint64_t HashBlock(const MrBlock& block) {
179 uint64_t hash = FxHashCombine(0, static_cast<uint64_t>(block.originX));
180 hash = FxHashCombine(hash, static_cast<uint64_t>(block.originY));
181 hash = FxHashCombine(hash, static_cast<uint64_t>(block.originZ));
182 hash = FxHashCombine(hash, static_cast<uint64_t>(static_cast<int64_t>(block.level)));
183 hash = FxHashCombine(hash, block.kind);
184 hash = FxHashCombine(hash, block.parent);
185 hash = FxHashCombine(hash, block.parentOctant);
186 hash = FxHashCombine(hash, block.fraction);
187 for (const uint32_t child : block.children)
188 hash = FxHashCombine(hash, child);
189
190 return hash;
191 }
192
193 // 刻むセルを 1 刻み(頁を持つブロック)
194 // 端数のエネルギー(枠が無ければ 0)
195 uint64_t FractionEnergy(const MultiresNest& nest, uint32_t fractionSlot, uint32_t index) {
196 return FractionAt(nest, fractionSlot, index).energy;
197 }
198
199 // 頁を持つブロックの刻むセル: 伝導の変化(deltas が空なら無し)を足し、react なら反応を待ちの丸めで進める
200 // (変わらなかったセルの次に評価の要る刻みの最小も求める。T-0115。上限に当たった印を数える器に足す。T-0163)
202 uint64_t worldSeed, uint64_t tick, bool react,
203 std::span<const MrEnergyDelta> deltas) {
204 const MrBlock block = nest.blocks[slot];
206 result.evaluated = react;
207 MrLimitTally tally = MrMakeLimitTally();
208 for (uint32_t index = 0; index < MR_BLOCK_CELLS; ++index) {
209 if (!MrIsSteppedCell(block, index))
210 continue;
211
212 RxCell& cell = CellAt(nest, slot, index);
213 const RxCell before = cell;
214 const uint64_t fractionBefore = FractionEnergy(nest, block.fraction, index);
215
216 // --- 伝導 ---
217 if (!deltas.empty()) {
218 const MrEnergyDelta& delta = deltas[nest_detail::PageCellAddress(nest, block.page, index)];
219 if (!MrEnergyDeltaIsZero(delta))
220 nest_detail::ApplyEnergyDelta(nest, slot, index, cell, delta);
221 }
222
223 // --- 反応 ---
224 if (react) {
225 const RxWaitStep step = MrStepCellWait(view, cell, worldSeed, tick, block, index);
226 result.wakeTick = std::min(result.wakeTick, step.wakeTick);
227 cell = step.cell;
228 tally = MrAddLimits(tally, step.limits);
229 }
230
231 result.changed = result.changed || MrCellChanged(before, cell) ||
232 FractionEnergy(nest, block.fraction, index) != fractionBefore;
233 }
234
235 // --- 上限に当たった印を数える(T-0163。GPU の StepPagedWait・ApplyCell と同じ数)---
236 nest.counters[MR_COUNTER_LIMIT_PRODUCTS] += tally.productsHeld;
237 nest.counters[MR_COUNTER_LIMIT_CANDIDATES] += tally.candidatesLimited;
238
239 return result;
240 }
241
242 } // namespace
243
244 namespace nest_detail {
245
246 void ApplyEnergyDelta(MultiresNest& nest, uint32_t slot, uint32_t index, RxCell& cell,
247 const MrEnergyDelta& delta) {
248 const uint32_t fractionSlot = nest.blocks[slot].fraction;
249 MrEnergyDelta value = MrMakeEnergyDelta();
250 value.whole = cell.energy;
251 value.fraction = FractionEnergy(nest, fractionSlot, index);
252 value = MrAddEnergyDelta(value, delta);
253
254 cell.energy = value.whole;
255 if (fractionSlot == MR_NO_FRACTION) {
256 FX_ASSERT(value.fraction == 0);
257 return;
258 }
259
260 MrFraction fraction = FractionAt(nest, fractionSlot, index);
261 fraction.energy = value.fraction;
262 SetFraction(nest, fractionSlot, index, fraction);
263 }
264
265 std::vector<uint8_t> ExpandForStep(MultiresNest& nest, std::span<BlockStepResult> results) {
266 const auto blockCount = static_cast<uint32_t>(nest.blocks.size());
267 std::vector<uint8_t> frozen(blockCount, 0);
268 for (uint32_t slot = 0; slot < blockCount; ++slot)
269 frozen[slot] = nest.blocks[slot].page == MR_PAGE_WANTED ? 1 : 0;
270
271 for (const uint32_t slot : ExpandWantedPages(nest)) {
272 frozen[slot] = 0;
273 results[slot].expanded = true;
274 }
275
276 return frozen;
277 }
278
279 std::vector<BlockStepResult> StepBlocks(MultiresNest& nest, const ReactionTableView& view,
280 std::span<uint8_t> stepped, uint64_t worldSeed, uint64_t tick,
281 const MultiresStepOptions& options, uint32_t wakeMark) {
282 const auto blockCount = static_cast<uint32_t>(nest.blocks.size());
283 std::vector<BlockStepResult> results(blockCount);
284
285 ResolvePokes(nest, tick);
286
287 // --- 一様なブロック: 反応が進む時だけ頁に広げる(T-0102)。伝導の流れがある時も(T-0019)---
288 for (uint32_t slot = 0; slot < blockCount; ++slot) {
289 MrBlock& block = nest.blocks[slot];
290 if (stepped[slot] == 0 || !MrIsUniform(block))
291 continue;
292
293 // --- 待ちの丸め(T-0115): 起こす刻みが来た時だけ評価する(その前はどのセルも変わらない)---
294 if (MrChangeMark(tick) < block.wakeTick)
295 continue;
296
297 const MrUniformWait wait = MrUniformWaitOf(view, UniformAt(nest, slot), worldSeed, tick, block);
298 if (wait.changed != 0) {
299 block.page = MR_PAGE_WANTED;
300 continue;
301 }
302
303 results[slot].evaluated = true;
304 results[slot].wakeTick = wait.wakeTick;
305 }
306
307 // --- 伝導(小刻みに分けるなら最後の 1 回の手前まで。T-0108)。枠の順に頁を配り、足りなかったブロックはその回は凍らせる
308 // (刻まず、面の流れも 0)---
309 std::vector<MrEnergyDelta> deltas;
310 if (options.conduction)
311 deltas = StepConduction(nest, view, stepped, options, wakeMark, results);
312 else
313 ExpandForStep(nest, results);
314
315 // --- 頁のブロック: 伝導の変化(最後の小刻みの分)を足してから反応(凍らせたブロックは一様のまま)---
316 for (uint32_t slot = 0; slot < blockCount; ++slot) {
317 if (MrIsUniform(nest.blocks[slot]))
318 continue;
319
320 const BlockStepResult earlier = results[slot];
321 results[slot] = StepPagedBlock(nest, view, slot, worldSeed, tick, stepped[slot] != 0, deltas);
322 results[slot].expanded = earlier.expanded;
323 results[slot].changed = results[slot].changed || earlier.changed;
324 }
325
326 return results;
327 }
328
329 void ResolvePokes(MultiresNest& nest, uint64_t tick) {
330 for (MrBlock& block : nest.blocks) {
331 if (block.busyTick != MR_BUSY_POKED)
332 continue;
333
334 // つつかれた刻みの印(静かさの判定は今までどおり)。評価は「刻みの直前に変わった」として(MrStepCellWait)
335 block.busyTick = MrChangeMark(tick);
336 block.wakeTick = 0;
337 }
338 }
339
340 void RecordWaitResults(MultiresNest& nest, std::span<const BlockStepResult> results, uint64_t tick) {
341 const uint64_t mark = MrChangeMark(tick);
342 for (uint32_t slot = 0; slot < nest.blocks.size(); ++slot) {
343 const BlockStepResult& result = results[slot];
344 MrBlock& block = nest.blocks[slot];
345 if (result.changed || result.expanded || block.busyTick == mark) {
346 // 変わった・つつかれた: 次の刻みは新しい tc で引き直すので、評価が要る(伝導の面の隣も起こす)
347 block.busyTick = mark;
348 block.wakeTick = mark + 1;
349 } else if (result.evaluated) {
350 block.wakeTick = result.wakeTick;
351 }
352 }
353 }
354
355 std::vector<uint32_t> ExpandWantedPages(MultiresNest& nest) {
356 std::vector<uint32_t> expanded;
357 for (uint32_t slot = 0; slot < nest.capacity.worldBlocks; ++slot) {
358 MrBlock& block = nest.blocks[slot];
359 if (block.page != MR_PAGE_WANTED)
360 continue;
361
362 // --- 頁が足りなければ、この刻みは刻まずに種に残す ---
363 if (nest.counters[MR_COUNTER_FREE_PAGES] == 0) {
364 block.page = MR_NO_PAGE;
365 nest.counters[MR_COUNTER_PAGE_SHORTAGE] += 1;
366 nest.seeds[slot] = 1;
367 continue;
368 }
369
370 block.page = PopPage(nest);
371 for (uint32_t index = 0; index < MR_BLOCK_CELLS; ++index)
372 PageCellAt(nest, block.page, index) = MrUniformCell(block, UniformAt(nest, slot), index);
373
374 nest.counters[MR_COUNTER_EXPANDED] += 1;
375 expanded.push_back(slot);
376 }
377
378 return expanded;
379 }
380
381 } // namespace nest_detail
382
383 RxCell LoadNestCell(const MultiresNest& nest, uint32_t slot, uint32_t index) {
384 const MrBlock& block = nest.blocks[slot];
385 if (MrIsUniform(block))
386 return MrUniformCell(block, UniformAt(nest, slot), index);
387
388 return nest.cells[nest.blocks.size() + (size_t{block.page} * MR_BLOCK_CELLS) + index];
389 }
390
391 uint32_t UsedWorldPages(const MultiresNest& nest) {
392 return nest.capacity.pages - nest.counters[MR_COUNTER_FREE_PAGES];
393 }
394
396 FX_ASSERT(capacity.indexEntries == 0 || (capacity.indexEntries & (capacity.indexEntries - 1)) == 0);
397 FX_ASSERT(capacity.ledgerColumns >= 1);
398 const size_t blockCount = size_t{capacity.worldBlocks} + capacity.observerBlocks;
399 const size_t pageCount = size_t{capacity.observerBlocks} + capacity.pages;
400
401 MultiresNest nest;
402 nest.capacity = capacity;
403 nest.blocks.assign(blockCount, MrMakeUnusedBlock());
404 nest.cells.assign(blockCount + (pageCount * MR_BLOCK_CELLS), RxMakeEmptyCell(0));
405 nest.fractions.assign(size_t{capacity.fractions} * MR_BLOCK_CELLS, MrMakeEmptyFraction());
406 nest.ledger.assign(size_t{MR_LEDGER_LEVELS} * capacity.ledgerColumns, 0);
407 nest.index.assign(capacity.indexEntries, MR_INDEX_EMPTY);
408 nest.requests.assign(MR_MAX_REQUESTS, MrMakeRequest(0, 0, 0, 0, 0));
409 nest.states.assign(MR_MAX_REQUESTS, MrMakeRequestState());
410 nest.claims.assign(capacity.worldBlocks, MR_NO_CLAIM);
411 nest.seeds.assign(capacity.worldBlocks, 0);
412
413 // --- 空きのスタック: 上(最後)から 0, 1, 2… と取れるように積む。世界の頁は観察の枠の頁の後ろの番号 ---
414 nest.freeBlocks.resize(size_t{capacity.worldBlocks} + capacity.pages);
415 for (uint32_t i = 0; i < capacity.worldBlocks; ++i)
416 nest.freeBlocks[i] = capacity.worldBlocks - 1 - i;
417
418 for (uint32_t i = 0; i < capacity.pages; ++i)
419 nest_detail::FreePageAt(nest, i) = capacity.observerBlocks + capacity.pages - 1 - i;
420
421 nest.freeFractions.resize(capacity.fractions);
422 for (uint32_t i = 0; i < capacity.fractions; ++i)
423 nest.freeFractions[i] = capacity.fractions - 1 - i;
424
425 nest.counters[MR_COUNTER_FREE_BLOCKS] = capacity.worldBlocks;
426 nest.counters[MR_COUNTER_FREE_FRACTIONS] = capacity.fractions;
427 nest.counters[MR_COUNTER_FREE_PAGES] = capacity.pages;
428
429 return nest;
430 }
431
432 void PlaceMirrorBlock(MultiresNest& nest, uint32_t slot, int32_t level, int64_t originX, int64_t originY,
433 int64_t originZ, std::span<const RxCell> cells) {
434 FX_ASSERT(cells.size() == MR_BLOCK_CELLS);
435 FX_ASSERT(IsObserverSlot(nest, slot));
436 const uint32_t page = MrObserverPage(slot, nest.capacity.worldBlocks);
437 nest.blocks[slot] = MrMakeMirrorBlock(level, originX, originY, originZ, page);
438 for (uint32_t index = 0; index < MR_BLOCK_CELLS; ++index)
439 PageCellAt(nest, page, index) = cells[index];
440 }
441
442 void RefineShadowChain(MultiresNest& nest, uint32_t parentSlot, uint32_t firstChildSlot, uint32_t levelCount,
443 const MultiresPoint& point) {
444 uint32_t parent = parentSlot;
445 for (uint32_t i = 0; i < levelCount; ++i) {
446 RefineShadowLevel(nest, parent, firstChildSlot + i, point);
447 parent = firstChildSlot + i;
448 }
449 }
450
451 void RemoveShadowChain(MultiresNest& nest, uint32_t firstSlot, uint32_t levelCount) {
452 for (uint32_t i = 0; i < levelCount; ++i) {
453 FX_ASSERT(nest.blocks[firstSlot + i].kind == MR_BLOCK_SHADOW);
454 nest.blocks[firstSlot + i] = MrMakeUnusedBlock();
455 }
456 }
457
458 void StepNest(MultiresNest& nest, const BakedReactionTable& table, uint64_t worldSeed, uint64_t tick,
459 const MultiresStepOptions& options) {
460 std::vector<uint8_t> stepped(nest.blocks.size(), 0);
461 for (uint32_t slot = 0; slot < nest.blocks.size(); ++slot) {
462 const uint32_t kind = nest.blocks[slot].kind;
463 stepped[slot] = kind != MR_BLOCK_UNUSED && kind != MR_BLOCK_MIRROR ? 1 : 0;
464 }
465
466 const std::vector<nest_detail::BlockStepResult> results = nest_detail::StepBlocks(nest, table.View(), stepped,
467 worldSeed, tick, options, 0);
468 nest_detail::RecordWaitResults(nest, results, tick);
469 }
470
471 void PullBackShadowChain(MultiresNest& nest, const BakedReactionTable& table, uint32_t firstShadowSlot,
472 uint32_t levelCount) {
473 const ReactionTableView view = table.View();
474 for (uint32_t i = 0; i < levelCount; ++i)
475 PullBackLevel(nest, view, firstShadowSlot + i);
476 }
477
478 uint64_t HashRealLeaves(const MultiresNest& nest) {
479 uint64_t hash = 0;
480 for (uint32_t slot = 0; slot < nest.blocks.size(); ++slot) {
481 const MrBlock& block = nest.blocks[slot];
482 if (block.kind != MR_BLOCK_REAL)
483 continue;
484
485 hash = FxHashCombine(hash, HashBlock(block));
486 for (uint32_t index = 0; index < MR_BLOCK_CELLS; ++index) {
487 if (!MrIsSteppedCell(block, index))
488 continue;
489
490 hash = FxHashCombine(hash, HashReactionCell(LoadNestCell(nest, slot, index)));
491 hash = FxHashCombine(hash, HashFraction(FractionAt(nest, block.fraction, index)));
492 }
493 }
494
495 return hash;
496 }
497
498 uint64_t HashWholeNest(const MultiresNest& nest) {
499 uint64_t hash = 0;
500 for (const MrBlock& block : nest.blocks) {
501 hash = FxHashCombine(hash, HashBlock(block));
502 hash = FxHashCombine(hash, block.activeTick);
503 hash = FxHashCombine(hash, block.busyTick);
504 hash = FxHashCombine(hash, block.wakeTick);
505 hash = FxHashCombine(hash, block.page);
506 hash = FxHashCombine(hash, block.quietCheck);
507 }
508
509 for (const RxCell& cell : nest.cells)
510 hash = FxHashCombine(hash, HashReactionCell(cell));
511
512 for (const MrFraction& fraction : nest.fractions)
513 hash = FxHashCombine(hash, HashFraction(fraction));
514
515 for (const uint32_t slot : nest.freeBlocks)
516 hash = FxHashCombine(hash, slot);
517
518 for (const uint32_t slot : nest.freeFractions)
519 hash = FxHashCombine(hash, slot);
520
521 for (const uint64_t value : nest.ledger)
522 hash = FxHashCombine(hash, value);
523
524 for (const uint32_t counter : nest.counters)
525 hash = FxHashCombine(hash, counter);
526
527 return hash;
528 }
529
531 int32_t finestLevel) {
532 ConservedTotals totals;
533 totals.elements.assign(table.elementNames.size(), Wide256{});
534 for (uint32_t slot = 0; slot < nest.blocks.size(); ++slot) {
535 const MrBlock& block = nest.blocks[slot];
536 if (block.kind != MR_BLOCK_REAL)
537 continue;
538
539 FX_ASSERT(block.level <= finestLevel);
540 const auto shiftBits = static_cast<uint32_t>(finestLevel - block.level) * MR_LEVEL_SHIFT;
541 for (uint32_t index = 0; index < MR_BLOCK_CELLS; ++index) {
542 if (!MrIsSteppedCell(block, index))
543 continue;
544
545 const MrFraction fraction = FractionAt(nest, block.fraction, index);
546 AddCellTotals(totals, table, LoadNestCell(nest, slot, index), fraction, shiftBits);
547 }
548 }
549
550 AddLedgerTotals(totals, nest, table, finestLevel);
551
552 return totals;
553 }
554
555} // namespace bicameral::sim
void PullBackLevel(MultiresNest &nest, const ReactionTableView &table, uint32_t shadowSlot)
void AddLedgerTotals(ConservedTotals &totals, const MultiresNest &nest, const BakedReactionTable &table, int32_t finestLevel)
void AddLedgerColumn(ConservedTotals &totals, const BakedReactionTable &table, uint32_t column, const Wide256 &amount)
multires::MrFraction FractionAt(const MultiresNest &nest, uint32_t fractionSlot, uint32_t index)
nest_detail::BlockStepResult StepPagedBlock(MultiresNest &nest, const ReactionTableView &view, uint32_t slot, uint64_t worldSeed, uint64_t tick, bool react, std::span< const MrEnergyDelta > deltas)
Wide256 LedgerValue(uint64_t value, int32_t level, int32_t finestLevel)
Wide256 MakeWide256(uint64_t high, uint64_t low, bool negative)
void AddTo(Wide256 &sum, const Wide256 &value)
reaction::RxCell & PageCellAt(MultiresNest &nest, uint32_t page, uint32_t index)
Wide256 ShiftLeft(const Wide256 &value, uint32_t bits)
void AddCellTotals(ConservedTotals &totals, const BakedReactionTable &table, const RxCell &cell, const MrFraction &fraction, uint32_t shiftBits)
uint64_t HashFraction(const MrFraction &fraction)
Wide256 MultiplySmall(Wide256 value, uint32_t factor)
reaction::RxCell & CellAt(MultiresNest &nest, uint32_t slot, uint32_t index)
uint64_t FractionEnergy(const MultiresNest &nest, uint32_t fractionSlot, uint32_t index)
bool IsObserverSlot(const MultiresNest &nest, uint32_t slot)
void RefineShadowLevel(MultiresNest &nest, uint32_t parentSlot, uint32_t childSlot, const MultiresPoint &point)
size_t PageCellAddress(const MultiresNest &nest, uint32_t page, uint32_t index)
uint32_t & FreePageAt(MultiresNest &nest, uint32_t position)
std::vector< BlockStepResult > StepBlocks(MultiresNest &nest, const ReactionTableView &view, std::span< uint8_t > stepped, uint64_t worldSeed, uint64_t tick, const MultiresStepOptions &options, uint32_t wakeMark)
multires::MrFraction FractionAt(const MultiresNest &nest, uint32_t fractionSlot, uint32_t index)
std::vector< uint8_t > ExpandForStep(MultiresNest &nest, std::span< BlockStepResult > results)
std::vector< uint32_t > ExpandWantedPages(MultiresNest &nest)
void RecordWaitResults(MultiresNest &nest, std::span< const BlockStepResult > results, uint64_t tick)
reaction::RxCell & UniformAt(MultiresNest &nest, uint32_t slot)
void ApplyEnergyDelta(MultiresNest &nest, uint32_t slot, uint32_t index, RxCell &cell, const MrEnergyDelta &delta)
reaction::RxCell & PageCellAt(MultiresNest &nest, uint32_t page, uint32_t index)
void SetFraction(MultiresNest &nest, uint32_t fractionSlot, uint32_t index, const multires::MrFraction &fraction)
uint32_t PopPage(MultiresNest &nest)
std::vector< MrEnergyDelta > StepConduction(MultiresNest &nest, const ReactionTableView &view, std::span< uint8_t > stepped, const MultiresStepOptions &options, uint32_t wakeMark, std::span< BlockStepResult > results)
void ResolvePokes(MultiresNest &nest, uint64_t tick)
reaction::RxCell & CellAt(MultiresNest &nest, uint32_t slot, uint32_t index)
void PlaceMirrorBlock(MultiresNest &nest, uint32_t slot, int32_t level, int64_t originX, int64_t originY, int64_t originZ, std::span< const RxCell > cells)
std::array< uint64_t, 4 > Wide256
void PullBackShadowChain(MultiresNest &nest, const BakedReactionTable &table, uint32_t firstShadowSlot, uint32_t levelCount)
uint64_t HashReactionCell(const RxCell &cell)
void RemoveShadowChain(MultiresNest &nest, uint32_t firstSlot, uint32_t levelCount)
ConservedTotals ComputeConservedTotals(const MultiresNest &nest, const BakedReactionTable &table, int32_t finestLevel)
MultiresNest MakeMultiresNest(const MultiresCapacity &capacity)
uint32_t UsedWorldPages(const MultiresNest &nest)
uint64_t HashWholeNest(const MultiresNest &nest)
uint64_t HashRealLeaves(const MultiresNest &nest)
void RefineShadowChain(MultiresNest &nest, uint32_t parentSlot, uint32_t firstChildSlot, uint32_t levelCount, const MultiresPoint &point)
RxCell LoadNestCell(const MultiresNest &nest, uint32_t slot, uint32_t index)
void StepNest(MultiresNest &nest, const BakedReactionTable &table, uint64_t worldSeed, uint64_t tick, const MultiresStepOptions &options)
std::vector< uint32_t > speciesElements
std::vector< std::string > elementNames
ReactionTableView View() const
std::vector< Wide256 > elements
std::vector< uint8_t > seeds
std::vector< reaction::RxCell > cells
std::vector< uint32_t > index
std::vector< multires::MrRequestState > states
std::vector< multires::MrBlock > blocks
std::vector< uint32_t > claims
std::vector< multires::MrFraction > fractions
std::vector< uint32_t > freeFractions
std::vector< uint64_t > ledger
std::vector< multires::MrRequest > requests
std::vector< uint32_t > freeBlocks
std::array< uint32_t, multires::MR_COUNTER_COUNT > counters