Bicameral Engine
GPU-resident engine for a magic-research open-world game
Loading...
Searching...
No Matches
multires_nest.h
Go to the documentation of this file.
1// multires_nest.h — 多重解像度の木の CPU リファレンス(17 §1・§3・§5。T-0017・T-0018・T-0100・T-0101。ADR-0015・ADR-0016)。
2// ブロックの枠・セル・端数・空きのスタック・索引・世界の帳簿を配列で持ち、GPU(sim/gpu_multires)と同じ順・同じ関数
3// (shaders/common/multires.hlsli・multires_tree.hlsli)で操作する。テストは CPU と GPU の配列をそのまま比べる(索引だけは引いた結果を比べる)。
4//
5// 枠の範囲: 世界の枠 [0, worldBlocks) は木の管理(要求)が空きのスタックから取る。観察の枠 [worldBlocks, +observerBlocks)
6// (影・世界の写し)は呼ぶ側が直接決める(覗いても世界の枠の番号が変わらない)。
7// 世界の木を変えるのは要求だけ(SubmitRequests → ProcessRequests)。根は最初に PlaceRootBlock で置く。
8// セル(T-0102): 見出しの枠とセルの頁は別のプール。一様なブロックは頁を持たず値 1 つ(multires.hlsli の先頭)。
9// セルを読むときは LoadNestCell(一様でも頁でも同じ「論理のセル」)を使う。
10// 浮動小数点は使わない(engine/src/sim は検査の対象)。
11#pragma once
12
13#include <array>
14#include <cstdint>
15#include <memory>
16#include <span>
17#include <vector>
18
19#include "common/multires_tree.hlsli"
21#include "sim/reaction_table.h"
22
23namespace bicameral::sim {
24
25 // 細かくする先の点(pointLevel のセルの単位の世界の座標)
27 int64_t x = 0;
28 int64_t y = 0;
29 int64_t z = 0;
30 int32_t level = 0;
31 };
32
34 uint32_t worldBlocks = 0; // 世界の枠の数
35 uint32_t observerBlocks = 0; // 観察の枠の数
36 uint32_t fractions = 0; // 端数の枠の数
37 uint32_t pages = 0; // 世界の頁の数(観察の枠の頁は別に観察の枠の数だけ。T-0102)
38 uint32_t indexEntries = 0; // 索引の大きさ(2 の冪。世界の枠の 2 倍以上を勧める)
39 uint32_t ledgerColumns = 1; // 帳簿の列(1 + 物質の数)
40 int32_t rootLevel = 0; // 根のレベル
41 };
42
43 struct MultiresNest {
45
46 // --- 状態(CPU と GPU で配列のまま一致する)---
47 std::vector<multires::MrBlock> blocks; // 世界の枠 + 観察の枠
48 std::vector<reaction::RxCell> cells; // [一様の値 × 枠][頁 × MR_BLOCK_CELLS](頁 = 観察の枠の数 + pages。T-0102)
49 std::vector<multires::MrFraction> fractions; // 端数の枠 × MR_BLOCK_CELLS
50 // [世界の枠の空きのスタック(数は counters[MR_COUNTER_FREE_BLOCKS])][世界の頁の空きのスタック(MR_COUNTER_FREE_PAGES)]
51 std::vector<uint32_t> freeBlocks;
52 std::vector<uint32_t> freeFractions; // 端数の枠の空きのスタック(数は counters[MR_COUNTER_FREE_FRACTIONS])
53 std::vector<uint64_t> ledger; // 世界の帳簿 [MR_LEDGER_LEVELS × ledgerColumns]
54 std::array<uint32_t, multires::MR_COUNTER_COUNT> counters{};
55
56 // --- 活性の種(T-0100)。次の StepActive で刻むブロックの元(世界の枠ごとに 0 / 1)。
57 // GPU は順の決まらない一覧で持つので、集合として比べる(状態の要約に入れない)---
58 std::vector<uint8_t> seeds;
59
60 // --- 索引(見出しから作り直せる。状態に入らない)---
61 std::vector<uint32_t> index;
62
63 // --- 1 刻みの要求(状態に入らない。数は counters[MR_COUNTER_REQUESTS])---
64 std::vector<multires::MrRequest> requests; // MR_MAX_REQUESTS 個
65 std::vector<multires::MrRequestState> states; // MR_MAX_REQUESTS 個
66 std::vector<uint32_t> claims; // 世界の枠ごとの取り合いの印
67
68 // --- 最後の刻みの陰解法の費用(T-0119。状態に入らない。計測用)---
70 uint32_t implicitCells = 0; // 陰解法の系に入れたセルの数(境のセルを含む)
71 uint32_t implicitDeferredBlocks = 0; // 上限に入らずこの刻み陽解法のままにしたブロックの数(T-0178)
72 std::vector<std::array<uint32_t, 2>>
73 implicitLevels; // 多重格子の段ごとの [節の数, 1 節の隣の最大](GPU の分け方を見る)
74
75 // true なら、陰解法を入れた刻みの系(解く前の形)を implicitGrid に写す(GPU の GpuImplicit と比べる試験・計測用。T-0127)
76 bool captureImplicitGrid = false;
78 // true なら、陰解法の系を作る時の木(陽解法の流れの後・変化を足す前)と凍った枠を写す(GPU で系を作る GpuImplicitBuild と
79 // 比べる試験用。T-0129)。写しの中の写しは空
80 bool captureImplicitNest = false;
81 std::shared_ptr<const MultiresNest> implicitNest;
82 std::vector<uint8_t> implicitFrozen;
83 };
84
85 // 細かい所の熱の陰解法の系の大きさの上限(T-0178。GPU では VRAM に先に取るバッファの大きさ = GpuMultiresImplicitLimits)。
86 // 欄ごとに 0 なら上限なし(CPU だけ。GPU は全部の欄が要る)。GPU と比べる CPU の刻みには EnableImplicitConduction と同じ値を入れる
88 uint32_t unknowns = 0; // 未知数のセル(面は 6 倍まで)
89 uint32_t cells = 0; // 未知数 + 境のセル
90 uint32_t nodes = 0; // 多重格子の全部の段の節(段 0 = セルを含む)
91 uint32_t links = 0; // 多重格子の全部の段の隣
92 };
93
94 // 陰解法の系が上限に入りきらない刻みの扱い(T-0178。QUESTIONS Q22。仮〔ユーザー未確認〕で A)
95 enum class ImplicitOverflow : uint8_t {
96 Explicit, // A: 入りきらないブロック(枠の順で上限を超えた所から後)はその刻みだけ陽解法(頭打ち)のまま。熱は流れ、保存も守る
97 Freeze, // B: その刻み凍らせる(未実装。選べる口だけ。選ぶと A で動く)
98 StopRefine, // C: 細かくする要求を止める(未実装。選べる口だけ。選ぶと A で動く)
99 };
100
101 // 系に入れるブロックを選ぶ時の予算(T-0178。CPU の MarkImplicitBlocks と GPU の BuildScanBlocks が同じ値を使う):
102 // 未知数 ≤ min(unknowns, links / 12)(段 0 の隣 = 面 × 2 ≤ 未知数 × 12)、セル ≤ min(cells, nodes)(段 0 の節 = セル)。
103 // 多重格子の 2 段目からの節・隣は、入りきらない段を作らずに縮約を止める(BuildImplicitGrid の limits・implicit_levels.hlsl の ParentScanGroups)
105 uint32_t unknowns = UINT32_MAX;
106 uint32_t cells = UINT32_MAX;
107 };
108
110 const auto bound = [](uint32_t value) {
111 return value == 0 ? UINT32_MAX : value;
112 };
113 constexpr uint32_t LINKS_PER_UNKNOWN = 12;
114
115 const uint32_t byLinks = limits.links == 0 ? UINT32_MAX : limits.links / LINKS_PER_UNKNOWN;
116 const uint32_t unknowns = bound(limits.unknowns) < byLinks ? bound(limits.unknowns) : byLinks;
117 const uint32_t cells = bound(limits.cells) < bound(limits.nodes) ? bound(limits.cells) : bound(limits.nodes);
118
119 return {.unknowns = unknowns, .cells = cells};
120 }
121
122 // 1 刻みの選択
124 // 熱の伝導(T-0019。multires_conduction.hlsli)。GPU(sim/gpu_multires の RecordStep・RecordStepActive)も同じ結果(T-0107)
125 bool conduction = false;
126
127 // --- 細かいレベルの熱の刻み(T-0108。multires_conduction.hlsli の「細かいレベルの刻み」)---
128 // レベル k の伝導を 1 刻みに 4^clamp(k − subcycleBaseLevel, 0, maxSubcycleGap) 回の小刻みに分ける。
129 // subcycleBaseLevel は面の係数が熱容量の上限で頭打ちにならない最後のレベル(MrSubcycleBaseLevel。最も伝わりやすい物質で決める)。
130 // maxSubcycleGap = 0 なら分けない(T-0019 と同じ結果)。上限は 3(64 回。17 §4)。GPU も同じ結果(T-0109)
131 int32_t subcycleBaseLevel = 0;
132 uint32_t maxSubcycleGap = 0;
133
134 // --- 細かいレベルの熱の陰解法(方式②。T-0119。ADR-0019・D-434 の案 a)---
135 // true なら、流れを計算する側(細かい側)のレベルが subcycleBaseLevel より細かい面を、陰解法の 1 刻みで解く
136 // (multires_implicit_conduction.cpp)。maxSubcycleGap は 0 のこと(方式①と②は混ぜない)。GPU の伝導の段はまだ(解く段は T-0127、系を作るのは T-0129、呼ぶのは T-0132)
137 bool implicitConduction = false;
138 // V サイクルの上限。新しい温度の誤差の見込みが 1 mK 以下になったら止める(ADR-0019)。D-436(鋭い熱でも解き切る)なので
139 // 上限は「届かない時の安全のため」だけの大きさにする(当たったら安全網が陽解法の流れに戻す)
140 uint32_t implicitMaxCycles = 64;
141 // 陰解法にする最も細かいレベル = subcycleBaseLevel + implicitMaxGap。それより細かい所は陽解法のまま(頭打ちで遅い)。
142 // 温度の端数(mK × 2^16)の精度で、面の流れの誤差は約 4^Δk × 2^-16 mK になり、Δk 8 を超えると 1 mK の判定に届かない(T-0119)
143 uint32_t implicitMaxGap = 8;
144 // 系の大きさの上限と、入りきらない刻みの扱い(T-0178)。GPU は EnableImplicitConduction の上限を使う(CPU と比べる時は同じ値をここへ)
147 };
148
149 constexpr uint32_t MULTIRES_MAX_SUBCYCLE_GAP = 3;
150
151 // 保存量の合計(最も細かい単位 × 2^-64。256bit の 2 の補数、下の語から)
152 using Wide256 = std::array<uint64_t, 4>;
153
155 std::vector<Wide256> elements; // 添字 = 元素
157
158 bool operator==(const ConservedTotals&) const = default;
159 };
160
161 [[nodiscard]] MultiresNest MakeMultiresNest(const MultiresCapacity& capacity);
162
163 // --- セル(T-0102)---
164
165 // 枠 slot のセル index(一様なら値か、覆われていれば空。頁なら頁のセル)
166 [[nodiscard]] reaction::RxCell LoadNestCell(const MultiresNest& nest, uint32_t slot, uint32_t index);
167
168 // 使っている世界の頁の数(観察の枠の頁は数えない)
169 [[nodiscard]] uint32_t UsedWorldPages(const MultiresNest& nest);
170
171 // --- 世界の木 ---
172
173 // 根(本物、親なし。レベルは capacity.rootLevel)を空きのスタックから取った枠に置き、索引に入れる。cells は MR_BLOCK_CELLS 個。
174 // 全部同じなら一様(頁なし)、でなければ頁を取る(T-0102)。枠を返す
175 uint32_t PlaceRootBlock(MultiresNest& nest, int64_t originX, int64_t originY, int64_t originZ,
176 std::span<const reaction::RxCell> cells);
177
178 // 要求を一覧の後ろに足す(一覧は決定的な順に。1 刻み MR_MAX_REQUESTS 件まで)
179 void SubmitRequests(MultiresNest& nest, std::span<const multires::MrRequest> requests);
180
181 // 一覧の要求を処理する(解決 → 確定 → 割り当て → 適用 → 解放 → 索引の作り直し。17 §5)。一覧は空になる
182 void ProcessRequests(MultiresNest& nest);
183
184 // 索引で (レベル, 原点) の本物のブロックを引く(無ければ MR_NO_BLOCK)
185 [[nodiscard]] uint32_t LookupBlock(const MultiresNest& nest, int32_t level, int64_t originX, int64_t originY,
186 int64_t originZ);
187
188 // 索引を空にして、使っている本物のブロックを全部入れ直す
189 void RebuildIndex(MultiresNest& nest);
190
191 // --- 観察の枠(呼ぶ側が枠を決める)---
192
193 // 世界の写し(MR_BLOCK_MIRROR。刻まない・世界のハッシュに入らない)を置く。写し直すときも同じ。cells は MR_BLOCK_CELLS 個。
194 // 木の形をしていない世界(仮の世界の 64³)の 1 ブロックを、影の鎖の根の親にするため(T-0096)
195 void PlaceMirrorBlock(MultiresNest& nest, uint32_t slot, int32_t level, int64_t originX, int64_t originY,
196 int64_t originZ, std::span<const reaction::RxCell> cells);
197
198 // parentSlot の、点を含む八分の一を観察の影 firstChildSlot に細かくし、それを levelCount 段続ける(子の枠は firstChildSlot から順)。
199 // 親に触れない(世界に返さない)
200 void RefineShadowChain(MultiresNest& nest, uint32_t parentSlot, uint32_t firstChildSlot, uint32_t levelCount,
201 const MultiresPoint& point);
202
203 // 影の鎖を捨てる(世界に返さない)
204 void RemoveShadowChain(MultiresNest& nest, uint32_t firstSlot, uint32_t levelCount);
205
206 // 影の鎖を上から順に親へ引き戻す(firstShadowSlot の親は本物か写しのブロック)
207 void PullBackShadowChain(MultiresNest& nest, const BakedReactionTable& table, uint32_t firstShadowSlot,
208 uint32_t levelCount);
209
210 // --- 刻みと要約 ---
211
212 // 刻むセル(本物の葉と影のセル)を 1 刻み(全部を刻む。活性の正しさを確かめる基準)。options.conduction なら熱の伝導の後に反応。
213 // 一様なブロックは変わる(反応が進む・伝導の流れがある)時だけ、枠の順で頁に広げて刻む(頁が足りなければ刻まずに数え、種にする。T-0102)。
214 // 伝導は面の隣を木から探し、レベルの違う面も保存量をビット単位で保って受け渡す(影は自分のブロックの中だけ。T-0019)
215 void StepNest(MultiresNest& nest, const BakedReactionTable& table, uint64_t worldSeed, uint64_t tick,
216 const MultiresStepOptions& options = {});
217
218 // 活性のブロックだけ刻む(T-0100。multires_activity.hlsli): 種とその面の隣に印(activeTick)を付けて刻み、
219 // 進める規則があった・変わったブロックを次の種にする。観察の枠は全部刻む。結果のセルは StepNest と同じになる。
220 // 待ちの丸め(T-0115)では、種 = 見出しを全部なめて起こす刻み(wakeTick)が来たブロック + つつかれたブロック。
221 // 変わったブロックは次の刻みに起こす(伝導の面の隣も起こすため)
222 void StepActive(MultiresNest& nest, const BakedReactionTable& table, uint64_t worldSeed, uint64_t tick,
223 const MultiresStepOptions& options = {});
224
225 // 頁を持つ世界のブロックで、刻み tick にちょうど静かになり(multires_activity.hlsli の MrWantsFoldCheck)一様なものを、
226 // 値 1 つに戻して頁を枠の順に空きのスタックへ返す(T-0103)。SubmitQuietCoarsenRequests の前に、StepActive で刻む木に使う
227 void FoldQuietPages(MultiresNest& nest, uint64_t tick);
228
229 // ほぼ同じ頁も畳む(T-0104。D-428。17 §5「ほぼ同じ頁を畳む」): 調べる時は上と同じ。覆われていないセルの差が tolerance の中なら
230 // 平均の値 1 つに戻し、切り捨てで余った単位(セルの数未満)を世界の帳簿へ移す(合計はビット一致)。
231 // 同じ時に、端数の枠を持つブロックは端数を全部帳簿へ移して枠を返す(1 単位未満 = 計器で測れない差。ADR-0017 追記)。
232 // tolerance が MrExactFoldTolerance なら上と同じ結果(ビット単位で同じ時だけ畳む・端数は返さない)。
233 // GPU は GpuMultires::RecordFoldPages の許容差つき(T-0112)
234 void FoldQuietPages(MultiresNest& nest, const BakedReactionTable& table, uint64_t tick,
235 const multires::MrFoldTolerance& tolerance);
236
237 // 頁を持つブロックの覆われていないセルの集計(T-0104。畳む判定と計測に使う)
238 [[nodiscard]] multires::MrFoldStats CollectFoldStats(const MultiresNest& nest, const ReactionTableView& table,
239 uint32_t slot);
240
241 // 静かな本物の葉(MR_QUIET_TICKS 刻みを超えて変わっていない。multires_activity.hlsli)を粗くする要求を、
242 // 一覧の後ろに世界の枠の順で足す(T-0101)。一覧が一杯なら足さずに数える。ProcessRequests の前に、StepActive で刻む木に使う。
243 // 粗くするのは、子のセル 2×2×2 の組ごとの差が tolerance の中の葉だけ(D-430・T-0113。MrCoarsenGroupWithin。
244 // 静かになって初めての時に 1 回調べて見出しの quietCheck に置く)。許容差を渡さない版は完全に同じ(組の中がビット単位で同じ)時だけ。
245 // GPU は GpuMultires::RecordQuietRequests(TreeQuietCheck → TreeQuiet)
246 void SubmitQuietCoarsenRequests(MultiresNest& nest, uint64_t tick);
247 void SubmitQuietCoarsenRequests(MultiresNest& nest, const BakedReactionTable& table, uint64_t tick,
248 const multires::MrFoldTolerance& tolerance);
249
250 // 活性の種の枠の一覧(枠の順)
251 [[nodiscard]] std::vector<uint32_t> SeedSlots(const MultiresNest& nest);
252
253 // 待ちの丸め(T-0115)で刻み tick の種になる世界の本物のブロックの枠の一覧(枠の順): 種の印があるか、起こす刻み(wakeTick)が来た
254 [[nodiscard]] std::vector<uint32_t> WaitSeedSlots(const MultiresNest& nest, uint64_t tick);
255
256 // 世界(本物の葉のセルと端数)の要約。影は入らない
257 [[nodiscard]] uint64_t HashRealLeaves(const MultiresNest& nest);
258
259 // 状態の全部(見出し〔活性と忙しさの印も〕・セル・端数・空きのスタック・帳簿・数える欄)の要約。CPU と GPU を比べるため。索引・要求・種は入らない
260 [[nodiscard]] uint64_t HashWholeNest(const MultiresNest& nest);
261
262 // 本物の葉のセルと世界の帳簿の、元素の数とエネルギーの合計を finestLevel の単位 × 2^-64 で
263 // (finestLevel は使っている最も細かいレベルと、帳簿に落ちた最も細かいレベル以上)
264 [[nodiscard]] ConservedTotals ComputeConservedTotals(const MultiresNest& nest, const BakedReactionTable& table,
265 int32_t finestLevel);
266
267} // namespace bicameral::sim
void FoldQuietPages(MultiresNest &nest, uint64_t tick)
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)
std::vector< uint32_t > SeedSlots(const MultiresNest &nest)
MrFoldStats CollectFoldStats(const MultiresNest &nest, const ReactionTableView &table, uint32_t slot)
void RemoveShadowChain(MultiresNest &nest, uint32_t firstSlot, uint32_t levelCount)
void SubmitQuietCoarsenRequests(MultiresNest &nest, uint64_t tick)
ConservedTotals ComputeConservedTotals(const MultiresNest &nest, const BakedReactionTable &table, int32_t finestLevel)
constexpr uint32_t MULTIRES_MAX_SUBCYCLE_GAP
MultiresNest MakeMultiresNest(const MultiresCapacity &capacity)
uint32_t UsedWorldPages(const MultiresNest &nest)
uint64_t HashWholeNest(const MultiresNest &nest)
uint64_t HashRealLeaves(const MultiresNest &nest)
constexpr MultiresImplicitBudget MakeMultiresImplicitBudget(const MultiresImplicitLimits &limits)
void SubmitRequests(MultiresNest &nest, std::span< const multires::MrRequest > requests)
void RefineShadowChain(MultiresNest &nest, uint32_t parentSlot, uint32_t firstChildSlot, uint32_t levelCount, const MultiresPoint &point)
void RebuildIndex(MultiresNest &nest)
RxCell LoadNestCell(const MultiresNest &nest, uint32_t slot, uint32_t index)
uint32_t LookupBlock(const MultiresNest &nest, int32_t level, int64_t originX, int64_t originY, int64_t originZ)
void ProcessRequests(MultiresNest &nest)
uint32_t PlaceRootBlock(MultiresNest &nest, int64_t originX, int64_t originY, int64_t originZ, std::span< const reaction::RxCell > cells)
void StepNest(MultiresNest &nest, const BakedReactionTable &table, uint64_t worldSeed, uint64_t tick, const MultiresStepOptions &options)
std::vector< uint32_t > WaitSeedSlots(const MultiresNest &nest, uint64_t tick)
void StepActive(MultiresNest &nest, const BakedReactionTable &table, uint64_t worldSeed, uint64_t tick, const MultiresStepOptions &options)
bool operator==(const ConservedTotals &) const =default
std::vector< Wide256 > elements
std::vector< uint8_t > implicitFrozen
std::vector< uint8_t > seeds
std::vector< reaction::RxCell > cells
std::vector< uint32_t > index
std::shared_ptr< const MultiresNest > implicitNest
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< std::array< uint32_t, 2 > > implicitLevels
std::vector< uint32_t > freeBlocks
std::array< uint32_t, multires::MR_COUNTER_COUNT > counters
MultiresImplicitLimits implicitLimits