Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
6e576fd
Initial plan
Copilot Jan 8, 2026
e363b7a
Refactor: Move AI sources to game/modes/ai/ and extract static functi…
Copilot Jan 8, 2026
2883f93
Add fat_line_of_sight, find_closest_unoccupied_cell, and random walka…
Copilot Jan 8, 2026
fc1e0fc
Add AI pathfinding module, sound_cue_message for footsteps, and foots…
Copilot Jan 8, 2026
72291f0
Add bomb pathfinding, editor debug pathfinding spawn, and test_mode A…
Copilot Jan 8, 2026
7dbe9ac
Fix code review issues: FAT_LOS_WIDTH and pointer syntax
Copilot Jan 8, 2026
1ad24b4
Address feedback: add cell helpers, refactor BFS, use vector arithmet…
Copilot Jan 8, 2026
70bc519
Address feedback: remove fat_line_of_sight, refactor pathfinding, use…
Copilot Jan 8, 2026
187c199
Replace magic numbers with named constants
Copilot Jan 8, 2026
9f1b003
Refactor: pathfinding_progress struct, bfs_full rename, simplify port…
Copilot Jan 8, 2026
e85b87b
Add pathfinding_graph_view to consolidate repeated lambdas
Copilot Jan 8, 2026
0a87a75
Fix documentation for is_cell_target_portal and bfs_full
Copilot Jan 8, 2026
09bf43a
Refactor: pathfinding_progress uses non-optional path, rename to find…
Copilot Jan 8, 2026
802863e
cant comment in introspected
geneotech Jan 8, 2026
e4dc22a
remove size check
geneotech Jan 8, 2026
84ea412
class -> struct
geneotech Jan 9, 2026
b2edbb0
constexpr constructor
geneotech Jan 9, 2026
eeb1110
Simplify find_closest_unoccupied_cell BFS, add island_id_type, remove…
Copilot Jan 9, 2026
d53bf66
Integrate pathfinding into arena_mode_ai, simplify for_each_neighbor,…
Copilot Jan 9, 2026
7274556
fix compiler errors
geneotech Jan 9, 2026
5c44443
fix the includes finally
geneotech Jan 9, 2026
7886dd1
errata
geneotech Jan 10, 2026
9623181
Extract navigate_pathfinding.hpp as reusable task for test_mode and a…
Copilot Jan 10, 2026
ea616d1
fix nav filename, set crosshair
geneotech Jan 10, 2026
f1efd1e
remove legacy pathfinding structs
geneotech Jan 10, 2026
6705eb3
Add navmesh_cell_id, fix deviation logic, clear pathfinding on telepo…
Copilot Jan 10, 2026
3bcde2f
agents notices
geneotech Jan 10, 2026
fdbe453
errata
geneotech Jan 10, 2026
89f8afa
errata
geneotech Jan 10, 2026
c70c931
Refactor check_path_deviation, add crosshair snapping during pathfind…
Copilot Jan 10, 2026
eaaeeef
fix timing in editor_setup.h
geneotech Jan 10, 2026
731583f
errata
geneotech Jan 10, 2026
0222ae7
Fix rerouting to only target unoccupied/portal cells, trim rerouting …
Copilot Jan 10, 2026
cf2887e
errata
geneotech Jan 10, 2026
e7a0068
dont navigate when inert
geneotech Jan 10, 2026
cbd0130
Rename get_pathfinding_movement_direction to get_pathfinding_crosshai…
Copilot Jan 10, 2026
d80d30e
Add DEBUG_LOGIC_STEP_RECTS for drawing cell AABBs in debug_draw_pathf…
Copilot Jan 10, 2026
55f1a53
Revert to get_pathfinding_movement_direction signature with crosshair…
Copilot Jan 10, 2026
f225f57
compilation fix
geneotech Jan 10, 2026
9bc473a
ease to next
geneotech Jan 10, 2026
94f7296
Use augs::make_rect_points for debug_rect constructor, simplify cell …
Copilot Jan 10, 2026
8d58210
Add stuck detection: rotate crosshair 90 degrees every 2 seconds when…
Copilot Jan 10, 2026
210155c
fix drawing, handle portal inertia
geneotech Jan 10, 2026
08afd4f
compilation fixes
geneotech Jan 10, 2026
71b9d73
ease the rotation over half a second
geneotech Jan 10, 2026
a97daae
fix bomb pathfinding
geneotech Jan 10, 2026
005ae54
small struct refactor [ci skip]
geneotech Jan 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@
abc == 2 ?
something :
else
;
; // take care to always have the ; a separate line!!!

/*
For multi-line boolean expressions and assignments,
Expand All @@ -96,12 +96,20 @@
if (long_clause
&& other_long_clause
&& another_long_clause
) {
) { // take care to always have these two characters in a separate line!!!

}
else {

}

/*
Long initialization:
*/
pathfinding.rerouting = pathfinding_progress {
std::move(*new_rerouting),
0
};
```

- If the initial variable value depends on several conditions, you can sometimes employ this trick to make the variable const:
Expand Down Expand Up @@ -293,6 +301,8 @@

- When implementing new functionality in the `augs/` folder, always put it in the `augs::` namespace.

- Separate generic algorithmic logic (BFS, A*, sorting, etc.) into `augs/algorithm/` headers. Game-specific code should call these generic functions with appropriate parameters. This keeps algorithms reusable and testable independent of game code.

## Common vec2 and ltrb functions (from augs/math/vec2.h and augs/math/rects.h)

- `float vec2::length()` - get the length of a vector
Expand Down
15 changes: 1 addition & 14 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,6 @@ set(HYPERSOMNIA_BOX2D_CPPS
# Test scene sources whose inclusion is conditional on the value of BUILD_TEST_SCENES.

set(HYPERSOMNIA_TEST_SCENES_CPPS
"src/test_scenes/ingredients/artificial_life.cpp"
"src/test_scenes/ingredients/backpack.cpp"
"src/test_scenes/ingredients/car.cpp"
"src/test_scenes/ingredients/guns.cpp"
Expand Down Expand Up @@ -704,7 +703,6 @@ set(HYPERSOMNIA_CODEBASE_CPPS
"src/game/stateless_systems/melee_system.cpp"
"src/game/stateless_systems/particles_existence_system.cpp"
"src/game/stateless_systems/sound_existence_system.cpp"
"src/game/stateless_systems/behaviour_tree_system.cpp"
"src/game/stateless_systems/car_system.cpp"
"src/game/stateless_systems/crosshair_system.cpp"
"src/game/stateless_systems/missile_system.cpp"
Expand All @@ -716,7 +714,6 @@ set(HYPERSOMNIA_CODEBASE_CPPS
"src/game/stateless_systems/intent_contextualization_system.cpp"
"src/game/stateless_systems/item_system.cpp"
"src/game/stateless_systems/movement_system.cpp"
"src/game/stateless_systems/pathfinding_system.cpp"
"src/game/stateless_systems/sentience_system.cpp"
"src/game/stateless_systems/trace_system.cpp"
"src/game/stateless_systems/visibility_system.cpp"
Expand All @@ -737,7 +734,6 @@ set(HYPERSOMNIA_CODEBASE_CPPS
"src/augs/string/typesafe_sprintf.cpp"
"src/augs/string/typesafe_sscanf.cpp"
"src/game/assets/animation.cpp"
"src/game/assets/behaviour_tree.cpp"
"src/game/assets/physical_material.cpp"
"src/game/components/flags_component.cpp"
"src/game/components/light_component.cpp"
Expand All @@ -757,7 +753,6 @@ set(HYPERSOMNIA_CODEBASE_CPPS
"src/game/enums/attitude_type.cpp"
"src/game/enums/item_category.cpp"
"src/game/enums/slot_physical_behaviour.cpp"
"src/game/detail/ai/create_standard_behaviour_trees.cpp"
"src/game/inferred_caches/physics_world_cache.cpp"
"src/game/inferred_caches/tree_of_npo_cache.cpp"
"src/game/other_unit_tests.cpp"
Expand All @@ -770,16 +765,8 @@ set(HYPERSOMNIA_CODEBASE_CPPS
"src/game/components/container_component.cpp"
"src/game/components/missile_component.cpp"
"src/game/components/movement_component.cpp"
"src/game/components/pathfinding_component.cpp"
"src/game/cosmos/cosmos.cpp"
"src/game/detail/ai/behaviours.cpp"
"src/game/detail/ai/behaviours/explore_in_search_for_last_seen_target.cpp"
"src/game/detail/ai/behaviours/immediate_evasion.cpp"
"src/game/detail/ai/behaviours/minimize_recoil_through_movement.cpp"
"src/game/detail/ai/behaviours/navigate_to_last_seen_position_of_target.cpp"
"src/game/detail/ai/behaviours/pull_trigger.cpp"
"src/game/detail/ai/behaviours/target_closest_enemy.cpp"
"src/game/modes/arena_mode_ai.cpp"
"src/game/modes/ai/arena_mode_ai.cpp"
"src/game/detail/entity_scripts.cpp"
"src/game/enums/filters.cpp"
"src/game/cosmos/cosmos_solvable_significant.cpp"
Expand Down
5 changes: 5 additions & 0 deletions src/application/arena/arena_playtesting_context.h
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
#pragma once
#include <optional>
#include "augs/math/vec2.h"
#include "game/enums/faction_type.h"
#include "game/cosmos/entity_id.h"

struct arena_playtesting_context {
// GEN INTROSPECTOR struct arena_playtesting_context
vec2 initial_spawn_pos;
faction_type first_player_faction = faction_type::SPECTATOR;

std::optional<vec2> debug_pathfinding_end;
entity_id debug_pathfinding_bomb_target;
// END GEN INTROSPECTOR

bool operator==(const arena_playtesting_context&) const = default;
Expand Down
2 changes: 2 additions & 0 deletions src/application/main/draw_debug_lines.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ void draw_debug_lines(

renderer.draw_debug_lines(
DEBUG_LOGIC_STEP_LINES,
DEBUG_LOGIC_STEP_RECTS,
DEBUG_PERSISTENT_LINES,
DEBUG_FRAME_LINES,

Expand All @@ -38,6 +39,7 @@ void draw_debug_lines(
);

renderer.call_and_clear_lines();
renderer.call_and_clear_triangles();

DEBUG_FRAME_LINES.clear();

Expand Down
76 changes: 72 additions & 4 deletions src/application/setups/editor/editor_setup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
#include "game/cosmos/for_each_entity.h"
#include "game/detail/passes_filter.h"
#include "game/detail/pathfinding.h"
#include "game/detail/explosive/like_explosive.h"
#include "application/setups/client/https_file_uploader.h"
#include "augs/misc/readable_bytesize.h"
#include "application/setups/editor/editor_rebuild_prefab_nodes.hpp"
Expand Down Expand Up @@ -395,6 +396,31 @@ bool editor_setup::handle_input_before_game(
}

if (is_playtesting()) {
/*
Handle numpad speed control during playtesting.
*/
const auto& e = in.e;

if (e.was_any_key_pressed()) {
const auto k = e.data.key.key;

auto set_speed = [&](const double s) {
playtest_speed = s;
};

switch (k) {
case key::NUMPAD0: set_speed(1.0); return true;
case key::NUMPAD1: set_speed(0.01); return true;
case key::NUMPAD2: set_speed(0.05); return true;
case key::NUMPAD3: set_speed(0.1); return true;
case key::NUMPAD4: set_speed(0.5); return true;
case key::NUMPAD5: set_speed(2.0); return true;
case key::NUMPAD6: set_speed(4.0); return true;
case key::NUMPAD7: set_speed(10.0); return true;
default: break;
}
}

return false;
}

Expand Down Expand Up @@ -3573,10 +3599,52 @@ const editor_official_resource_map& editor_setup::get_official_resource_map() co
}

arena_playtesting_context editor_setup::make_playtesting_context() const {
return {
get_camera_eye().transform.pos,
project.playtesting.starting_faction
};
arena_playtesting_context ctx;
ctx.initial_spawn_pos = get_camera_eye().transform.pos;
ctx.first_player_faction = project.playtesting.starting_faction;

/*
Look for DEBUG_PATHFINDING_START and DEBUG_PATHFINDING_END markers.
If DEBUG_PATHFINDING_START exists, spawn at that position.
If DEBUG_PATHFINDING_END exists, store it for AI pathfinding testing.
*/
std::optional<vec2> pathfinding_start;
std::optional<vec2> pathfinding_end;

scene.world.for_each_having<invariants::point_marker>(
[&](const auto& typed_handle) {
const auto& marker = typed_handle.template get<invariants::point_marker>();

if (marker.type == point_marker_type::DEBUG_PATHFINDING_START) {
pathfinding_start = typed_handle.get_logic_transform().pos;
}
else if (marker.type == point_marker_type::DEBUG_PATHFINDING_END) {
pathfinding_end = typed_handle.get_logic_transform().pos;
}
}
);

if (pathfinding_start.has_value()) {
ctx.initial_spawn_pos = *pathfinding_start;
}

if (pathfinding_end.has_value()) {
ctx.debug_pathfinding_end = pathfinding_end;
}
else {
/*
Check if there's a planted bomb on the ground to use as pathfinding target.
*/
scene.world.for_each_having<invariants::hand_fuse>(
[&](const auto& typed_handle) {
if (::is_like_plantable_bomb(typed_handle)) {
ctx.debug_pathfinding_bomb_target = typed_handle.get_id();
}
}
);
}

return ctx;
}

#include "application/main/game_frame_buffer.h"
Expand Down
10 changes: 6 additions & 4 deletions src/application/setups/editor/editor_setup.h
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ class editor_setup : public default_setup_settings, public arena_gui_mixin<edito
mode_player_id local_player_id;

bool playtesting = false;
double playtest_speed = 1.0;

scene_entity_to_node_map scene_entity_to_node;

Expand Down Expand Up @@ -610,7 +611,7 @@ class editor_setup : public default_setup_settings, public arena_gui_mixin<edito
/*********************************************************/

auto get_audiovisual_speed() const {
return 1.0;
return is_playtesting() ? playtest_speed : 1.0;
}

const auto& get_viewed_cosmos() const {
Expand Down Expand Up @@ -647,9 +648,10 @@ class editor_setup : public default_setup_settings, public arena_gui_mixin<edito
const setup_advance_input& in,
const C& callbacks
) {
global_time_seconds += in.frame_delta.in_seconds();

timer.advance(in.frame_delta);
auto dt = in.frame_delta;
dt *= playtest_speed;
global_time_seconds += dt.in_seconds();
timer.advance(dt);

auto steps = timer.extract_num_of_logic_steps(get_inv_tickrate());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ editor_project_paths::editor_project_paths(const augs::path_type& target_folder)
};

project_json = in_folder(arena_name + ".json");
project_nav = in_folder(arena_name + ".nav");
project_nav = in_folder("navmesh.nav");
legacy_autosave_json = in_folder("autosave.json");
last_saved_json = in_folder("last_saved.json");
editor_view = in_folder("editor_view.json");
Expand Down
2 changes: 0 additions & 2 deletions src/application/tests_of_traits.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@

#include "augs/templates/traits/container_traits.h"
#include "augs/templates/traits/is_enum_map.h"
#include "game/components/pathfinding_component.h"
#include "game/organization/for_each_entity_type.h"
#include "game/organization/for_each_component_type.h"
#include "game/cosmos/entity_handle.h"
Expand Down Expand Up @@ -302,7 +301,6 @@ struct tests_of_traits {
static_assert(is_container_v<std::vector<int>>, "Trait has failed");
static_assert(is_container_v<std::vector<vec2>>, "Trait has failed");
static_assert(is_container_v<std::vector<cosmos>>, "Trait has failed");
static_assert(is_container_v<std::vector<pathfinding_session>>, "Trait has failed");

static_assert(can_access_data_v<std::string>, "Trait has failed");
static_assert(can_access_data_v<std::vector<int>>, "Trait has failed");
Expand Down
78 changes: 78 additions & 0 deletions src/augs/algorithm/bfs.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,3 +111,81 @@ namespace augs {
}

} /* namespace augs */

namespace augs {

/*
Generic BFS traversal with predicate-based target detection.
Calls a callback for each node matching IsTarget predicate.
Supports early abort via callback return value.
Named "full" because it can potentially iterate the entire graph.

Template parameters:
Id - The type of node identifier
GetVisited - Lambda: (Id) -> bool, returns true if node was visited
SetVisited - Lambda: (Id) -> void, marks node as visited
SetParent - Lambda: (Id child, Id parent) -> void, records parent for path reconstruction
ForEachNeighbor - Lambda: (Id, Callback) -> void, calls callback with each neighbor's Id
IsTarget - Lambda: (Id) -> bool, returns true if node matches the search criteria
OnMatch - Lambda: (Id) -> callback_result, called when a matching node is found.
Return ABORT to stop traversal, CONTINUE to keep searching.

Returns: nothing (results are handled via OnMatch callback)
*/

template <
class Id,
class GetVisited,
class SetVisited,
class SetParent,
class ForEachNeighbor,
class IsTarget,
class OnMatch
>
void bfs_full(
const Id start,
GetVisited&& get_visited,
SetVisited&& set_visited,
SetParent&& set_parent,
ForEachNeighbor&& for_each_neighbor,
IsTarget&& is_target,
OnMatch&& on_match
) {
std::queue<Id> queue;
bool should_abort = false;

set_visited(start);
queue.push(start);

while (!queue.empty() && !should_abort) {
const auto current = queue.front();
queue.pop();

for_each_neighbor(current, [&](const Id neighbor) {
if (should_abort) {
return callback_result::ABORT;
}

if (get_visited(neighbor)) {
return callback_result::CONTINUE;
}

set_visited(neighbor);
set_parent(neighbor, current);

if (is_target(neighbor)) {
if (on_match(neighbor) == callback_result::ABORT) {
should_abort = true;
return callback_result::ABORT;
}
}
else {
queue.push(neighbor);
}

return callback_result::CONTINUE;
});
}
}

}
Loading