feat(game): implement authoritative movement and visibility core

This commit is contained in:
2026-09-04 11:29:20 +03:00
parent 5d780def9d
commit 2aaa1650ae
11 changed files with 1085 additions and 7 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
idf_component_register(
SRCS "main.c"
REQUIRES esp_event esp_netif esp_timer heap maze32_config maze32_maze maze32_network maze32_platform maze32_web
REQUIRES esp_event esp_netif esp_timer heap maze32_config maze32_game maze32_maze maze32_network maze32_platform maze32_web
)
+111
View File
@@ -14,6 +14,7 @@
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "maze32_config_store.h"
#include "maze32_game.h"
#include "maze32_health.h"
#include "maze32_maze.h"
#include "maze32_network.h"
@@ -25,6 +26,109 @@ static httpd_handle_t web_server;
static maze32_network_t network;
static maze32_gateway_t gateway;
#ifdef MAZE32_MILESTONE_006_DEVICE_TEST
static maze32_maze_t game_test_maze;
static maze32_maze_workspace_t game_test_workspace;
static maze32_game_t device_test_game;
static maze32_view_t device_test_view;
static char device_test_payload[MAZE32_GAME_VIEW_CHUNK_BYTES];
static maze32_game_direction_t first_open_direction(const maze32_maze_t* maze, maze32_cell_t cell) {
static const maze32_game_direction_t DIRECTIONS[] = {
MAZE32_GAME_DIRECTION_NORTH,
MAZE32_GAME_DIRECTION_EAST,
MAZE32_GAME_DIRECTION_SOUTH,
MAZE32_GAME_DIRECTION_WEST,
};
static const maze32_passage_t PASSAGES[] = {
MAZE32_PASSAGE_NORTH,
MAZE32_PASSAGE_EAST,
MAZE32_PASSAGE_SOUTH,
MAZE32_PASSAGE_WEST,
};
for (size_t index = 0U; index < sizeof(DIRECTIONS) / sizeof(DIRECTIONS[0]); ++index) {
if (maze32_maze_has_passage(maze, cell, PASSAGES[index])) {
return DIRECTIONS[index];
}
}
return (maze32_game_direction_t)UINT8_MAX;
}
static esp_err_t run_game_device_test(void) {
maze32_maze_config_t maze_config = maze32_maze_default_config();
maze32_maze_report_t maze_report;
if (maze32_maze_generate(&game_test_maze, &game_test_workspace, &maze_config,
UINT32_C(0x5a320006), &maze_report) != MAZE32_MAZE_OK) {
ESP_LOGE(TAG, "M006 physical game test could not generate its maze");
return ESP_FAIL;
}
maze32_game_config_t game_config = maze32_game_default_config();
size_t free_before = heap_caps_get_free_size(MALLOC_CAP_8BIT);
int64_t started_us = esp_timer_get_time();
if (maze32_game_init(&device_test_game, &game_test_maze, &game_config) !=
MAZE32_GAME_RESULT_ACCEPTED) {
return ESP_FAIL;
}
maze32_game_set_phase(&device_test_game, MAZE32_GAME_PHASE_ACTIVE);
maze32_game_direction_t direction =
first_open_direction(&game_test_maze, device_test_game.players[0].position);
uint32_t command_sequence = 0U;
if (direction > MAZE32_GAME_DIRECTION_WEST ||
maze32_game_enqueue_move(&device_test_game, MAZE32_GAME_ROLE_PLAYER_ONE, direction, 0U,
&command_sequence) != MAZE32_GAME_RESULT_ACCEPTED) {
return ESP_FAIL;
}
maze32_game_command_result_t command_result = maze32_game_process_next(&device_test_game);
if (command_result.result != MAZE32_GAME_RESULT_ACCEPTED ||
command_result.state_sequence != 1U ||
maze32_game_project_view(&device_test_game, MAZE32_GAME_ROLE_PLAYER_ONE, 3U,
&device_test_view) != MAZE32_GAME_RESULT_ACCEPTED) {
return ESP_FAIL;
}
uint16_t player_cells = device_test_view.cell_count;
uint8_t player_objects = device_test_view.object_count;
size_t player_chunks = maze32_game_view_chunk_count(&device_test_view);
size_t payload_bytes = 0U;
if (maze32_game_serialize_view_chunk(&device_test_view, 0U, device_test_payload,
sizeof(device_test_payload),
&payload_bytes) != MAZE32_GAME_RESULT_ACCEPTED) {
return ESP_FAIL;
}
if (maze32_game_project_view(&device_test_game, MAZE32_GAME_ROLE_OBSERVER, 0U,
&device_test_view) != MAZE32_GAME_RESULT_ACCEPTED) {
return ESP_FAIL;
}
uint64_t elapsed_us = (uint64_t)(esp_timer_get_time() - started_us);
size_t free_after = heap_caps_get_free_size(MALLOC_CAP_8BIT);
size_t fixed_bytes =
sizeof(device_test_game) + sizeof(device_test_view) + sizeof(device_test_payload);
ESP_LOGI(TAG, "M006 command: command=%" PRIu32 ", state=%" PRIu32 ", result=%s, position=%u,%u",
command_sequence, command_result.state_sequence,
maze32_game_result_name(command_result.result), device_test_game.players[0].position.x,
device_test_game.players[0].position.y);
ESP_LOGI(TAG,
"M006 views: player_cells=%u, player_objects=%u, player_chunks=%u, "
"observer_cells=%u, observer_objects=%u, observer_chunks=%u, first_chunk=%u bytes",
player_cells, player_objects, (unsigned int)player_chunks, device_test_view.cell_count,
device_test_view.object_count,
(unsigned int)maze32_game_view_chunk_count(&device_test_view),
(unsigned int)payload_bytes);
ESP_LOGI(TAG, "M006 budget: fixed=%u/%u bytes, runtime=%" PRIu64 "/%u ms, heap_delta=%d",
(unsigned int)fixed_bytes, (unsigned int)MAZE32_GAME_RAM_BUDGET_BYTES,
elapsed_us / 1000U, (unsigned int)MAZE32_GAME_RUNTIME_BUDGET_MS,
(int)(free_before - free_after));
if (device_test_view.cell_count != MAZE32_MAZE_DEFAULT_WIDTH * MAZE32_MAZE_DEFAULT_HEIGHT ||
fixed_bytes > MAZE32_GAME_RAM_BUDGET_BYTES ||
elapsed_us > (uint64_t)MAZE32_GAME_RUNTIME_BUDGET_MS * 1000U || free_before != free_after) {
ESP_LOGE(TAG, "M006 physical game test failed");
return ESP_FAIL;
}
ESP_LOGI(TAG, "M006 physical game test: PASS");
return ESP_OK;
}
#endif
#ifdef MAZE32_MILESTONE_005_DEVICE_TEST
static maze32_maze_t device_test_maze;
static maze32_maze_workspace_t device_test_workspace;
@@ -159,6 +263,13 @@ void app_main(void) {
}
#endif
#ifdef MAZE32_MILESTONE_006_DEVICE_TEST
if (run_game_device_test() != ESP_OK) {
maze32_health_set(&health, MAZE32_HEALTH_ERROR, (int)ESP_FAIL);
return;
}
#endif
vTaskDelay(pdMS_TO_TICKS(1500));
ESP_LOGI(TAG, "maze32 firmware version %s starting", application->version);