Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
ca_checkpoint_safety_test.cc
Go to the documentation of this file.
1/*
2 Aleph_w
3
4 Data structures & Algorithms
5 version 2.0.0b
6 https://github.com/lrleon/Aleph-w
7
8 This file is part of Aleph-w library
9
10 Copyright (c) 2002-2026 Leandro Rabindranath Leon
11
12 Permission is hereby granted, free of charge, to any person obtaining a copy
13 of this software and associated documentation files (the "Software"), to deal
14 in the Software without restriction, including without limitation the rights
15 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16 copies of the Software, and to permit persons to whom the Software is
17 furnished to do so, subject to the following conditions:
18
19 The above copyright notice and this permission notice shall be included in all
20 copies or substantial portions of the Software.
21
22 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28 SOFTWARE.
29*/
30
49#include <atomic>
50#include <chrono>
51#include <cstdint>
52#include <thread>
53#include <cstdlib>
54#include <cstring>
55#include <filesystem>
56#include <fstream>
57#include <random>
58#include <string>
59#include <vector>
60
61#if !defined(_WIN32)
62# include <sys/wait.h>
63# include <unistd.h>
64#else
65# include <process.h>
66#endif
67
68#include <gtest/gtest.h>
69
71#include <ca-checkpoint.H>
72#include <tpl_ca_engine.H>
73#include <tpl_ca_lattice.H>
74#include <tpl_ca_neighborhood.H>
75#include <tpl_ca_rule.H>
76#include <tpl_ca_storage.H>
78
79using namespace Aleph;
80using namespace Aleph::CA;
81
82namespace
83{
84
87
89long long process_id() noexcept
90{
91#if defined(_WIN32)
92 return static_cast<long long>(_getpid());
93#else
94 return static_cast<long long>(getpid());
95#endif
96}
97
98std::filesystem::path tmp_dir(const std::string &name)
99{
100 // A steady_clock tick alone is unique *within* a process, but not
101 // across the several processes that actually run this suite (each
102 // TEST() is its own ctest process, and CI runs ctest with
103 // --parallel) -- two processes' calls landing in the same clock tick
104 // produce the identical name and race on the same directory. Mixing
105 // in the process id and a per-process counter closes that gap
106 // regardless of clock resolution or call count.
107 static std::atomic<unsigned long long> counter{0};
108 const auto tick = std::chrono::steady_clock::now().time_since_epoch().count();
109 auto p = std::filesystem::temp_directory_path()
110 / ("aleph_ca_ckpt_safety_" + name + "_"
111 + std::to_string(static_cast<long long>(tick))
112 + "_" + std::to_string(process_id())
113 + "_" + std::to_string(counter++));
114 std::filesystem::create_directories(p);
115 return p;
116}
117
118Grid random_grid(ca_size_t side, std::uint32_t seed, double p_alive = 0.4)
119{
120 std::mt19937 rng(seed);
121 std::uniform_real_distribution<double> u(0.0, 1.0);
122 Grid g({side, side}, 0);
123 for (ca_size_t i = 0; i < side; ++i)
124 for (ca_size_t j = 0; j < side; ++j)
125 g.set({static_cast<ca_index_t>(i), static_cast<ca_index_t>(j)},
126 u(rng) < p_alive ? 1 : 0);
127 return g;
128}
129
130bool grids_equal(const Grid &a, const Grid &b)
131{
132 if (a.extents() != b.extents())
133 return false;
134 for (ca_size_t i = 0; i < a.size(0); ++i)
135 for (ca_size_t j = 0; j < a.size(1); ++j)
136 if (a.at({static_cast<ca_index_t>(i), static_cast<ca_index_t>(j)})
137 != b.at({static_cast<ca_index_t>(i), static_cast<ca_index_t>(j)}))
138 return false;
139 return true;
140}
141
142std::size_t byte_count(const std::filesystem::path &p)
143{
144 std::error_code ec;
145 const auto sz = std::filesystem::file_size(p, ec);
146 return ec ? 0 : static_cast<std::size_t>(sz);
147}
148
149std::size_t count_tmp_siblings(const std::filesystem::path &target)
150{
151 std::size_t n = 0;
152 const auto dir = target.parent_path();
153 const auto stem = target.filename().string();
154 for (const auto &entry : std::filesystem::directory_iterator(dir))
155 {
156 const auto name = entry.path().filename().string();
157 if (name.rfind(stem + ".tmp.", 0) == 0)
158 ++n;
159 }
160 return n;
161}
162
163} // namespace
164
165// =====================================================================
166// Compressed round-trip matches the raw round-trip
167// =====================================================================
168
170{
171 const auto dir = tmp_dir("compress_rt");
172 const auto raw_path = dir / "raw.bin";
173 const auto cmp_path = dir / "compressed.bin";
174
176 eng.run(11);
177
180 opts.compress = true;
181 opts.level = 6;
183
184 const auto raw_h = inspect_checkpoint(raw_path);
185 const auto cmp_h = inspect_checkpoint(cmp_path);
188 EXPECT_EQ(cmp_h.compression_level, 6u);
189 EXPECT_EQ(raw_h.cell_count, cmp_h.cell_count);
190 EXPECT_EQ(raw_h.step_count, cmp_h.step_count);
191
192 // Restore both checkpoints into fresh engines and compare the frames.
193 Engine restored_raw(Grid({48, 48}, 0), make_game_of_life_rule(), Moore<2, 1>{});
194 Engine restored_cmp(Grid({48, 48}, 0), make_game_of_life_rule(), Moore<2, 1>{});
197
199 EXPECT_EQ(restored_raw.steps_run(), restored_cmp.steps_run());
200
201 // Compressed payload should be strictly smaller than raw for this
202 // mid-density GoL pattern (cells are mostly 0/1 ints, so DEFLATE
203 // catches the long runs of equal bytes).
205
206 std::filesystem::remove_all(dir);
207}
208
209// =====================================================================
210// Atomic-rename: a crash between fsync and rename leaves any
211// pre-existing target intact and never publishes a partial file.
212// =====================================================================
213
214#if !defined(_WIN32)
215
217{
218 const auto dir = tmp_dir("crash_preserve");
219 const auto target = dir / "snap.bin";
220
221 // Plant a baseline file that must survive an aborted write.
222 {
223 std::ofstream f(target, std::ios::binary);
224 f << "PRE-EXISTING";
225 }
226 const std::string before = [&]
227 {
228 std::ifstream in(target);
229 std::string s((std::istreambuf_iterator<char>(in)),
230 std::istreambuf_iterator<char>());
231 return s;
232 }();
233 ASSERT_EQ(before, std::string("PRE-EXISTING"));
234
235 const pid_t pid = ::fork();
236 ASSERT_GE(pid, 0);
237 if (pid == 0)
238 {
239 // Child: emulate save_checkpoint up to (but not past) the
240 // rename point, then exit hard. `_Exit` skips destructors so
241 // the temp file remains; the parent verifies the *target* file
242 // is unchanged regardless.
243 using namespace Aleph::CA::ca_checkpoint_detail;
244 const auto tmp = make_tmp_path(target);
245 {
246 std::ofstream out(tmp, std::ios::binary | std::ios::trunc);
247 std::vector<char> dummy(4096, 'X');
248 out.write(dummy.data(), dummy.size());
249 out.flush();
250 }
251 sync_file(tmp);
252 // ------- CRASH WINDOW: post-flush, pre-rename -------
253 std::_Exit(0);
254 }
255
256 int status = 0;
257 ::waitpid(pid, &status, 0);
258
259 EXPECT_TRUE(std::filesystem::exists(target));
260 std::ifstream in(target);
261 const std::string after((std::istreambuf_iterator<char>(in)),
262 std::istreambuf_iterator<char>());
263 EXPECT_EQ(after, std::string("PRE-EXISTING"))
264 << "target must keep its previous contents when the writer crashes "
265 "before rename";
266
267 // The child died after writing the .tmp sibling — that file may
268 // legitimately linger on disk. What must NOT happen is the partial
269 // file being mistaken for `target`.
270 std::filesystem::remove_all(dir);
271}
272
274{
275 const auto dir = tmp_dir("crash_new");
276 const auto target = dir / "snap.bin";
277 ASSERT_FALSE(std::filesystem::exists(target));
278
279 const pid_t pid = ::fork();
280 ASSERT_GE(pid, 0);
281 if (pid == 0)
282 {
283 using namespace Aleph::CA::ca_checkpoint_detail;
284 const auto tmp = make_tmp_path(target);
285 {
286 std::ofstream out(tmp, std::ios::binary | std::ios::trunc);
287 std::vector<char> dummy(2048, 'Y');
288 out.write(dummy.data(), dummy.size());
289 out.flush();
290 }
291 sync_file(tmp);
292 std::_Exit(0);
293 }
294 int status = 0;
295 ::waitpid(pid, &status, 0);
296
297 EXPECT_FALSE(std::filesystem::exists(target))
298 << "no target file must exist when the writer crashes before rename";
299 std::filesystem::remove_all(dir);
300}
301
302#endif // !_WIN32
303
304// =====================================================================
305// Async writer persists every submitted task by flush()
306// =====================================================================
307
309{
310 const auto dir = tmp_dir("async_persist");
311
315
316 constexpr std::size_t N = 6;
317 std::vector<std::filesystem::path> paths;
318 paths.reserve(N);
319 for (std::size_t k = 0; k < N; ++k)
320 {
321 eng.run(1);
322 const auto p = dir / ("async_" + std::to_string(k) + ".bin");
323 paths.push_back(p);
324 writer.submit(eng, p);
325 }
326 writer.flush();
327
328 for (const auto &p : paths)
329 EXPECT_TRUE(std::filesystem::exists(p)) << "missing async snapshot: " << p;
330 EXPECT_EQ(writer.total_written(), N);
331 EXPECT_EQ(writer.total_dropped(), 0u);
332
333 // No leftover temp siblings.
334 for (const auto &p : paths)
336
337 std::filesystem::remove_all(dir);
338}
339
340// =====================================================================
341// Drop_Oldest policy actually drops queued tasks under back-pressure
342// =====================================================================
343
345{
346 const auto dir = tmp_dir("async_drop");
347
351
352 // Throttle the worker so the queue fills up reliably on any hardware.
353 // Without this, fast SSDs can drain the 2-slot queue between submissions
354 // and zero drops are observed (flaky in Debug builds).
355 writer.set_before_write_hook([] {
356 std::this_thread::sleep_for(std::chrono::milliseconds(20));
357 });
358
359 constexpr std::size_t N = 32;
360 for (std::size_t k = 0; k < N; ++k)
361 {
362 eng.run(1);
363 writer.submit(eng, dir / ("snap_" + std::to_string(k) + ".bin"));
364 }
365 writer.flush();
366
367 EXPECT_GT(writer.total_dropped(), 0u)
368 << "Drop_Oldest must drop at least one task under sustained pressure";
369 EXPECT_EQ(writer.total_written() + writer.total_dropped(), N);
370
371 std::filesystem::remove_all(dir);
372}
373
374// =====================================================================
375// Compression ratio exceeds 4× on a sparse binary 1024² grid
376// =====================================================================
377
379{
380 const auto dir = tmp_dir("compress_ratio");
381 const auto raw_path = dir / "raw.bin";
382 const auto cmp_path = dir / "compressed.bin";
383
384 // 1024² sparse binary grid (~1% live cells, the rest are zeros).
385 constexpr ca_size_t side = 1024;
386 std::mt19937 rng(0x600D);
387 std::bernoulli_distribution coin(0.01);
388 Grid g({side, side}, 0);
389 for (ca_size_t i = 0; i < side; ++i)
390 for (ca_size_t j = 0; j < side; ++j)
391 g.set({static_cast<ca_index_t>(i), static_cast<ca_index_t>(j)},
392 coin(rng) ? 1 : 0);
393 Engine eng(std::move(g), make_game_of_life_rule(), Moore<2, 1>{});
394
397 opts.compress = true;
398 opts.level = 6;
400
401 const auto raw_size = byte_count(raw_path);
402 const auto cmp_size = byte_count(cmp_path);
403 ASSERT_GT(raw_size, 0u);
404 ASSERT_GT(cmp_size, 0u);
405 const double ratio = static_cast<double>(raw_size) / static_cast<double>(cmp_size);
406 EXPECT_GT(ratio, 4.0)
407 << "Sparse 1024^2 grid should compress > 4x (raw=" << raw_size
408 << ", compressed=" << cmp_size << ", ratio=" << ratio << ")";
409
410 std::filesystem::remove_all(dir);
411}
412
413// =====================================================================
414// v1 file reader: hand-craft a Phase-15 file and verify inspect()
415// =====================================================================
416
417namespace
418{
419
420// Write a minimal valid v1 file with a freshly-trained type hash. The
421// state payload encodes `cell_count` int32 values matching the engine.
422template <typename Engine>
423void write_v1_file(const std::filesystem::path &path,
424 const Engine &reference,
425 std::uint64_t step_count,
426 const std::vector<std::int32_t> &cells)
427{
428 using namespace Aleph::CA::ca_checkpoint_detail;
429 using Lattice = typename Engine::lattice_type;
430 std::ofstream out(path, std::ios::binary | std::ios::trunc);
431 ASSERT_TRUE(out.good());
432 out.write(magic_bytes.data(), magic_bytes.size());
433 const std::uint32_t v1 = 1;
434 out.write(reinterpret_cast<const char *>(&v1), sizeof(v1));
435 const std::uint64_t th = type_hash<Engine>();
436 out.write(reinterpret_cast<const char *>(&th), sizeof(th));
437 const std::uint32_t st_size = static_cast<std::uint32_t>(sizeof(std::int32_t));
438 out.write(reinterpret_cast<const char *>(&st_size), sizeof(st_size));
439 const std::uint32_t rank = static_cast<std::uint32_t>(Lattice::rank);
440 out.write(reinterpret_cast<const char *>(&rank), sizeof(rank));
441 std::array<std::uint64_t, 4> ext{};
442 const auto &e = reference.frame().extents();
443 for (std::size_t d = 0; d < Lattice::rank; ++d)
444 ext[d] = static_cast<std::uint64_t>(e[d]);
445 for (std::size_t d = 0; d < 4; ++d)
446 out.write(reinterpret_cast<const char *>(&ext[d]), sizeof(std::uint64_t));
447 out.write(reinterpret_cast<const char *>(&step_count), sizeof(step_count));
448 const std::uint8_t has_rng = 0;
449 out.write(reinterpret_cast<const char *>(&has_rng), sizeof(has_rng));
450 const char pad[7] = {0, 0, 0, 0, 0, 0, 0};
451 out.write(pad, sizeof(pad));
452 const std::uint64_t master_seed = 0;
453 out.write(reinterpret_cast<const char *>(&master_seed), sizeof(master_seed));
454 const std::uint64_t cell_count = cells.size();
455 out.write(reinterpret_cast<const char *>(&cell_count), sizeof(cell_count));
456 out.write(reinterpret_cast<const char *>(cells.data()),
457 static_cast<std::streamsize>(cells.size() * sizeof(std::int32_t)));
458 out.flush();
459}
460
461} // namespace
462
464{
465 const auto dir = tmp_dir("v1_legacy");
466 const auto path = dir / "legacy.bin";
467
468 Engine reference(Grid({8, 8}, 0), make_game_of_life_rule(), Moore<2, 1>{});
469 std::vector<std::int32_t> cells(64);
470 for (std::size_t i = 0; i < cells.size(); ++i)
471 cells[i] = static_cast<std::int32_t>((i * 37) % 5);
472
473 write_v1_file(path, reference, /*step=*/3, cells);
474
475 const auto h = inspect_checkpoint(path);
476 EXPECT_EQ(h.format_version, 1u);
477 EXPECT_EQ(h.rank, 2u);
478 EXPECT_EQ(h.cell_count, 64u);
479 EXPECT_EQ(h.step_count, 3u);
480 EXPECT_EQ(h.flags, 0u);
481 EXPECT_EQ(h.payload_size, 64u * sizeof(std::int32_t));
482
483 Engine engine(Grid({8, 8}, 0), make_game_of_life_rule(), Moore<2, 1>{});
485 EXPECT_EQ(tok.header.format_version, 1u);
486 EXPECT_EQ(engine.steps_run(), 3u);
487
488 // Spot-check a couple of cells were restored faithfully.
489 EXPECT_EQ(engine.frame().at({0, 0}), cells[0]);
490 EXPECT_EQ(engine.frame().at({1, 1}), cells[1 * 8 + 1]);
491
492 std::filesystem::remove_all(dir);
493}
494
495// =====================================================================
496// Delta round-trip: save baseline, advance, save delta, apply delta
497// =====================================================================
498
500{
501 const auto dir = tmp_dir("delta");
502 const auto base_path = dir / "base.bin";
503 const auto delta_path = dir / "delta.bin";
504
506 eng.run(5);
508
509 // Capture the baseline payload before advancing — `save_delta`
510 // diff's against this exact buffer.
511 const auto baseline_bytes
513 const auto base_step = static_cast<std::uint64_t>(eng.steps_run());
514
515 // Advance, then store only the diff.
516 eng.run(3);
518 opts.compress = true;
520
521 const auto dh = inspect_checkpoint(delta_path);
523 EXPECT_EQ(dh.delta_base_step, base_step);
524 EXPECT_EQ(dh.step_count, base_step + 3);
525
526 // Rebuild from baseline + delta and compare to the live engine.
527 Engine restored(Grid({20, 20}, 0), make_game_of_life_rule(), Moore<2, 1>{});
529 EXPECT_EQ(restored.steps_run(), base_step);
531 EXPECT_EQ(restored.steps_run(), base_step + 3);
532 EXPECT_TRUE(grids_equal(restored.frame(), eng.frame()));
533
534 // load_checkpoint_into must refuse to consume a delta file.
535 Engine bad(Grid({20, 20}, 0), make_game_of_life_rule(), Moore<2, 1>{});
536 EXPECT_THROW(load_checkpoint_into(bad, delta_path), std::runtime_error);
537
538 std::filesystem::remove_all(dir);
539}
540
541// =====================================================================
542// Hardening against hostile headers (CWE-789 / CWE-190).
543//
544// These reproduce a class of bug surfaced by the Phase-26 checkpoint
545// fuzzer: a header field that drives an allocation must be validated
546// against the file before the allocation happens. Field offsets follow
547// the v2 layout (see header_bytes_v1/v2 in ca-checkpoint.H):
548// magic(8) version(4) type_hash(8) state_size(4) rank(4) extents(32)
549// step(8) has_rng(1) pad(7) master_seed(8) cell_count(8) ...
550// ^offset 84
551// ... flags(4) compression_level(4) payload_size(8) ...
552// ^offset 100
553// =====================================================================
554
555namespace
556{
557
558constexpr std::streamoff kCellCountOffset = 84;
559constexpr std::streamoff kPayloadSizeOffset = 100;
560
561// Overwrite the little-endian uint64 field at `offset` in-place.
562void patch_u64(const std::filesystem::path &path, std::streamoff offset,
563 std::uint64_t value)
564{
565 std::fstream f(path, std::ios::binary | std::ios::in | std::ios::out);
566 ASSERT_TRUE(f.good());
567 f.seekp(offset, std::ios::beg);
568 unsigned char bytes[8];
569 for (int i = 0; i < 8; ++i)
570 bytes[i] = static_cast<unsigned char>((value >> (8 * i)) & 0xFF);
571 f.write(reinterpret_cast<const char *>(bytes), sizeof(bytes));
572 ASSERT_TRUE(f.good());
573}
574
575} // namespace
576
578{
579 const auto dir = tmp_dir("hostile_cellcount");
580 const auto path = dir / "snap.bin";
581
582 Engine eng(Grid({16, 16}, 0), make_game_of_life_rule(), Moore<2, 1>{});
583 eng.run(2);
584 save_checkpoint(eng, path);
585
586 // Claim a cell_count inconsistent with the 16x16 extents.
587 patch_u64(path, kCellCountOffset, 0xFFFFFFFFFFFFFFFFull);
588
589 EXPECT_THROW(inspect_checkpoint(path), std::runtime_error);
590
591 std::filesystem::remove_all(dir);
592}
593
595{
596 const auto dir = tmp_dir("hostile_payload");
597 const auto path = dir / "snap.bin";
598
599 Engine eng(Grid({16, 16}, 0), make_game_of_life_rule(), Moore<2, 1>{});
600 eng.run(2);
601 save_checkpoint(eng, path); // raw, uncompressed: payload_size is meaningful
602
603 // cell_count stays consistent, but the payload claims to be enormous.
604 // The file-size guard in read_raw_payload must reject this before the
605 // multi-exabyte std::vector allocation is attempted.
606 patch_u64(path, kPayloadSizeOffset, 0xFFFFFFFFFFFFFFFFull);
607
608 Engine target(Grid({16, 16}, 0), make_game_of_life_rule(), Moore<2, 1>{});
609 EXPECT_THROW(load_checkpoint_into(target, path), std::runtime_error);
610
611 std::filesystem::remove_all(dir);
612}
long double h
Definition btreepic.C:154
size_t size_t int32_t value
Definition ca-c-api.h:116
size_t size_t int32_t * out
Definition ca-c-api.h:120
Internal compress / decompress helpers used by ca-checkpoint.H (Phase 17).
Binary checkpoint format for Aleph::CA engines (Phase 15 + Phase 17 crash-safe / compress / async wri...
Background writer pumping snapshots onto a dedicated thread.
Lattice that adds boundary-aware access on top of a storage.
static constexpr std::size_t rank
Moore (Chebyshev) neighborhood of radius R in N dimensions.
Synchronous double-buffered engine.
#define TEST(name)
static mt19937 rng
#define N
Definition fib.C:294
size_t blossom_maximum_cardinality_matching(const GT &g, DynDlist< typename GT::Arc * > &matching, SA sa=SA())
Alias of compute_maximum_cardinality_general_matching().
Definition Blossom.H:466
const long double offset[]
Offset values indexed by symbol string length (bounded by MAX_OFFSET_INDEX)
constexpr std::array< char, 8 > magic_bytes
Magic prefix identifying an Aleph CA checkpoint stream.
std::vector< std::uint8_t > snapshot_frame_bytes(const Engine &engine)
Flatten an engine frame into a row-major byte buffer.
constexpr std::uint32_t compressed
Frame payload was written through DEFLATE (miniz).
constexpr std::uint32_t delta
File contains only the cells that changed relative to a baseline.
ca_size_t cell_count(const Lattice &lat) noexcept
Total cell count of a lattice (product of extents).
constexpr Game_Of_Life_Rule make_game_of_life_rule() noexcept
Build the canonical Game of Life rule.
Checkpoint_Header inspect_checkpoint(const std::filesystem::path &path)
Read just the header from a checkpoint file.
Resume_Token apply_delta_checkpoint(Engine &engine, const std::filesystem::path &path)
Apply a delta checkpoint to the engine's current state.
void save_delta_checkpoint(Engine &engine, const std::vector< std::uint8_t > &baseline, const std::uint64_t base_step, const std::filesystem::path &path, const Checkpoint_Options &options={})
Write a delta checkpoint relative to a previous payload.
std::size_t ca_size_t
Unsigned size component used for extents and counts.
Definition ca-traits.H:63
Resume_Token load_checkpoint_into(Engine &engine, const std::filesystem::path &path)
Restore an engine's state in-place from a checkpoint file.
void save_checkpoint(Engine &engine, const std::filesystem::path &path, const Checkpoint_Options &options={})
Write a complete engine snapshot to disk atomically.
Main namespace for Aleph-w library functions.
Definition ah-arena.H:89
STL namespace.
User-tunable knobs for save_checkpoint.
bool compress
Enable DEFLATE compression of the frame payload.
Resume handle returned by load_checkpoint_into.
The lattice wraps around on every axis.
Definition ca-traits.H:124
static long counter
Definition test-splice.C:40
ValueArg< size_t > seed
Definition testHash.C:53
static int * k
static mt19937 engine
Synchronous double-buffered engine for cellular automata.
Cellular automata lattice with pluggable boundary policies.
Neighborhoods catalogue for Aleph::CA.
Rule mechanisms for Aleph::CA.
Reproducible stochastic CA rules (Phase 8).
Dense, contiguous storage for cellular automata cells (1D/2D/3D).