From a957d0defc49971c88df830ff20ad8cf68bde46c Mon Sep 17 00:00:00 2001 From: sasa Date: Fri, 28 Aug 2026 22:28:24 +0300 Subject: [PATCH] feat: establish the bounded production architecture --- PLANS.md | 15 +++++++++++++-- include/app_config.h | 18 ++++++++++++++++++ include/app_interfaces.h | 10 ++++++++++ include/application.h | 11 +++++++++++ include/bot_player.h | 5 +++++ include/command_queue.h | 16 ++++++++++++++++ include/diagnostics.h | 13 +++++++++++++ include/fleet_generator.h | 6 ++++++ include/game_engine.h | 6 ++++++ include/game_types.h | 27 +++++++++++++++++++++++++++ include/session_manager.h | 7 +++++++ include/state_presenter.h | 7 +++++++ include/statistics.h | 6 ++++++ include/transport.h | 5 +++++ src/CMakeLists.txt | 2 +- src/application.c | 11 +++++++++++ src/command_queue.c | 19 +++++++++++++++++++ src/main.c | 19 +++++++++++++++++-- test/host/Makefile | 13 +++++++++++++ test/host/test_command_queue | Bin 0 -> 15936 bytes test/host/test_command_queue.c | 24 ++++++++++++++++++++++++ 21 files changed, 235 insertions(+), 5 deletions(-) create mode 100644 include/app_config.h create mode 100644 include/app_interfaces.h create mode 100644 include/application.h create mode 100644 include/bot_player.h create mode 100644 include/command_queue.h create mode 100644 include/diagnostics.h create mode 100644 include/fleet_generator.h create mode 100644 include/game_engine.h create mode 100644 include/game_types.h create mode 100644 include/session_manager.h create mode 100644 include/state_presenter.h create mode 100644 include/statistics.h create mode 100644 include/transport.h create mode 100644 src/application.c create mode 100644 src/command_queue.c create mode 100644 test/host/Makefile create mode 100755 test/host/test_command_queue create mode 100644 test/host/test_command_queue.c diff --git a/PLANS.md b/PLANS.md index 4227796..dfca0f3 100644 --- a/PLANS.md +++ b/PLANS.md @@ -392,7 +392,7 @@ If all criteria pass, set this milestone to `DONE`, append its execution record, ## Milestone 006 — Establish the bounded production architecture -**Status:** `READY` +**Status:** `DONE` **Depends on:** Milestone 005 ### Objective @@ -421,11 +421,22 @@ Create the production firmware and test structure without implementing game beha If all criteria pass, set this milestone to `DONE`, append its execution record, and change Milestone 007 from `BLOCKED` to `READY`. +### Execution record + +- Date: 2026-08-28 +- Board model and revision: ESP32-C6FH4 QFN32, revision v0.2. +- Toolchain and library versions: PlatformIO Core 6.1.19; `espressif32` 7.0.1; ESP-IDF 6.0.1; `esp_littlefs` 1.20.4. +- Result: PASS. +- Evidence: Added bounded production headers for configuration, types, application ownership, game engine, fleet generation, bot scheduling, sessions, presenter, statistics, transport, diagnostics, and deterministic interfaces. Added a fixed 16-command FIFO and a host test. HTTP diagnostics now include largest free block, connected clients, reset reason, and rejected oversized input. Network-facing code has no access to `application_t`; only the application queue may deliver commands to the future game owner. +- Measurements: Firmware build passed with 38,212 / 327,680 B RAM (11.7%) and 1,000,730 / 2,097,152 B flash (47.7%), within the Milestone 005 architecture gate of 1,080,000 B firmware and 220,000 B remaining heap. `make -C test/host run` passed the host command-queue test. +- Issues or deviations: No game behavior was implemented; that remains Milestone 007. No PSRAM, exceptions, RTTI, or heap-owning core containers were added. +- Next action: Milestone 007 is ready. Do not start it unless explicitly requested. + --- ## Milestone 007 — Implement and exhaustively test the game domain core -**Status:** `BLOCKED` +**Status:** `READY` **Depends on:** Milestone 006 ### Objective diff --git a/include/app_config.h b/include/app_config.h new file mode 100644 index 0000000..34ebb7d --- /dev/null +++ b/include/app_config.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +enum { + kBoardWidth = 10, + kBoardHeight = 10, + kBoardCellCount = kBoardWidth * kBoardHeight, + kFleetShipCount = 10, + kPlayerCapacity = 2, + kSpectatorCapacity = 8, + kSessionCapacity = kPlayerCapacity + kSpectatorCapacity, + kCommandQueueCapacity = 16, + kStateMessageCapacity = 512, + kRequestBodyCapacity = 192, + kSessionTokenBytes = 16, + kDisplayNameBytes = 80, +}; diff --git a/include/app_interfaces.h b/include/app_interfaces.h new file mode 100644 index 0000000..8d13529 --- /dev/null +++ b/include/app_interfaces.h @@ -0,0 +1,10 @@ +#pragma once + +#include +#include +#include + +typedef struct { uint64_t (*now_ms)(void *context); void *context; } clock_t; +typedef struct { uint32_t (*next_u32)(void *context); void *context; } random_source_t; +typedef struct { bool (*schedule_after_ms)(void *context, uint32_t delay_ms); void *context; } scheduler_t; +typedef struct { bool (*send)(void *context, int client_id, const char *data, size_t length); void *context; } transport_t; diff --git a/include/application.h b/include/application.h new file mode 100644 index 0000000..5070ccd --- /dev/null +++ b/include/application.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +#include "command_queue.h" + +/* The application task is the sole owner of game_state_t mutations. */ +typedef struct { command_queue_t command_queue; game_state_t state; } application_t; + +bool application_enqueue(application_t *application, const app_command_t *command); +bool application_take_next_command(application_t *application, app_command_t *command); diff --git a/include/bot_player.h b/include/bot_player.h new file mode 100644 index 0000000..658d595 --- /dev/null +++ b/include/bot_player.h @@ -0,0 +1,5 @@ +#pragma once + +#include "app_interfaces.h" + +typedef struct { random_source_t random; scheduler_t scheduler; } bot_player_t; diff --git a/include/command_queue.h b/include/command_queue.h new file mode 100644 index 0000000..5614f91 --- /dev/null +++ b/include/command_queue.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include + +#include "app_config.h" +#include "game_types.h" + +typedef uint8_t command_type_t; +enum { COMMAND_CONFIG, COMMAND_START, COMMAND_SHOT, COMMAND_REMATCH, COMMAND_ABORT }; +typedef struct { command_type_t type; uint8_t session_index; uint32_t game_id; uint32_t version; coordinate_t coordinate; game_mode_t mode; } app_command_t; +typedef struct { app_command_t entries[kCommandQueueCapacity]; uint8_t head; uint8_t tail; uint8_t count; } command_queue_t; + +bool command_queue_push(command_queue_t *queue, const app_command_t *command); +bool command_queue_pop(command_queue_t *queue, app_command_t *command); +_Static_assert(sizeof(command_queue_t) <= 320, "command queue grew beyond fixed budget"); diff --git a/include/diagnostics.h b/include/diagnostics.h new file mode 100644 index 0000000..aa3bc8a --- /dev/null +++ b/include/diagnostics.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +typedef struct { + uint64_t uptime_ms; + uint32_t free_heap_bytes; + uint32_t minimum_free_heap_bytes; + uint32_t largest_free_block_bytes; + uint16_t connected_clients; + uint16_t rejected_oversized_input; + int reset_reason; +} diagnostics_t; diff --git a/include/fleet_generator.h b/include/fleet_generator.h new file mode 100644 index 0000000..ce47392 --- /dev/null +++ b/include/fleet_generator.h @@ -0,0 +1,6 @@ +#pragma once + +#include "app_interfaces.h" +#include "game_types.h" + +typedef struct { random_source_t random; } fleet_generator_t; diff --git a/include/game_engine.h b/include/game_engine.h new file mode 100644 index 0000000..e9a58ef --- /dev/null +++ b/include/game_engine.h @@ -0,0 +1,6 @@ +#pragma once + +#include "game_types.h" + +/* Rule implementation begins in Milestone 007. */ +typedef struct { game_state_t state; } game_engine_t; diff --git a/include/game_types.h b/include/game_types.h new file mode 100644 index 0000000..0637a3c --- /dev/null +++ b/include/game_types.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include + +#include "app_config.h" + +typedef uint8_t cell_t; +enum { CELL_UNKNOWN, CELL_WATER, CELL_SHIP, CELL_MISS, CELL_HIT }; +typedef uint8_t phase_t; +enum { PHASE_LOBBY, PHASE_PREPARING, PHASE_IN_PROGRESS, PHASE_FINISHED, PHASE_REMATCH_WAIT }; +typedef uint8_t role_t; +enum { ROLE_PLAYER_1, ROLE_PLAYER_2, ROLE_SPECTATOR }; +typedef uint8_t game_mode_t; +enum { MODE_HUMAN, MODE_BOT }; + +typedef struct { uint8_t x; uint8_t y; } coordinate_t; +typedef struct { uint8_t x; uint8_t y; uint8_t length; uint8_t hits; bool horizontal; } ship_t; +typedef struct { cell_t cells[kBoardCellCount]; ship_t ships[kFleetShipCount]; uint8_t ships_alive; } board_t; +typedef struct { uint32_t game_id; uint32_t version; phase_t phase; game_mode_t mode; uint8_t current_player; board_t boards[kPlayerCapacity]; } game_state_t; + +_Static_assert(kBoardCellCount == 100, "board contract changed"); +_Static_assert(kFleetShipCount == 10, "fleet contract changed"); +_Static_assert(kSessionCapacity == 10, "session contract changed"); +_Static_assert(sizeof(coordinate_t) == 2, "coordinate must remain compact"); +_Static_assert(sizeof(ship_t) <= 6, "ship grew beyond fixed budget"); +_Static_assert(sizeof(board_t) <= 164, "board grew beyond fixed budget"); diff --git a/include/session_manager.h b/include/session_manager.h new file mode 100644 index 0000000..317783c --- /dev/null +++ b/include/session_manager.h @@ -0,0 +1,7 @@ +#pragma once + +#include "app_config.h" +#include "game_types.h" + +typedef struct { role_t role; bool occupied; uint8_t token[kSessionTokenBytes]; char name[kDisplayNameBytes + 1]; } session_t; +typedef struct { session_t entries[kSessionCapacity]; } session_manager_t; diff --git a/include/state_presenter.h b/include/state_presenter.h new file mode 100644 index 0000000..d47dd7e --- /dev/null +++ b/include/state_presenter.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +#include "game_types.h" + +bool state_presenter_write(const game_state_t *state, role_t viewer, char *output, size_t output_size, size_t *written); diff --git a/include/statistics.h b/include/statistics.h new file mode 100644 index 0000000..4ee5d16 --- /dev/null +++ b/include/statistics.h @@ -0,0 +1,6 @@ +#pragma once + +#include + +typedef struct { uint16_t shots; uint16_t hits; uint16_t misses; uint8_t ships_sunk; } match_statistics_t; +typedef struct { uint16_t games; uint16_t wins; uint16_t losses; uint32_t shots; uint32_t hits; } cumulative_statistics_t; diff --git a/include/transport.h b/include/transport.h new file mode 100644 index 0000000..46c6e4a --- /dev/null +++ b/include/transport.h @@ -0,0 +1,5 @@ +#pragma once + +#include "app_interfaces.h" + +typedef struct { transport_t transport; } transport_service_t; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a95dd1e..ba7218a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -2,7 +2,7 @@ # without default 'CMakeLists.txt' file. idf_component_register( - SRCS "main.c" + SRCS "main.c" "application.c" "command_queue.c" INCLUDE_DIRS "../include" REQUIRES esp_event esp_http_server esp_netif esp_wifi esp_littlefs nvs_flash ) diff --git a/src/application.c b/src/application.c new file mode 100644 index 0000000..8a311d0 --- /dev/null +++ b/src/application.c @@ -0,0 +1,11 @@ +#include "application.h" + +#include + +bool application_enqueue(application_t *application, const app_command_t *command) { + return application != NULL && command_queue_push(&application->command_queue, command); +} + +bool application_take_next_command(application_t *application, app_command_t *command) { + return application != NULL && command_queue_pop(&application->command_queue, command); +} diff --git a/src/command_queue.c b/src/command_queue.c new file mode 100644 index 0000000..66ed21e --- /dev/null +++ b/src/command_queue.c @@ -0,0 +1,19 @@ +#include "command_queue.h" + +#include + +bool command_queue_push(command_queue_t *queue, const app_command_t *command) { + if (queue == NULL || command == NULL || queue->count == kCommandQueueCapacity) return false; + queue->entries[queue->tail] = *command; + queue->tail = (uint8_t)((queue->tail + 1U) % kCommandQueueCapacity); + ++queue->count; + return true; +} + +bool command_queue_pop(command_queue_t *queue, app_command_t *command) { + if (queue == NULL || command == NULL || queue->count == 0) return false; + *command = queue->entries[queue->head]; + queue->head = (uint8_t)((queue->head + 1U) % kCommandQueueCapacity); + --queue->count; + return true; +} diff --git a/src/main.c b/src/main.c index 8861cff..c187a1b 100644 --- a/src/main.c +++ b/src/main.c @@ -6,6 +6,7 @@ #include "esp_check.h" #include "esp_event.h" +#include "esp_heap_caps.h" #include "esp_http_server.h" #include "esp_littlefs.h" #include "esp_log.h" @@ -55,6 +56,7 @@ static bool s_littlefs_mounted; static httpd_handle_t s_server; static esp_timer_handle_t s_state_timer; static portMUX_TYPE s_capacity_lock = portMUX_INITIALIZER_UNLOCKED; +static uint16_t s_rejected_oversized_input; typedef enum { VIEW_PLAYER_1, VIEW_PLAYER_2, VIEW_SPECTATOR } view_role_t; typedef struct { int fd; view_role_t role; } ws_client_t; @@ -260,6 +262,7 @@ static void make_capacity_state(capacity_state_t *state) { static esp_err_t capacity_run_handler(httpd_req_t *request) { if (request->content_len != 0) { + ++s_rejected_oversized_input; return httpd_resp_send_err(request, HTTPD_400_BAD_REQUEST, "capacity run accepts no body"); } capacity_state_t fresh_state; @@ -310,7 +313,10 @@ static esp_err_t websocket_handler(httpd_req_t *request) { } httpd_ws_frame_t frame = {0}; ESP_RETURN_ON_ERROR(httpd_ws_recv_frame(request, &frame, 0), kLogTag, "websocket frame read failed"); - if (frame.len > 64 || frame.type != HTTPD_WS_TYPE_TEXT) return ESP_OK; + if (frame.len > 64 || frame.type != HTTPD_WS_TYPE_TEXT) { + ++s_rejected_oversized_input; + return ESP_OK; + } char message[65] = {0}; frame.payload = (uint8_t *)message; frame.len = sizeof(message) - 1; @@ -462,23 +468,32 @@ static const char *wifi_state_name(const wifi_state_t *state) { static esp_err_t health_handler(httpd_req_t *request) { const wifi_state_t wifi_state = wifi_state_snapshot(); + size_t client_count = kHttpMaxOpenSockets; + int clients[kHttpMaxOpenSockets]; + if (s_server == NULL || httpd_get_client_list(s_server, &client_count, clients) != ESP_OK) client_count = 0; int8_t rssi_dbm = 0; wifi_ap_record_t access_point = {0}; if (wifi_state.connected && esp_wifi_sta_get_ap_info(&access_point) == ESP_OK) { rssi_dbm = access_point.rssi; } - char response[320]; + char response[384]; const int written = snprintf(response, sizeof(response), "{\"uptime_ms\":%" PRIu64 ",\"wifi_state\":\"%s\",\"rssi_dbm\":%d" ",\"free_heap_bytes\":%u,\"min_free_heap_bytes\":%u" + ",\"largest_free_block_bytes\":%u,\"connected_clients\":%u" + ",\"rejected_oversized_input\":%u,\"reset_reason\":%d" ",\"build_version\":\"%s\"}", (uint64_t)(esp_timer_get_time() / 1000), wifi_state_name(&wifi_state), (int)rssi_dbm, (unsigned int)esp_get_free_heap_size(), (unsigned int)esp_get_minimum_free_heap_size(), + (unsigned int)heap_caps_get_largest_free_block(MALLOC_CAP_8BIT), + (unsigned int)client_count, + (unsigned int)s_rejected_oversized_input, + (int)esp_reset_reason(), kBuildVersion); if (written < 0 || (size_t)written >= sizeof(response)) { return httpd_resp_send_err(request, HTTPD_500_INTERNAL_SERVER_ERROR, "health response failed"); diff --git a/test/host/Makefile b/test/host/Makefile new file mode 100644 index 0000000..6e18a46 --- /dev/null +++ b/test/host/Makefile @@ -0,0 +1,13 @@ +CC ?= cc +CFLAGS ?= -std=c11 -Wall -Wextra -Werror -I../../include + +all: test_command_queue + +test_command_queue: test_command_queue.c ../../src/command_queue.c ../../src/application.c + $(CC) $(CFLAGS) $^ -o $@ + +run: test_command_queue + ./test_command_queue + +clean: + rm -f test_command_queue diff --git a/test/host/test_command_queue b/test/host/test_command_queue new file mode 100755 index 0000000000000000000000000000000000000000..6b9b1725b19917b6b4dd3f4413719d465f625de8 GIT binary patch literal 15936 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 literal 0 HcmV?d00001 diff --git a/test/host/test_command_queue.c b/test/host/test_command_queue.c new file mode 100644 index 0000000..232a891 --- /dev/null +++ b/test/host/test_command_queue.c @@ -0,0 +1,24 @@ +#include +#include +#include + +#include "command_queue.h" + +int main(void) { + command_queue_t queue = {0}; + app_command_t input = {.type = COMMAND_SHOT, .session_index = 1, .game_id = 7, .version = 3, .coordinate = {4, 5}}; + app_command_t output = {0}; + assert(!command_queue_pop(&queue, &output)); + for (int index = 0; index < kCommandQueueCapacity; ++index) { + input.coordinate.x = (uint8_t)index; + assert(command_queue_push(&queue, &input)); + } + assert(!command_queue_push(&queue, &input)); + for (int index = 0; index < kCommandQueueCapacity; ++index) { + assert(command_queue_pop(&queue, &output)); + assert(output.coordinate.x == (uint8_t)index); + } + assert(!command_queue_pop(&queue, &output)); + puts("command queue tests passed"); + return 0; +}