#include "Extension/Customization/board_material.h" #include #include #include #include namespace { using namespace dingosdk; using namespace dingosdk::board_material; namespace n = addr::native_cosmetics; void check(bool ok, const std::string& message) { if (ok) return; std::cerr << "FAILED: " << message << '\n'; std::exit(1); } // Memory as the game lays a board out, by pages; a read of a page never written fails. struct Memory { std::map> pages; void put_bytes(std::uintptr_t at, const void* data, std::size_t size) { for (std::size_t i = 0; i < size; ++i, ++at) pages[at & ~std::uintptr_t{4095}][at & 4095] = static_cast(data)[i]; } template void put(std::uintptr_t at, const T& value) { put_bytes(at, &value, sizeof(value)); } bool operator()(std::uintptr_t at, void* out, std::size_t size) const { for (std::size_t i = 0; i < size; ++i, ++at) { const auto page = pages.find(at & ~std::uintptr_t{4095}); if (page == pages.end()) return false; static_cast(out)[i] = page->second[at & 4095]; } return true; } }; constexpr std::uintptr_t base = 0x140000000; constexpr std::uint64_t number_key = 0x841c7d74837f1d39ULL, integer_key = 0xb00abec403499469ULL, switch_key = 0xa5f9a6b8098279adULL; struct Board { Memory memory; std::uintptr_t colour_type{}; std::uintptr_t entity = 0x10000000; std::uintptr_t next = 0x20000000; // where the next allocation goes std::uintptr_t grip{}, deck{}, trucks{}; std::uintptr_t deck_item{}; std::map grip_nodes; std::uintptr_t take(std::size_t size) { const auto at = next; next += (size + 0xfff) & ~std::size_t{0xfff}; std::vector zero(size); memory.put_bytes(at, zero.data(), zero.size()); return at; } // A type object and its information, in the game's image as the real ones are. std::uintptr_t type(std::uintptr_t object, std::uint32_t hash, std::uint16_t size) { const auto info = object + 0x100; memory.put(base + object, base + info); memory.put(base + info, hash); memory.put(base + info + 6, size); return base + object; } struct Value { std::uint64_t key; std::uintptr_t type; std::uint16_t size; std::array raw{}; std::uint32_t flags = 3; }; // What the engine's typed setter does with a key a material does not hold: a node of its own, // linked into the material's map (developer_board.cpp, the native set_shader_parameter). std::uintptr_t add(std::uintptr_t material, std::uint64_t key, std::uint16_t identity, const Colour& value) { std::uintptr_t buckets{}, node{}; std::uint32_t count{}; memory(material + n::material_parameters, &buckets, sizeof(buckets)); memory(material + n::material_parameter_count, &count, sizeof(count)); const auto added = take(0x48); memory.put(added, developer_hoodie_detail::ParameterKey{identity, 12, 1, 1, key}); std::array raw{}; std::memcpy(raw.data(), value.data(), sizeof(value)); memory.put(added + n::parameter_value, raw); memory.put(added + n::parameter_type, colour_type); memory.put(added + n::parameter_flags, 3); memory(buckets, &node, sizeof(node)); memory.put(added + n::parameter_next, node); // first in its bucket memory.put(buckets, added); memory.put(material + n::material_parameter_count, count + 1); return added; } std::uintptr_t material(const std::vector& values, std::map* nodes = nullptr) { const auto at = take(0x300), buckets = take(16); memory.put(at + n::material_parameters, buckets); memory.put(at + n::material_parameter_buckets, 1); memory.put(at + n::material_parameter_count, static_cast(values.size())); memory.put(buckets + 8, 0xdead0000); // the map's end std::uintptr_t previous{}; for (const auto& v : values) { const auto node = take(0x48); memory.put(node, developer_hoodie_detail::ParameterKey{0x1234, v.size, 1, 1, v.key}); memory.put(node + n::parameter_value, v.raw); memory.put(node + n::parameter_type, v.type); memory.put(node + n::parameter_flags, v.flags); memory.put(previous ? previous + n::parameter_next : buckets, node); previous = node; if (nodes) (*nodes)[v.key] = node; } return at; } std::uintptr_t item(std::uintptr_t appearance, std::string_view slot, const std::vector>& materials) { const auto at = take(0x200), name = take(256), buckets = take(16); memory.put_bytes(name, slot.data(), slot.size()); memory.put(at + 0x30, name); memory.put(at + 0x48, appearance + 0x40); memory.put(at + 0xb0, 2); memory.put(at + 0x1f0, buckets); memory.put(at + 0x1f8, 1); memory.put(at + 0x1fc, static_cast(materials.size())); memory.put(buckets + 8, 0xdead0000); std::uintptr_t previous{}; for (const auto& [material_name, material] : materials) { const auto node = take(0x30); memory.put(node, developer_hoodie_detail::material_hash(material_name)); memory.put(node + 8, material); memory.put(previous ? previous + 0x28 : buckets, node); previous = node; } return at; } // `declared`: the deck as the Example declared-input deck's material holds it live: integers and switches only. explicit Board(bool declared = false) { const auto colour = type(0x7780c50, n::color_rgb_type_hash, 12), number = type(0x765db28, n::float_type_hash, 4), integer = type(0x765da68, n::integer_type_hash, 4), toggle = type(0x7780f50, n::boolean_type_hash, 4); colour_type = colour; const auto bits = [](float a, float b = 0, float c = 0) { std::array raw{}; const std::array f{a, b, c}; std::memcpy(raw.data(), f.data(), sizeof(f)); return raw; }; grip = material({{graphic_colour, colour, 12, bits(.2f, .3f, .4f)}, {number_key, number, 4, bits(1)}, {integer_key, integer, 4, {13}}, {switch_key, toggle, 4, {1}}, {0x1111, colour, 12, bits(.5f, .5f, .5f), 0}}, // not live: bit 0 of its flags clear &grip_nodes); if (declared) { std::vector selectors; for (std::uint64_t k = 1; k <= 7; ++k) selectors.push_back({0x5000 + k, k % 2 ? integer : toggle, 4, {static_cast(k)}}); deck = material(selectors); } else { deck = material({{base_colour, colour, 12, bits(.5f, .5f, .5f)}}); } trucks = material({{base_colour, colour, 12, bits(.1f, .1f, .1f)}}); const auto collection = take(0x80), cosmetic = take(0x200), appearance = take(0x100), list = take(24); memory.put(entity, base + addr::engine::board_entity_vtable); memory.put(entity + 0x70, collection); memory.put(collection, entity); memory.put(collection + 8, 2); memory.put(collection + 0x20, cosmetic); memory.put(collection + 0x40, appearance); memory.put(cosmetic, base + n::cosmetic_component_vtable); memory.put(cosmetic + 0x18, collection); memory.put(appearance, base + addr::engine::skater_appearance_vtable); memory.put(appearance + 0x18, collection); deck_item = item(appearance, developer_board_detail::slots[0], {{"Deck_Mat", deck}, {"DeckTop_Mat", grip}}); memory.put(list, deck_item); memory.put(list + 8, item(appearance, developer_board_detail::slots[1], {{"Truck_Mat", trucks}})); memory.put(appearance + 0x90, list); memory.put(appearance + 0x98, list + 16); memory.put(appearance + 0xa0, list + 24); } detail::Found walk() { return detail::walk(memory, base, entity); } Colour colour_at(std::uintptr_t node) { Colour c{}; memory(node + n::parameter_value, c.data(), sizeof(c)); return c; } }; const detail::Material* part_of(const detail::Found& found, Part part) { for (const auto& m : found.materials) if (m.part == part) return &m; return nullptr; } void walks_the_board() { Board board; const auto found = board.walk(); check(!found.pending && found.materials.size() == 3, "The board's three materials are found"); const auto* grip = part_of(found, Part::grip); const auto* deck = part_of(found, Part::deck); check(grip && deck && part_of(found, Part::trucks) && !part_of(found, Part::wheels), "each as its part, and no wheels without their slot"); check(grip->address == board.grip && grip->item == board.deck_item && grip->nodes.size() == 5, "The grip is the deck slot's DeckTop_Mat"); const auto& p = grip->nodes; check(p[0].parameter.kind == Kind::colour && p[0].parameter.key == graphic_colour && p[0].parameter.value == Colour{.2f, .3f, .4f}, "A colour reads as one"); check(p[1].parameter.kind == Kind::number && p[1].parameter.value[0] == 1, "a number as one"); check(p[2].parameter.kind == Kind::integer && p[2].parameter.integer == 13, "an integer as one"); check(p[3].parameter.kind == Kind::toggle && p[3].parameter.integer == 1, "a switch as one"); check(p[4].parameter.kind == Kind::other, "and a value not live is of no kind plugins set"); } void refuses_what_picks_the_look() { Board board; const auto found = board.walk(); Kind kind{}; check(detail::refusal(found, Part::grip, graphic_colour, {1, 0, 0}, true, kind).empty() && kind == Kind::colour, "A colour may be held"); check(detail::refusal(found, Part::grip, number_key, {-3, 0, 0}, false, kind).empty() && kind == Kind::number, "and a number"); check(detail::refusal(found, Part::grip, integer_key, {13, 0, 0}, false, kind).find("only read") != std::string::npos, "An integer, which picks how the board is drawn, is not"); check(detail::refusal(found, Part::grip, switch_key, {0, 0, 0}, false, kind).find("only read") != std::string::npos, "nor a switch"); check(!detail::refusal(found, Part::grip, 0x1111, {.5f, .5f, .5f}, true, kind).empty(), "nor a value that is not live"); check(!detail::refusal(found, Part::grip, graphic_colour, {1.5f, 0, 0}, true, kind).empty(), "A colour is 0 to 1"); check(!detail::refusal(found, Part::grip, graphic_colour, {1, 0, 0}, false, kind).empty(), "and a number is not a colour"); check(!detail::refusal(found, Part::grip, number_key, {1000, 0, 0}, false, kind).empty(), "A number has bounds"); check(detail::refusal(found, Part::wheels, base_colour, {1, 0, 0}, true, kind).find("no parameter") != std::string::npos, "A part without the key says so"); } void holds_and_puts_back() { Board board; std::vector saved; std::vector holds{{"glow", Part::grip, graphic_colour, Kind::colour, {1, 0, 0}}}; int writes{}, publishes{}; auto write = [&](std::uintptr_t material, std::uintptr_t node, Kind kind, const Colour& value) { ++writes; board.memory.put_bytes(node + n::parameter_value, value.data(), kind == Kind::colour ? sizeof(Colour) : sizeof(float)); board.memory.put(material + n::material_changed, 1); return true; }; auto publish = [&](std::uintptr_t item) { check(item == board.deck_item, "The grip's item publishes its materials"); ++publishes; }; const auto tick = [&] { const auto found = board.walk(); detail::apply(saved, holds, found, write, publish); }; const auto node = board.grip_nodes[graphic_colour]; tick(); check(board.colour_at(node) == Colour{1, 0, 0} && writes == 1 && publishes == 1, "A held colour is written and published"); std::uint8_t changed{}; board.memory(board.grip + n::material_changed, &changed, 1); check(changed == 1, "and its material marked changed"); for (int i = 0; i < 6; ++i) tick(); check(writes == 1 && publishes == 1 + developer_hoodie_detail::settle_publishes, "A colour standing still is published until it shows, then left"); holds[0].value = {0, 1, 0}; tick(); check(board.colour_at(node) == Colour{0, 1, 0} && writes == 2, "A new value is written"); // The game sets its own while it is held: held again. board.memory.put(node + n::parameter_value, Colour{.7f, .7f, .7f}); tick(); check(board.colour_at(node) == Colour{0, 1, 0} && writes == 3, "What is held wins over a change"); holds.clear(); publishes = 0; for (int i = 0; i < 8; ++i) tick(); check(board.colour_at(node) == Colour{.7f, .7f, .7f} && writes == 4, "Let go, it goes back to the board's own (the game's latest)"); check(saved.empty() && publishes == 1 + developer_hoodie_detail::settle_publishes, "and is published until that shows"); // A number, let go after the game changed it: left as the game has it. holds = {{"glow", Part::grip, number_key, Kind::number, {4, 0, 0}}}; tick(); float number{}; board.memory(board.grip_nodes[number_key] + n::parameter_value, &number, 4); check(number == 4, "A held number is written"); holds.clear(); board.memory.put(board.grip_nodes[number_key] + n::parameter_value, 9.0f); tick(); board.memory(board.grip_nodes[number_key] + n::parameter_value, &number, 4); check(number == 9 && saved.empty(), "and one the game changed is not put back"); // A recipe on its way: nothing is written. holds = {{"glow", Part::grip, graphic_colour, Kind::colour, {0, 0, 1}}}; auto found = board.walk(); found.pending = true; const auto before = writes; detail::apply(saved, holds, found, write, publish); check(writes == before, "Nothing is written while the board is being dressed"); } // ---- shader inputs a mod declares for its own deck (reskate-materials.json) --------------------------------- // The Example declared-input deck's eight inputs (parameter-contract.json): colours its compiled shader reads, // in no live map until something sets them. struct Liquid { std::uint64_t key; std::uint16_t identity; }; constexpr Liquid liquid[] = { {6569911127076434714ULL, 46697}, {5869417437401782227ULL, 45276}, {6633899794922297581ULL, 59994}, {6507148046963062431ULL, 65135}, {6339966355936362695ULL, 20838}, {6498503899632300675ULL, 22501}, {6196286651448922263ULL, 47378}, {6344408684715011858ULL, 17465}}; constexpr std::uint64_t declared_number = 0x7777777777777777ULL; std::vector declared_declarations() { mods::MaterialDeclaration d{"Example", "deck", {"Example_Deck"}, {}}; for (const auto& l : liquid) d.parameters.push_back({l.key, l.identity, true, {.5f, .5f, .5f}}); d.parameters.push_back({declared_number, 0x4242, false, {.25f, 0, 0}}); return {d}; } auto never_sold = [](std::string_view) { return false; }; detail::Found declared_found(Board& board) { auto found = board.walk(); found.declared = detail::declared_for(declared_declarations(), Part::deck, "example_deck", never_sold, "Example"); return found; } void declarations_follow_the_equipped_item() { const auto all = declared_declarations(); const auto deck = detail::declared_for(all, Part::deck, "EXAMPLE_DECK", never_sold, "Example"); check(deck.size() == 9 && deck[0].key == liquid[0].key && deck[0].identity == liquid[0].identity && deck[0].kind == Kind::colour && deck[0].fallback == Colour{.5f, .5f, .5f} && deck[8].kind == Kind::number, "The equipped item's declarations apply (its key in any case)"); check(detail::declared_for(all, Part::deck, "Some_Other_Deck", never_sold, "Example").empty(), "another deck's do not"); check(detail::declared_for(all, Part::grip, "Example_Deck", never_sold, "Example").empty(), "nor another part's"); check(detail::declared_for(all, Part::deck, "", never_sold, "Example").empty(), "nor an empty slot's"); auto sold = [](std::string_view) { return true; }; check(detail::declared_for(all, Part::deck, "Example_Deck", sold, "Example").empty(), "An item the game sells (or one it cannot tell) takes none"); // Two mods giving one key differently: which shader is right cannot be told, so neither. auto both = all; both.push_back({"Other", "deck", {"Example_Deck"}, {{liquid[0].key, 1, true, {0, 0, 0}}, {liquid[1].key, liquid[1].identity, true, {.5f, .5f, .5f}}}}); check(detail::declared_for(all,Part::deck,"Example_Deck",never_sold,"Other").empty(),"Other mod cannot declare this item"); check(detail::declared_for(all,Part::deck,"Example_Deck",never_sold,"").empty(),"Unknown ownership is refused"); const auto merged = detail::declared_for(both, Part::deck, "Example_Deck", never_sold, "Example"); check(std::any_of(merged.begin(), merged.end(), [](const detail::Declared& d) { return d.key == liquid[0].key; }) && std::count_if(merged.begin(), merged.end(), [](const detail::Declared& d) { return d.key == liquid[1].key; }) == 1, "Another mod cannot suppress the owner's declarations"); } void lists_and_refuses_declared() { Board board(true); auto found = declared_found(board); const auto listed = detail::listed(found, Part::deck); check(listed.size() == 7 + 9, "The deck lists its seven live selectors and its nine declared inputs"); check(std::all_of(listed.begin(), listed.begin() + 7, [](const Parameter& p) { return !p.inherited && (p.kind == Kind::integer || p.kind == Kind::toggle); }), "the selectors as the material holds them"); check(std::all_of(listed.begin() + 7, listed.end(), [](const Parameter& p) { return p.inherited; }) && listed[7].value == Colour{.5f, .5f, .5f}, "the declared ones inherited, at their default"); check(detail::listed(board.walk(), Part::deck).size() == 7, "Without the declaration only the live ones show"); Kind kind{}; check(detail::refusal(found, Part::deck, liquid[0].key, {.1f, .2f, .3f}, true, kind).empty() && kind == Kind::colour, "A declared colour the material does not hold may be held"); check(detail::refusal(board.walk(), Part::deck, liquid[0].key, {.1f, .2f, .3f}, true, kind).find("no parameter") != std::string::npos, "but not on a deck that does not declare it"); check(!detail::refusal(found, Part::deck, liquid[0].key, {.1f, .2f, .3f}, false, kind).empty(), "A declared colour is not a number"); check(!detail::refusal(found, Part::deck, liquid[0].key, {1.5f, 0, 0}, true, kind).empty(), "and its channels are 0 to 1"); check(detail::refusal(found, Part::deck, declared_number, {.5f, 0, 0}, false, kind).find("only a declared colour") != std::string::npos, "A declared number with no live value cannot be added (its type is not known to this build)"); check(detail::refusal(found, Part::deck, 0x5001, {1, 0, 0}, false, kind).find("only read") != std::string::npos, "A live integer stays read-only"); // A declaration naming a key the material holds as an integer or a switch changes nothing. auto clash = found; clash.declared.push_back({Part::deck, 0x5002, 0x1234, Kind::colour, {0, 0, 0}}); check(detail::refusal(clash, Part::deck, 0x5002, {1, 0, 0}, true, kind).find("not as its mod declares") != std::string::npos, "A switch a mod declares as a colour is refused"); // A live colour whose identity is not the declared one is not the declared parameter. Board ordinary; auto other = ordinary.walk(); other.declared.push_back({Part::grip, graphic_colour, 0x9999, Kind::colour, {.5f, .5f, .5f}}); check(detail::refusal(other, Part::grip, graphic_colour, {1, 0, 0}, true, kind).find("not as its mod declares") != std::string::npos, "and so is a live colour of another identity"); } void owners_and_limits() { Board board(true); const auto found = declared_found(board); std::vector holds; check(detail::take(holds, found, "liquid", Part::deck, liquid[0].key, {.1f, .2f, .3f}, true).empty() && holds.size() == 1 && holds[0].declared && holds[0].kind == Kind::colour, "A plugin takes a declared colour"); check(detail::take(holds, found, "other", Part::deck, liquid[0].key, {1, 1, 1}, true).find("liquid holds") != std::string::npos, "another plugin cannot take it from it"); check(detail::take(holds, found, "liquid", Part::deck, liquid[0].key, {.4f, .5f, .6f}, true).empty() && holds.size() == 1 && holds[0].value == Colour{.4f, .5f, .6f}, "its owner sets it again"); check(!detail::take(holds, found, "liquid", Part::deck, 0x5001, {1, 0, 0}, false).empty() && holds.size() == 1, "A refusal holds nothing"); for (std::size_t i = 1; i < most_held; ++i) holds.push_back({"liquid", Part::grip, 0x9000 + i, Kind::colour, {}, false}); check(detail::take(holds, found, "liquid", Part::deck, liquid[1].key, {0, 0, 0}, true).find("at most") != std::string::npos, "A plugin holds at most 32, declared ones included"); check(detail::take(holds, found, "other", Part::deck, liquid[1].key, {0, 0, 0}, true).empty(), "another has its own 32"); detail::release(holds, "liquid", Part::grip, std::nullopt); check(holds.size() == 2, "Letting go of a part lets go of the plugin's holds there only"); detail::release(holds, "liquid", std::nullopt, std::nullopt); check(holds.size() == 1 && holds[0].owner == "other", "and letting go of all (an unload) leaves the others' alone"); } void adds_and_puts_back_declared() { Board board(true); std::vector saved; std::vector holds; int writes{}, publishes{}, inserts{}; std::map added; auto write = [&](std::uintptr_t material, std::uintptr_t node, Kind kind, const Colour& value) { check(material == board.deck, "Only the deck is written"); ++writes; board.memory.put_bytes(node + n::parameter_value, value.data(), kind == Kind::colour ? sizeof(Colour) : sizeof(float)); return true; }; auto publish = [&](std::uintptr_t item) { check(item == board.deck_item, "The deck's item publishes"); ++publishes; }; auto insert = [&](std::uintptr_t material, const detail::Declared& d, const Colour& value) { check(material == board.deck && d.part == Part::deck && d.kind == Kind::colour, "A declared colour is added to the deck's material"); ++inserts; added[d.key] = board.add(material, d.key, d.identity, value); return true; }; const auto found = [&] { return declared_found(board); }; const auto tick = [&] { detail::apply(saved, holds, found(), write, publish, insert); }; // The liquid controller's eight colours, all held at once. for (std::size_t i = 0; i < 8; ++i) check(detail::take(holds, found(), "liquid", Part::deck, liquid[i].key, {.1f * i, .2f, .3f}, true).empty(), "each liquid colour is taken"); tick(); check(inserts == 8 && writes == 0 && publishes == 1, "The eight are added by the engine's setter, and the deck published once"); for (std::size_t i = 0; i < 8; ++i) check(board.colour_at(added[liquid[i].key]) == Colour{.1f * i, .2f, .3f}, "at the held value"); const auto walked = board.walk(); const auto* deck = part_of(walked, Part::deck); check(deck && deck->nodes.size() == 15 && std::count_if(deck->nodes.begin(), deck->nodes.end(), [&](const detail::Node& node) { return node.parameter.kind == Kind::colour && node.identity == liquid[0].identity; }) == 1, "and then the material holds them live, with their identities"); for (int i = 0; i < 6; ++i) tick(); check(inserts == 8 && writes == 0, "Once added they are not added again"); // About 30 times a second, the controller's new values: written in place. holds[0].value = {.9f, .8f, .7f}; tick(); check(inserts == 8 && writes == 1 && board.colour_at(added[liquid[0].key]) == Colour{.9f, .8f, .7f}, "A new value is written in place"); // Let go (clear_parameter, or the plugin unloading): each goes back to its declared default. detail::release(holds, "liquid", std::nullopt, std::nullopt); for (int i = 0; i < 8; ++i) tick(); for (std::size_t i = 0; i < 8; ++i) check(board.colour_at(added[liquid[i].key]) == Colour{.5f, .5f, .5f}, "Let go, a declared colour goes back to its shader's default"); check(saved.empty(), "and nothing is kept once that shows"); const auto after = board.walk(); check(part_of(after, Part::deck)->nodes.size() == 15, "What was added stays (the renderer copies it), at its default"); check(std::all_of(part_of(after, Part::deck)->nodes.begin(), part_of(after, Part::deck)->nodes.begin() + 15, [](const detail::Node& node) { return node.parameter.kind != Kind::integer || node.parameter.integer != 0; }), "and the selectors were never touched"); } void a_change_of_deck_lets_go() { Board board(true); std::vector saved; std::vector holds; std::uintptr_t node{}; int inserts{}; auto write = [&](std::uintptr_t, std::uintptr_t at, Kind, const Colour& value) { board.memory.put_bytes(at + n::parameter_value, value.data(), sizeof(Colour)); return true; }; auto publish = [](std::uintptr_t) {}; auto insert = [&](std::uintptr_t material, const detail::Declared& d, const Colour& value) { ++inserts; node = board.add(material, d.key, d.identity, value); return true; }; check(detail::take(holds, declared_found(board), "liquid", Part::deck, liquid[0].key, {1, 0, 0}, true).empty(), "taken"); detail::apply(saved, holds, declared_found(board), write, publish, insert); check(inserts == 1 && board.colour_at(node) == Colour{1, 0, 0}, "added on the declared-input deck"); // The deck is changed for one that declares nothing: the walk finds no declaration. auto changed = board.walk(); check(detail::drop_undeclared(holds, changed) == 1 && holds.empty(), "The hold is let go when its deck no longer declares it"); for (int i = 0; i < 8; ++i) detail::apply(saved, holds, changed, write, publish, insert); check(inserts == 1 && board.colour_at(node) == Colour{.5f, .5f, .5f} && saved.empty(), "what it added goes back, and nothing more is added"); // A hold kept (taken while declared) is never written to a deck that does not declare it. holds = {{"liquid", Part::deck, liquid[1].key, Kind::colour, {0, 1, 0}, true}}; detail::apply(saved, holds, changed, write, publish, insert); check(inserts == 1 && saved.empty(), "A declared hold is not carried onto an incompatible deck"); // A board being dressed: nothing is let go or written while the recipe is on its way. changed.declarations_pending[0]=true; check(detail::drop_undeclared(holds,changed)==0 && holds.size()==1,"Rebuilding materials retain declared holds while settling"); changed.pending = true; check(detail::drop_undeclared(holds, changed) == 0 && holds.size() == 1, "nor let go while the board is being dressed"); // Back on the declared-input deck it applies again. detail::apply(saved, holds, declared_found(board), write, publish, insert); check(inserts == 2, "and is added again once the declaring deck is back"); } void declarations_wait_for_the_board_to_settle() { detail::Gate gate; check(!detail::settled(gate, "Example_Deck", 0x1000, 0), "A newly equipped item does not apply at once"); check(!detail::settled(gate, "Example_Deck", 0x1000, detail::settle_ms - 1), "nor before it has settled"); check(detail::settled(gate, "Example_Deck", 0x1000, detail::settle_ms), "then it does"); check(!detail::settled(gate, "Example_Deck", 0x2000, detail::settle_ms + 5), "A new material under it starts again"); check(!detail::settled(gate, "Other_Deck", 0x2000, 2 * detail::settle_ms + 5), "as does another item"); check(!detail::settled(gate, "Other_Deck", 0, 9 * detail::settle_ms), "and no material never settles"); } } // namespace int main() { walks_the_board(); refuses_what_picks_the_look(); holds_and_puts_back(); declarations_follow_the_equipped_item(); lists_and_refuses_declared(); owners_and_limits(); adds_and_puts_back_declared(); a_change_of_deck_lets_go(); declarations_wait_for_the_board_to_settle(); std::cout << "board material tests passed\n"; }