Include AI failure context in stream errors (#8193)

* Include AI failure context in stream errors

* Fix AI diagnostics end condition logging
This commit is contained in:
2026-06-26 07:44:44 -07:00
committed by GitHub
parent 19e85bde8b
commit b1263ec4df
@@ -58,6 +58,45 @@ namespace {
return out.str();
}
[[nodiscard]] auto DescribeCurrentStateForLog(const ShardokEngine &engine) -> std::string {
const auto *state = engine.GetCurrentGameState().Get();
if (state == nullptr) { return "state=<null>"; }
const auto vectorSize = [](const auto *vector) -> size_t {
return vector == nullptr ? 0 : vector->size();
};
std::ostringstream out;
out << "state={round=" << static_cast<int>(state->current_round())
<< " current_player=" << static_cast<int>(state->current_player())
<< " history_count=" << engine.GetUnfilteredHistoryCount();
if (const auto *status = state->status(); status != nullptr) {
out << " status=" << static_cast<int>(status->state()) << " end_condition=";
if (const auto *endCondition = status->end_game_condition(); endCondition != nullptr) {
out << "{type=" << static_cast<int>(endCondition->victory_type())
<< " victory_details=" << static_cast<int>(endCondition->victory_details())
<< " draw_details=" << static_cast<int>(endCondition->draw_details()) << '}';
} else {
out << "<null>";
}
} else {
out << " status=<null>";
}
out << " units=" << vectorSize(state->units())
<< " player_infos=" << vectorSize(state->player_infos());
if (const auto *hexMap = state->hex_map(); hexMap != nullptr) {
out << " map=" << hexMap->row_count() << 'x' << hexMap->column_count();
} else {
out << " map=<null>";
}
out << '}';
return out.str();
}
} // namespace
class InvalidTokenException : public std::exception {};
@@ -242,27 +281,32 @@ void ShardokGameController::DoAIThread() {
aiThreadKeepGoing = !engine->GameIsOver();
}
} catch (const std::exception &e) {
std::ostringstream error;
error << "AI error in game " << cachedGameId << ", player " << playerId << ", round "
<< gsv.current_round() << ": " << e.what();
std::ostringstream context;
context << "AI failure context: phase=" << aiPhase
<< ", expected_history_count=" << expectedHistoryCount
<< ", current_history_count=" << engine->GetUnfilteredHistoryCount()
<< ", chosen_index=" << chosenIndex
<< ", current_player=" << static_cast<int>(engine->GetCurrentPlayerId()) << ", "
<< DescribeCurrentStateForLog(*engine);
const std::string commandsContext = availableCommands
? DescribeCommandsForLog(*availableCommands)
: "available_command_count=<cleared>";
std::cerr << "AI thread exception in game " << cachedGameId << ", player " << playerId
<< ", round " << gsv.current_round() << ": " << e.what() << '\n';
std::cerr << "AI failure context: phase=" << aiPhase
<< ", expected_history_count=" << expectedHistoryCount
<< ", current_history_count=" << engine->GetUnfilteredHistoryCount()
<< ", chosen_index=" << chosenIndex
<< ", current_player=" << static_cast<int>(engine->GetCurrentPlayerId())
<< '\n';
if (availableCommands) {
std::cerr << DescribeCommandsForLog(*availableCommands) << '\n';
} else {
std::cerr << "available_command_count=<cleared>" << '\n';
}
std::cerr << context.str() << '\n';
std::cerr << commandsContext << '\n';
void *backtraceArray[20];
const int backtraceSize = backtrace(backtraceArray, 20);
backtrace_symbols_fd(backtraceArray, backtraceSize, STDERR_FILENO);
aiThreadErrorMessage = "AI error in game " + cachedGameId + ", player " +
std::to_string(playerId) + ", round " +
std::to_string(gsv.current_round()) + ": " + e.what();
aiThreadErrorMessage = error.str() + "\n" + context.str() + "\n" + commandsContext;
aiThreadFailed.store(true);
updateCondition.notify_all();
break;