3#include <tess/core/shape.h>
4#include <tess/topology/movement_class.h>
5#include <tess/topology/transition_provider.h>
18enum class TransitionKind : std::uint8_t {
24enum class TransitionAvailability : std::uint8_t {
31template <
typename Cost = std::u
int32_t>
34 std::uint64_t to_index = 0;
36 TransitionKind kind = TransitionKind::Regular;
37 TransitionAvailability availability = TransitionAvailability::Blocked;
38 bool cost_overflow =
false;
43struct RegularTransitionCandidate {
47 std::uint32_t multiplier = 1;
48 std::uint8_t clearance_count = 0;
51template <
typename Shape,
typename Sink>
52constexpr void emit_regular_candidate(Coord3 to, std::uint32_t multiplier,
54 if (contains<Shape>(to)) {
55 sink(RegularTransitionCandidate{.to = to, .multiplier = multiplier});
59template <
typename Shape,
typename Policy,
typename Sink>
60constexpr void emit_diagonal_candidate(Coord3 to, Coord3 clearance_a,
61 Coord3 clearance_b, Sink&& sink) {
62 if (contains<Shape>(to)) {
63 sink(RegularTransitionCandidate{
65 .clearance_a = clearance_a,
66 .clearance_b = clearance_b,
67 .multiplier = Policy::diagonal_step_multiplier,
73template <
typename Shape,
typename Sink>
74constexpr void for_each_orthogonal_face_candidate(Coord3 from,
75 std::uint32_t multiplier,
77 emit_regular_candidate<Shape>(Coord3{from.x + 1, from.y, from.z}, multiplier,
79 emit_regular_candidate<Shape>(Coord3{from.x - 1, from.y, from.z}, multiplier,
81 emit_regular_candidate<Shape>(Coord3{from.x, from.y + 1, from.z}, multiplier,
83 emit_regular_candidate<Shape>(Coord3{from.x, from.y - 1, from.z}, multiplier,
85 emit_regular_candidate<Shape>(Coord3{from.x, from.y, from.z + 1}, multiplier,
87 emit_regular_candidate<Shape>(Coord3{from.x, from.y, from.z - 1}, multiplier,
91template <
typename Shape,
typename Sink>
92constexpr void for_each_hex_candidate(Coord3 from, Sink&& sink) {
99 emit_regular_candidate<Shape>(Coord3{from.x + 1, from.y, from.z}, 1, sink);
100 emit_regular_candidate<Shape>(Coord3{from.x - 1, from.y, from.z}, 1, sink);
101 emit_regular_candidate<Shape>(Coord3{from.x, from.y + 1, from.z}, 1, sink);
102 emit_regular_candidate<Shape>(Coord3{from.x, from.y - 1, from.z}, 1, sink);
103 emit_regular_candidate<Shape>(Coord3{from.x + 1, from.y - 1, from.z}, 1,
105 emit_regular_candidate<Shape>(Coord3{from.x - 1, from.y + 1, from.z}, 1,
109template <
typename Shape,
typename Policy,
typename Sink>
110constexpr void for_each_diagonal_candidate(Coord3 from, Sink&& sink) {
111 for_each_orthogonal_face_candidate<Shape>(from, Policy::cost_scale, sink);
112 if constexpr (!ShapeTraits<Shape>::degenerate_x &&
113 !ShapeTraits<Shape>::degenerate_y) {
114 emit_diagonal_candidate<Shape, Policy>(
115 Coord3{from.x + 1, from.y + 1, from.z},
116 Coord3{from.x + 1, from.y, from.z}, Coord3{from.x, from.y + 1, from.z},
118 emit_diagonal_candidate<Shape, Policy>(
119 Coord3{from.x + 1, from.y - 1, from.z},
120 Coord3{from.x + 1, from.y, from.z}, Coord3{from.x, from.y - 1, from.z},
122 emit_diagonal_candidate<Shape, Policy>(
123 Coord3{from.x - 1, from.y + 1, from.z},
124 Coord3{from.x - 1, from.y, from.z}, Coord3{from.x, from.y + 1, from.z},
126 emit_diagonal_candidate<Shape, Policy>(
127 Coord3{from.x - 1, from.y - 1, from.z},
128 Coord3{from.x - 1, from.y, from.z}, Coord3{from.x, from.y - 1, from.z},
130 }
else if constexpr (!ShapeTraits<Shape>::degenerate_x &&
131 !ShapeTraits<Shape>::degenerate_z) {
132 emit_diagonal_candidate<Shape, Policy>(
133 Coord3{from.x + 1, from.y, from.z + 1},
134 Coord3{from.x + 1, from.y, from.z}, Coord3{from.x, from.y, from.z + 1},
136 emit_diagonal_candidate<Shape, Policy>(
137 Coord3{from.x + 1, from.y, from.z - 1},
138 Coord3{from.x + 1, from.y, from.z}, Coord3{from.x, from.y, from.z - 1},
140 emit_diagonal_candidate<Shape, Policy>(
141 Coord3{from.x - 1, from.y, from.z + 1},
142 Coord3{from.x - 1, from.y, from.z}, Coord3{from.x, from.y, from.z + 1},
144 emit_diagonal_candidate<Shape, Policy>(
145 Coord3{from.x - 1, from.y, from.z - 1},
146 Coord3{from.x - 1, from.y, from.z}, Coord3{from.x, from.y, from.z - 1},
149 emit_diagonal_candidate<Shape, Policy>(
150 Coord3{from.x, from.y + 1, from.z + 1},
151 Coord3{from.x, from.y + 1, from.z}, Coord3{from.x, from.y, from.z + 1},
153 emit_diagonal_candidate<Shape, Policy>(
154 Coord3{from.x, from.y + 1, from.z - 1},
155 Coord3{from.x, from.y + 1, from.z}, Coord3{from.x, from.y, from.z - 1},
157 emit_diagonal_candidate<Shape, Policy>(
158 Coord3{from.x, from.y - 1, from.z + 1},
159 Coord3{from.x, from.y - 1, from.z}, Coord3{from.x, from.y, from.z + 1},
161 emit_diagonal_candidate<Shape, Policy>(
162 Coord3{from.x, from.y - 1, from.z - 1},
163 Coord3{from.x, from.y - 1, from.z}, Coord3{from.x, from.y, from.z - 1},
168template <
typename Shape,
typename Policy,
typename Sink>
169constexpr void for_each_regular_candidate(Coord3 from, Sink&& sink) {
170 static_assert(movement::StepPolicyFor<Policy, Shape>);
175 if (!contains<Shape>(from)) {
178 if constexpr (std::is_same_v<Policy, movement::DefaultSteps>) {
179 if constexpr (std::is_same_v<typename ShapeTraits<Shape>::lattice_type,
180 lattice::Orthogonal>) {
181 for_each_orthogonal_face_candidate<Shape>(from, 1, sink);
183 for_each_hex_candidate<Shape>(from, sink);
186 for_each_diagonal_candidate<Shape, Policy>(from, sink);
190template <
typename Shape>
191[[nodiscard]]
constexpr auto transition_index(Coord3 coord)
noexcept
193 static_assert(ShapeTraits<Shape>::tile_key_bits <= 64,
194 "Resolved transitions require u64 tile keys.");
195 return static_cast<std::uint64_t
>(tile_key<Shape>(coord).value);
198[[nodiscard]]
constexpr auto saturating_u32(UInt128 value)
noexcept
200 constexpr auto max = std::numeric_limits<std::uint32_t>::max();
201 return value.hi != 0 || value.lo > max ? max
202 :
static_cast<std::uint32_t
>(value.lo);
205struct TransitionProbeSink {
206 constexpr void operator()(TransitionProbe<>)
const noexcept {}
210 constexpr void operator()(ChunkKey)
const noexcept {}
213template <
typename Expr,
typename Schema,
typename =
void>
214struct CostExpressionMaximum {
215 static constexpr bool known =
false;
216 static constexpr std::uint32_t value = 0;
219template <
typename Expr,
typename Schema>
220struct CostExpressionMaximum<Expr, Schema,
221 std::void_t<decltype(Expr::maximum_entry_cost)>> {
222 static constexpr bool known =
true;
223 static constexpr std::uint32_t value = Expr::maximum_entry_cost;
226template <
typename Schema>
227struct CostExpressionMaximum<movement::UnitCost, Schema> {
228 static constexpr bool known =
true;
229 static constexpr std::uint32_t value = 1;
232template <std::u
int32_t N,
typename Schema>
233struct CostExpressionMaximum<movement::ConstantCost<N>, Schema> {
234 static constexpr bool known =
true;
235 static constexpr std::uint32_t value = N;
238template <
typename Tag,
typename Schema>
239struct CostExpressionMaximum<movement::FieldCost<Tag>, Schema> {
240 using value_type =
typename Schema::template value_type<Tag>;
241 static_assert(std::is_integral_v<value_type>);
242 static constexpr bool known =
true;
243 static constexpr auto source_max = std::numeric_limits<value_type>::max();
244 static constexpr std::uint32_t value =
245 static_cast<std::uintmax_t
>(source_max) >
246 std::numeric_limits<std::uint32_t>::max()
247 ? std::numeric_limits<std::uint32_t>::max()
248 : static_cast<std::uint32_t>(source_max);
251template <
typename Tag,
typename Set,
typename Clear,
typename Schema>
252struct CostExpressionMaximum<movement::SelectCost<Tag, Set, Clear>, Schema> {
253 using set_max = CostExpressionMaximum<Set, Schema>;
254 using clear_max = CostExpressionMaximum<Clear, Schema>;
255 static constexpr bool known = set_max::known && clear_max::known;
256 static constexpr std::uint32_t value =
257 set_max::value > clear_max::value ? set_max::value : clear_max::value;
262template <
typename Base,
typename Overlay,
typename Schema>
263struct CostExpressionMaximum<movement::OverlayCost<Base, Overlay>, Schema> {
264 using base_max = CostExpressionMaximum<Base, Schema>;
265 using overlay_max = CostExpressionMaximum<Overlay, Schema>;
266 static constexpr bool known = base_max::known && overlay_max::known;
267 static constexpr std::uint32_t value = [] {
268 const auto sum =
static_cast<std::uint64_t
>(base_max::value) +
269 static_cast<std::uint64_t
>(overlay_max::value);
270 constexpr auto ceiling =
271 static_cast<std::uint64_t
>(std::numeric_limits<std::uint32_t>::max());
272 return static_cast<std::uint32_t
>(sum > ceiling ? ceiling : sum);
276template <
typename Class,
typename Schema,
typename =
void>
277struct MovementClassMaximum {
278 static constexpr bool known =
false;
279 static constexpr std::uint32_t value = 0;
282template <
typename Class,
typename Schema>
283struct MovementClassMaximum<Class, Schema,
284 std::void_t<typename Class::cost_expr>>
285 : CostExpressionMaximum<typename Class::cost_expr, Schema> {};
287template <
typename Prov
ider>
288struct IsBuiltInStairProvider : std::false_type {};
290template <
typename Tag>
291struct IsBuiltInStairProvider<StairTransitions<Tag>> : std::true_type {};
293template <
typename Prov
ider>
294inline constexpr bool provider_maximum_known =
295 std::is_same_v<Provider, AdjacentTransitions> ||
296 IsBuiltInStairProvider<Provider>::value ||
297 requires { Provider::maximum_transition_cost; };
299template <
typename Prov
ider>
300[[nodiscard]]
consteval auto provider_maximum() -> std::uint32_t {
301 if constexpr (
requires { Provider::maximum_transition_cost; }) {
302 return Provider::maximum_transition_cost;
311[[nodiscard]]
constexpr auto compact_cost_bound_overflows(
312 UInt128 edges, std::uint32_t maximum_cost,
313 std::uint32_t maximum_step_multiplier)
noexcept ->
bool {
314 if (edges == UInt128{0} || maximum_cost == 0 ||
315 maximum_step_multiplier == 0) {
319 static_cast<std::uint64_t
>(maximum_cost) * maximum_step_multiplier;
320 constexpr auto infinity = std::numeric_limits<std::uint32_t>::max();
321 const auto first_overflowing_edge_count =
322 (
static_cast<std::uint64_t
>(infinity) + factor - 1u) / factor;
323 return edges >= UInt128{first_overflowing_edge_count};
329enum class CostRangeAssessment : std::uint8_t {
336template <
typename World,
typename MovementClass,
338inline constexpr CostRangeAssessment path_cost_range_assessment = [] {
339 using Class = movement::movement_class_of<MovementClass>;
341 detail::MovementClassMaximum<Class, typename World::schema_type>;
342 const auto tile_count =
344 const auto edges = tile_count -
UInt128{1};
346 return CostRangeAssessment::ProvenSafe;
348 if constexpr (!Maximum::known || !detail::provider_maximum_known<Provider>) {
349 return CostRangeAssessment::Unknown;
351 constexpr auto provider_max = detail::provider_maximum<Provider>();
352 constexpr auto maximum_cost =
353 Maximum::value > provider_max ? Maximum::value : provider_max;
354 using Policy = movement::step_policy_of<Class>;
355 constexpr auto maximum_step_multiplier =
356 std::is_same_v<Policy, movement::DefaultSteps>
357 ? movement::DefaultSteps::maximum_step_multiplier
358 : Policy::maximum_step_multiplier;
359 return detail::compact_cost_bound_overflows(edges, maximum_cost,
360 maximum_step_multiplier)
361 ? CostRangeAssessment::PotentialOverflow
362 : CostRangeAssessment::ProvenSafe;
368template <
typename World,
typename MovementClass,
370consteval void require_proven_path_cost_range() {
372 path_cost_range_assessment<World, MovementClass, Provider> ==
373 CostRangeAssessment::ProvenSafe,
374 "Path cost range is not proven safe for the compact uint32 domain.");
378template <
typename World,
typename ClassOrTag,
380class ResolvedTransitionModel {
382 using world_type =
World;
383 using shape_type =
typename World::shape_type;
384 using class_type = movement::movement_class_of<ClassOrTag>;
385 using step_policy = movement::step_policy_of<class_type>;
386 using provider_type = Provider;
387 using cost_type = std::uint32_t;
390 "Movement step policy is invalid for the world shape.");
392 "Resolved exact search requires per-origin provider "
395 constexpr ResolvedTransitionModel() =
default;
396 constexpr explicit ResolvedTransitionModel(Provider provider)
397 : provider_(std::move(provider)) {}
399 static constexpr auto lattice_identity =
400 ShapeTraits<shape_type>::lattice_identity;
401 static constexpr auto lattice_version =
402 ShapeTraits<shape_type>::lattice_version;
403 static constexpr auto step_policy_identity = step_policy::identity;
404 static constexpr std::uint32_t cost_scale = step_policy::cost_scale;
405 static constexpr bool preserves_default_connectivity =
406 std::is_same_v<typename ShapeTraits<shape_type>::lattice_type,
408 std::is_same_v<Provider, AdjacentTransitions>;
409 static constexpr bool has_special_transitions =
410 !std::is_same_v<Provider, AdjacentTransitions>;
412 template <
typename Sink>
413 constexpr void for_each_forward(
const World& world,
Coord3 from,
414 std::uint64_t from_index, Sink&& sink)
const {
416 detail::for_each_regular_candidate<shape_type, step_policy>(
417 from, [&](detail::RegularTransitionCandidate candidate) {
418 emit_candidate(world, candidate, candidate.to, sink);
420 provider_.for_each_forward(
422 emit_special_candidate(world, candidate, candidate.to, sink);
426 template <
typename Sink>
427 constexpr void for_each_reverse(
const World& world,
Coord3 to,
428 std::uint64_t to_index, Sink&& sink)
const {
430 "Reverse fields require per-target provider enumeration.");
436 if (!contains<shape_type>(to)) {
439 const auto origin = world.resolve(to);
440 const auto* origin_page = world.try_chunk(origin.chunk_key);
441 if (origin_page ==
nullptr ||
442 !class_type::passable(*origin_page, origin.local_tile_id)) {
445 detail::for_each_regular_candidate<shape_type, step_policy>(
446 to, [&](detail::RegularTransitionCandidate candidate) {
447 emit_candidate(world, candidate, to, sink);
449 provider_.for_each_reverse(
451 emit_special_candidate(world, candidate, to, sink);
455 template <
typename Sink>
456 constexpr void for_each_dependency_chunk(
const World& world,
Coord3 from,
463 if (!contains<shape_type>(from)) {
466 sink(chunk_key<shape_type>(chunk_coord<shape_type>(from)));
467 detail::for_each_regular_candidate<shape_type, step_policy>(
468 from, [&](detail::RegularTransitionCandidate candidate) {
469 sink(chunk_key<shape_type>(chunk_coord<shape_type>(candidate.to)));
470 if (candidate.clearance_count != 0) {
471 sink(chunk_key<shape_type>(
472 chunk_coord<shape_type>(candidate.clearance_a)));
473 sink(chunk_key<shape_type>(
474 chunk_coord<shape_type>(candidate.clearance_b)));
477 provider_.for_each_forward(
479 if (contains<shape_type>(candidate.to)) {
480 sink(chunk_key<shape_type>(chunk_coord<shape_type>(candidate.to)));
490 detail::for_each_regular_candidate<shape_type, step_policy>(
491 from, [&](detail::RegularTransitionCandidate candidate) {
492 found = found || candidate.to == to;
501 -> TransitionAvailability {
502 auto availability = TransitionAvailability::Blocked;
503 detail::for_each_regular_candidate<shape_type, step_policy>(
504 from, [&](detail::RegularTransitionCandidate candidate) {
505 if (candidate.to == to) {
506 availability = clearance_availability(world, candidate);
512 [[nodiscard]]
static constexpr auto heuristic(
const World&,
Coord3 from,
515 if constexpr (has_special_transitions) {
517 }
else if constexpr (std::is_same_v<step_policy, movement::DefaultSteps>) {
518 if constexpr (std::is_same_v<
519 typename ShapeTraits<shape_type>::lattice_type,
520 lattice::HexAxial>) {
521 const auto distance =
522 hex_distance(to_hex_coord(from), to_hex_coord(goal));
523 return distance > std::numeric_limits<cost_type>::max()
524 ? std::numeric_limits<cost_type>::max()
525 : static_cast<cost_type>(distance);
527 const auto distance = manhattan_distance(from, goal);
528 return distance > std::numeric_limits<cost_type>::max()
529 ? std::numeric_limits<cost_type>::max()
530 : static_cast<cost_type>(distance);
533 return diagonal_heuristic(from, goal);
537 [[nodiscard]]
constexpr auto revision() const noexcept -> std::uint64_t {
538 return detail::transition_provider_revision(provider_);
542 enum class Clearance : std::uint8_t {
548 [[nodiscard]]
static constexpr auto clearance_at(
const World& world,
549 Coord3 coord)
noexcept
551 const auto resolved = world.resolve(coord);
552 const auto* page = world.try_chunk(resolved.chunk_key);
553 if (page ==
nullptr) {
554 return Clearance::Missing;
556 return class_type::passable(*page, resolved.local_tile_id)
558 : Clearance::Blocked;
561 [[nodiscard]]
static constexpr auto clearance_availability(
562 const World& world_value,
563 detail::RegularTransitionCandidate candidate)
noexcept
564 -> TransitionAvailability {
565 if constexpr (std::is_same_v<step_policy, movement::DefaultSteps>) {
566 return TransitionAvailability::Legal;
568 if (candidate.clearance_count == 0) {
569 return TransitionAvailability::Legal;
571 const auto a = clearance_at(world_value, candidate.clearance_a);
572 const auto b = clearance_at(world_value, candidate.clearance_b);
573 if constexpr (step_policy::corner_rule ==
574 movement::CornerRule::RequireBothClear) {
575 if (a == Clearance::Blocked || b == Clearance::Blocked) {
576 return TransitionAvailability::Blocked;
578 return a == Clearance::Missing || b == Clearance::Missing
579 ? TransitionAvailability::MissingTopology
580 : TransitionAvailability::Legal;
582 if (a == Clearance::Clear || b == Clearance::Clear) {
583 return TransitionAvailability::Legal;
585 return a == Clearance::Missing || b == Clearance::Missing
586 ? TransitionAvailability::MissingTopology
587 : TransitionAvailability::Blocked;
592 template <
typename Sink>
593 static constexpr void emit_candidate(
594 const World& world, detail::RegularTransitionCandidate candidate,
595 Coord3 cost_coord, Sink&& sink) {
596 const auto resolved = world.resolve(candidate.to);
597 const auto* page = world.try_chunk(resolved.chunk_key);
598 if (page ==
nullptr) {
599 sink(TransitionProbe<>{
601 .to_index = detail::transition_index<shape_type>(candidate.to),
602 .availability = TransitionAvailability::MissingTopology,
606 if (!class_type::passable(*page, resolved.local_tile_id)) {
609 const auto clearance = clearance_availability(world, candidate);
610 if (clearance == TransitionAvailability::Blocked) {
613 const auto cost_resolved = world.resolve(cost_coord);
614 const auto* cost_page = world.try_chunk(cost_resolved.chunk_key);
615 if (cost_page ==
nullptr) {
616 sink(TransitionProbe<>{
618 .to_index = detail::transition_index<shape_type>(candidate.to),
619 .availability = TransitionAvailability::MissingTopology,
623 const auto entry_cost =
624 class_type::entry_cost(*cost_page, cost_resolved.local_tile_id);
625 if (entry_cost == 0) {
628 const auto precise_cost =
629 UInt128{entry_cost} * UInt128{candidate.multiplier};
630 constexpr auto infinite = std::numeric_limits<std::uint32_t>::max();
631 sink(TransitionProbe<>{
633 .to_index = detail::transition_index<shape_type>(candidate.to),
634 .cost = detail::saturating_u32(precise_cost),
635 .availability = clearance,
636 .cost_overflow = precise_cost.hi != 0 || precise_cost.lo >= infinite,
640 template <
typename Sink>
641 static constexpr void emit_special_candidate(
642 const World& world, SpecialTransitionCandidate candidate,
643 Coord3 forward_destination, Sink&& sink) {
644 if (!contains<shape_type>(candidate.to)) {
647 const auto target_index =
648 detail::transition_index<shape_type>(candidate.to);
649 if (candidate.missing_topology) {
650 sink(TransitionProbe<>{
652 .to_index = target_index,
653 .kind = TransitionKind::Special,
654 .availability = TransitionAvailability::MissingTopology,
658 const auto resolved = world.resolve(candidate.to);
659 const auto* page = world.try_chunk(resolved.chunk_key);
660 if (page ==
nullptr) {
661 sink(TransitionProbe<>{
663 .to_index = target_index,
664 .kind = TransitionKind::Special,
665 .availability = TransitionAvailability::MissingTopology,
669 if (!class_type::passable(*page, resolved.local_tile_id)) {
676 const auto destination = world.resolve(forward_destination);
677 const auto* destination_page = world.try_chunk(destination.chunk_key);
678 if (destination_page ==
nullptr ||
679 class_type::entry_cost(*destination_page, destination.local_tile_id) ==
683 if (candidate.cost == 0) {
686 const auto precise_cost = UInt128{candidate.cost} * UInt128{cost_scale};
687 constexpr auto infinite = std::numeric_limits<std::uint32_t>::max();
688 sink(TransitionProbe<>{
690 .to_index = target_index,
691 .cost = detail::saturating_u32(precise_cost),
692 .kind = TransitionKind::Special,
693 .availability = TransitionAvailability::Legal,
694 .cost_overflow = precise_cost.hi != 0 || precise_cost.lo >= infinite,
698 [[nodiscard]]
static constexpr auto diagonal_heuristic(Coord3 from,
699 Coord3 goal)
noexcept
701 auto a = std::uint64_t{0};
702 auto b = std::uint64_t{0};
703 if constexpr (!ShapeTraits<shape_type>::degenerate_x &&
704 !ShapeTraits<shape_type>::degenerate_y) {
705 a = detail::abs_delta(from.x, goal.x);
706 b = detail::abs_delta(from.y, goal.y);
707 }
else if constexpr (!ShapeTraits<shape_type>::degenerate_x &&
708 !ShapeTraits<shape_type>::degenerate_z) {
709 a = detail::abs_delta(from.x, goal.x);
710 b = detail::abs_delta(from.z, goal.z);
712 a = detail::abs_delta(from.y, goal.y);
713 b = detail::abs_delta(from.z, goal.z);
715 const auto minor = std::min(a, b);
716 const auto major = std::max(a, b);
717 const auto ticks = detail::add(
718 UInt128{minor} * UInt128{step_policy::diagonal_step_multiplier},
719 UInt128{major - minor} * UInt128{step_policy::cost_scale});
720 return detail::saturating_u32(ticks);
723 [[no_unique_address]] Provider provider_{};
727template <
typename Model,
typename World>
729 const Model& model,
const World& world,
Coord3 from, std::uint64_t index) {
730 model.for_each_forward(world, from, index, detail::TransitionProbeSink{});
731 model.for_each_dependency_chunk(world, from, detail::ChunkKeySink{});
732 { model.heuristic(world, from, from) } -> std::convertible_to<std::uint32_t>;
733 { model.revision() } -> std::convertible_to<std::uint64_t>;
737template <
typename Model,
typename World>
740 requires(
const Model& model,
const World& world,
Coord3 to,
741 std::uint64_t index) {
742 model.for_each_reverse(world, to, index, detail::TransitionProbeSink{});
static constexpr auto regular_availability(const World &world, Coord3 from, Coord3 to) noexcept -> TransitionAvailability
Definition transition_model.h:498
static constexpr auto is_regular_candidate(Coord3 from, Coord3 to) noexcept -> bool
Definition transition_model.h:486
Definition transition_model.h:728
Definition transition_provider.h:73
Definition transition_model.h:738
Definition transition_provider.h:86
Definition step_policy.h:91
Supplies no special transitions beyond ordinary face adjacency.
Definition transition_provider.h:132
Definition transition_provider.h:14
Definition transition_model.h:32
Packed 128-bit key storage with a deliberately partial operator set.
Definition uint128.h:34