Compare commits

...
Author SHA1 Message Date
admin bfc32e8068 Use direct vector declarations in library tests 2026-06-23 12:56:41 -07:00
adminandGitHub 549cf37127 Require eagle map on GameState (#7924) 2026-06-23 12:44:12 -07:00
adminandGitHub 237d5ce7c3 Tidy transposition table accessors (#7918) 2026-06-23 12:25:57 -07:00
adminandGitHub 05fc72c611 Tidy AI command evaluator result state (#7917) 2026-06-23 12:25:36 -07:00
adminandGitHub 5d889b71aa Remove GameState bootstrap date helper (#7916) 2026-06-23 12:24:57 -07:00
adminandGitHub 8641d15fc7 Remove single-future reserve hints (#7915) 2026-06-23 12:24:00 -07:00
adminandGitHub e4fc742990 Make GameState currentDate required (#7913) 2026-06-23 12:10:56 -07:00
adminandGitHub a27dfe895f Use span for odds other factors (#7914) 2026-06-23 12:07:11 -07:00
adminandGitHub 098d4b4dcc Tidy random battle runner setup (#7903) 2026-06-23 11:52:39 -07:00
adminandGitHub 765ca02255 Use clamped perf test unit counts (#7902) 2026-06-23 11:52:13 -07:00
adminandGitHub 186dec0ad9 Reserve controller update fields (#7901) 2026-06-23 11:50:01 -07:00
adminandGitHub 14a6d143bc Reserve odds helper factors (#7904) 2026-06-23 11:48:42 -07:00
adminandGitHub ade5a4fa10 Size game state guesser buffers first (#7905) 2026-06-23 11:47:38 -07:00
adminandGitHub 85d3ae8fce Tidy action point distance setup (#7906) 2026-06-23 11:47:22 -07:00
adminandGitHub fb200278ae Tidy available command assembly (#7907) 2026-06-23 11:47:02 -07:00
adminandGitHub 34da29bef0 Reserve reinforcement setup commands (#7908) 2026-06-23 11:46:47 -07:00
adminandGitHub f164fbb150 Add GameState required current date helper (#7910) 2026-06-23 11:46:03 -07:00
adminandGitHub fe1537bb12 Trim map names by extension (#7909) 2026-06-23 11:45:06 -07:00
adminandGitHub 69cc336cef Tidy map info position loop (#7911) 2026-06-23 11:44:48 -07:00
adminandGitHub d05030f99a Improve remaining iPad touch targets (#7900) 2026-06-23 11:03:39 -07:00
adminandGitHub 9699d095c5 Reserve command execution result buffers (#7883) 2026-06-23 11:03:01 -07:00
adminandGitHub 6ca2d7a37c Reserve action execution buffers (#7884) 2026-06-23 11:02:40 -07:00
adminandGitHub b0c4944a6c Remove Postgres text store early returns (#7881) 2026-06-23 11:02:13 -07:00
adminandGitHub d25925d525 Avoid implicit food lookup insertion (#7885) 2026-06-23 11:01:38 -07:00
adminandGitHub 07a6834216 Handle zero-length cube lines (#7880) 2026-06-23 11:00:47 -07:00
adminandGitHub 69f1f51460 Tighten shardok server request conversion (#7879) 2026-06-23 11:00:14 -07:00
adminandGitHub 8b30a2c5f2 Reserve tutorial reinforcement units (#7886) 2026-06-23 10:59:49 -07:00
adminandGitHub 65a534ebae Remove SQLite text store early returns (#7887) 2026-06-23 10:59:31 -07:00
adminandGitHub ac0a154fc4 Reserve game state view fields (#7888) 2026-06-23 10:59:10 -07:00
adminandGitHub 1b510007fd Use Await.result in server setup (#7889) 2026-06-23 10:58:23 -07:00
adminandGitHub 692c363264 Guard action filter actor lookup (#7890) 2026-06-23 10:58:02 -07:00
adminandGitHub cf5d88c69d Avoid copying filtered history (#7892) 2026-06-23 10:57:36 -07:00
adminandGitHub 109f49e522 Clarify custom battle player view lookup (#7891) 2026-06-23 10:57:05 -07:00
adminandGitHub 53e3f7a1b0 Use fixed adjacent tile offsets (#7893) 2026-06-23 10:56:51 -07:00
adminandGitHub c7d3e162b9 Clarify ResolveBattle required winners (#7894) 2026-06-23 10:56:23 -07:00
adminandGitHub 7b2bb34a36 Clarify mid-game AI required values (#7895) 2026-06-23 10:56:07 -07:00
adminandGitHub c7b1082954 Clarify GamesManager required loads (#7896) 2026-06-23 10:54:42 -07:00
adminandGitHub aa18d0dd1a Clarify chronicle required leaders (#7897) 2026-06-23 10:54:22 -07:00
adminandGitHub 711d0382f3 Make province distance lookup explicit (#7898) 2026-06-23 10:53:55 -07:00
adminandGitHub 7b02c39172 Make tutorial loyalty lookups explicit (#7899) 2026-06-23 10:53:04 -07:00
adminandGitHub b178f11bea Improve iPad touch targets in popup UI (#7882) 2026-06-23 10:49:29 -07:00
adminandGitHub 42da8a7d15 Clarify great person faction requirement (#7870) 2026-06-23 10:08:24 -07:00
adminandGitHub bd9b62d7de Reserve AI evaluator lookahead vectors (#7869) 2026-06-23 10:08:07 -07:00
adminandGitHub 98fcd94614 Reserve engine output vectors (#7868) 2026-06-23 10:07:49 -07:00
adminandGitHub 06e7112db1 Clarify battle participant lookups (#7847) 2026-06-23 10:07:32 -07:00
adminandGitHub 9ba023b0fd Reserve hex map proto fields (#7871) 2026-06-23 10:07:08 -07:00
adminandGitHub ff63c0fff2 Reserve game state helper collections (#7872) 2026-06-23 10:06:52 -07:00
adminandGitHub 421dbcae80 Clarify required quest request fields (#7873) 2026-06-23 10:06:34 -07:00
adminandGitHub 4c321967f4 Reserve geometry utility vectors (#7874) 2026-06-23 10:06:18 -07:00
adminandGitHub b3c0138074 Clarify required proto submessages (#7875) 2026-06-23 10:05:53 -07:00
adminandGitHub 4b7c862ac8 Clarify service invariant lookups (#7876) 2026-06-23 10:05:31 -07:00
adminandGitHub cf4b99710c Clarify Shardok interface required fields (#7877) 2026-06-23 10:05:11 -07:00
adminandGitHub d25e0121a6 Clarify required hero library conjunctions (#7878) 2026-06-23 10:04:52 -07:00
adminandGitHub 213a339d34 Delete stale editor log zip (#7845) 2026-06-23 08:38:58 -07:00
adminandGitHub d2205dc361 Reserve Shardok test helper vectors (#7846) 2026-06-23 08:38:46 -07:00
adminandGitHub 21e0586add Reserve AI test helper vectors (#7848) 2026-06-23 08:38:07 -07:00
adminandGitHub cbbe200c98 Avoid raw battle battalion options (#7862) 2026-06-23 08:37:50 -07:00
adminandGitHub fbeddc7b19 Avoid raw heads in string parser (#7849) 2026-06-23 08:29:08 -07:00
adminandGitHub 1ed6c85b5d Improve iPad touch targets in Eagle UI (#7850) 2026-06-23 08:28:25 -07:00
adminandGitHub c7f10dbcbc Clarify required engine history result (#7851) 2026-06-23 08:28:08 -07:00
adminandGitHub 81f747c75b Reserve simulator setup vectors (#7852) 2026-06-23 08:27:41 -07:00
adminandGitHub a76dc0515c Clarify required dates in round advancement (#7853) 2026-06-23 08:26:39 -07:00
adminandGitHub bb77ecbd73 Clarify province resource date requirement (#7854) 2026-06-23 08:26:21 -07:00
adminandGitHub 5d35e7d78b Extract MCTS action sorting helper (#7855) 2026-06-23 08:25:56 -07:00
adminandGitHub e20c4e7027 Avoid vector allocation in ranged bonus scoring (#7856) 2026-06-23 08:25:25 -07:00
adminandGitHub 3977df70f4 Clarify more current date requirements (#7857) 2026-06-23 08:24:52 -07:00
adminandGitHub 7f72a8bb39 Reserve AI strategy working vectors (#7858) 2026-06-23 08:24:26 -07:00
adminandGitHub de27e9fcb8 Right-size APD working vectors (#7859) 2026-06-23 08:24:05 -07:00
adminandGitHub 76bd94ddb5 Clarify config parse failures (#7860) 2026-06-23 08:23:35 -07:00
adminandGitHub 651e209f07 Move completed action results into vectors (#7861) 2026-06-23 08:23:03 -07:00
adminandGitHub 448ec1e0ee Reserve simulator CLI path vectors (#7863) 2026-06-23 08:21:32 -07:00
adminandGitHub 6e2afeee3c Clarify required battle ended result (#7864) 2026-06-23 08:21:08 -07:00
adminandGitHub edce49467c Reserve map info results (#7865) 2026-06-23 08:20:51 -07:00
adminandGitHub 2934027437 Clarify battle unit return lookups (#7866) 2026-06-23 08:20:35 -07:00
adminandGitHub b2250cda79 Reserve tutorial controller result vectors (#7867) 2026-06-23 08:20:17 -07:00
adminandGitHub 96d69b349f Reserve Shardok filter result vectors (#7839) 2026-06-23 07:49:53 -07:00
adminandGitHub 4a6ae0380a Clarify current date requirements in command helpers (#7840) 2026-06-23 07:49:38 -07:00
adminandGitHub e339ddd227 Require factions for leader-aware command choices (#7841) 2026-06-23 07:49:18 -07:00
adminandGitHub afaf6c9e19 Move Shardok vector temporaries (#7842) 2026-06-23 07:49:03 -07:00
adminandGitHub e21eaf8606 Add narrow March command flow (#7747) 2026-06-23 07:48:32 -07:00
adminandGitHub a43a650d17 Document PR review commit history preference (#7844) 2026-06-23 07:48:17 -07:00
adminandGitHub 17cb742964 Clarify province conquest required values (#7843) 2026-06-23 07:40:12 -07:00
adminandGitHub 9d8231b253 Clarify current date requirements in aftermath phases (#7836) 2026-06-23 07:00:16 -07:00
adminandGitHub 5bf2ea95e5 Clarify current date requirements in resolution events (#7837) 2026-06-23 07:00:02 -07:00
adminandGitHub 2f48330d74 Document cleanup PR batching guidance (#7838) 2026-06-23 06:59:31 -07:00
adminandGitHub b0b516a990 Clarify current date requirements in event phases (#7835) 2026-06-23 06:59:05 -07:00
adminandGitHub 1057271163 Reserve move destination vector (#7830) 2026-06-23 06:58:47 -07:00
adminandGitHub 296d5df16f Reserve fall into water helper vectors (#7829) 2026-06-23 06:58:33 -07:00
adminandGitHub b2abe00b14 Reserve possible chargee ids (#7828) 2026-06-23 06:58:20 -07:00
adminandGitHub 793d665d80 Reserve allied player id vector (#7826) 2026-06-23 06:58:07 -07:00
adminandGitHub bbbe596e72 Reserve game state guesser vectors (#7825) 2026-06-23 06:57:54 -07:00
adminandGitHub c3c148f5db Reserve Shardok game setup vectors (#7823) 2026-06-23 06:57:33 -07:00
adminandGitHub 4b8f1d2d20 Reserve hex cube result vectors (#7821) 2026-06-23 06:57:18 -07:00
adminandGitHub 8a3222ed54 Reserve map name vector capacity (#7819) 2026-06-23 06:56:56 -07:00
adminandGitHub 9f82af2e12 Make remaining Shardok test casts explicit (#7818) 2026-06-23 06:56:35 -07:00
adminandGitHub 2536e5146c Fix empty AI score reserve underflow (#7832) 2026-06-23 06:54:09 -07:00
adminandGitHub 98b0502e20 Use reinterpret cast for resolved unit bytes (#7811) 2026-06-23 06:08:33 -07:00
adminandGitHub 25c1198c0a Clarify captured hero command acting hero (#7790) 2026-06-23 06:05:58 -07:00
adminandGitHub a6e31596d6 Qualify flatbuffer wrapper memcpy calls (#7791) 2026-06-23 06:05:42 -07:00
adminandGitHub c9b3cd6fc0 Clarify beast info requirement (#7792) 2026-06-23 06:05:26 -07:00
adminandGitHub 2c18d1d39b Move AI command evaluator sample sequence (#7794) 2026-06-23 06:05:09 -07:00
adminandGitHub 28895e3e04 Clarify captured hero command choice requirement (#7793) 2026-06-23 06:04:54 -07:00
adminandGitHub f8ef044226 Include cstring in unit helpers (#7789) 2026-06-23 06:04:36 -07:00
adminandGitHub b8da5750c7 Clarify please recruit command choice requirements (#7795) 2026-06-23 06:02:59 -07:00
adminandGitHub fbdc0f79a8 Move AI battle simulator setup objects (#7796) 2026-06-23 06:02:35 -07:00
adminandGitHub ea7f666e36 Move game state helper unit structs (#7797) 2026-06-23 06:02:18 -07:00
adminandGitHub 48ff8a7e1b Bind march origin command choice (#7798) 2026-06-23 06:02:00 -07:00
adminandGitHub 033a378e14 Use static cast for game status conversion (#7799) 2026-06-23 06:01:41 -07:00
adminandGitHub a3d19f246e Move random battle runner units (#7800) 2026-06-23 06:01:25 -07:00
adminandGitHub ec67215bc2 Clean up percentile roll casts (#7801) 2026-06-23 06:00:59 -07:00
adminandGitHub f207bef4a8 Clarify armament cost lookup (#7802) 2026-06-23 06:00:34 -07:00
adminandGitHub 41bb944c26 Use static casts for action result sizes (#7803) 2026-06-23 06:00:19 -07:00
adminandGitHub 38eb33367d Bind attack command current date (#7804) 2026-06-23 05:59:45 -07:00
adminandGitHub 5ae59006fb Bind alms command current date (#7805) 2026-06-23 05:59:26 -07:00
adminandGitHub e66e7d4a03 Clarify update game status player id check (#7806) 2026-06-23 05:59:05 -07:00
adminandGitHub 34bbfaf256 Clarify backstory update counter parsing (#7807) 2026-06-23 05:58:30 -07:00
adminandGitHub abd323a232 Use static casts in hex map utilities (#7808) 2026-06-23 05:58:00 -07:00
adminandGitHub 97ba1d0be1 Use static casts in hex cube fixup (#7809) 2026-06-23 05:57:43 -07:00
adminandGitHub deb211a420 Clarify new round current date requirement (#7810) 2026-06-23 05:57:19 -07:00
adminandGitHub 9481eed7b8 Use static cast for new wind direction (#7812) 2026-06-23 05:51:51 -07:00
adminandGitHub 50c457d877 Use static cast for battalion type size check (#7813) 2026-06-23 05:51:34 -07:00
adminandGitHub a544a0d15e Use static casts in hex map direction arithmetic (#7814) 2026-06-22 23:52:15 -07:00
adminandGitHub 8c26ac5fca Clarify expand selector required hero checks (#7777) 2026-06-22 21:19:09 -07:00
adminandGitHub d56e0de9fd Clarify new hero province selection (#7778) 2026-06-22 21:18:40 -07:00
adminandGitHub 5132152334 Catch Shardok server exceptions by const reference (#7779) 2026-06-22 21:18:11 -07:00
adminandGitHub ef6ba7e585 Throw battle simulator CLI helper errors (#7780) 2026-06-22 21:17:51 -07:00
adminandGitHub 695a9d19d4 Return from AI performance argument parsing (#7781) 2026-06-22 21:17:30 -07:00
adminandGitHub 45682dff9b Parse real battle extractor integers with stoi (#7782) 2026-06-22 21:17:08 -07:00
adminandGitHub 47c5653bc0 Parse AI experiment id with from_chars (#7783) 2026-06-22 21:16:43 -07:00
adminandGitHub 033ebc802a Parse Shardok server config without strtok (#7784) 2026-06-22 21:16:12 -07:00
adminandGitHub 14e0fb66fb Drop unused random battle srand calls (#7785) 2026-06-22 21:15:28 -07:00
adminandGitHub 18d9756fc5 Make AI evaluation counter noncopyable (#7786) 2026-06-22 21:15:15 -07:00
adminandGitHub f976548fa1 Make attacker score timer noncopyable (#7787) 2026-06-22 21:14:58 -07:00
adminandGitHub a3fed6836d Include cstring in flatbuffer wrapper (#7788) 2026-06-22 21:14:38 -07:00
adminandGitHub 78d89f83da Scope remaining Shardok cpp tests to namespace (#7776) 2026-06-22 17:04:14 -07:00
adminandGitHub 66b93b0c83 Clarify tribute demand fallback (#7775) 2026-06-22 17:03:21 -07:00
adminandGitHub fe06ead2e2 Scope flee command tests to shardok namespace (#7767) 2026-06-22 17:03:09 -07:00
adminandGitHub 71e5b1b866 Clarify faction change victor lookups (#7774) 2026-06-22 17:02:38 -07:00
adminandGitHub 35ac2d6297 Scope ice action tests to shardok namespace (#7764) 2026-06-22 17:02:00 -07:00
adminandGitHub ff33d13822 Scope snow action tests to shardok namespace (#7762) 2026-06-22 17:01:47 -07:00
adminandGitHub ffb02d1d6c Scope build bridge command tests to shardok namespace (#7771) 2026-06-22 17:01:34 -07:00
adminandGitHub 86f7e85ce9 Clarify prisoner move required hero lookup (#7770) 2026-06-22 17:01:20 -07:00
adminandGitHub cd6318ba84 Clarify faction change required lookups (#7772) 2026-06-22 17:01:07 -07:00
adminandGitHub 7385b8ced2 Scope evacuate prisoners command tests to shardok namespace (#7773) 2026-06-22 17:00:54 -07:00
adminandGitHub a36ccd4157 Clarify captured hero recruitment requirements (#7765) 2026-06-22 17:00:40 -07:00
adminandGitHub 29a3459649 Scope blow bridge command tests to shardok namespace (#7766) 2026-06-22 17:00:27 -07:00
adminandGitHub 8dd7df989d Bind selected captured hero options once (#7763) 2026-06-22 17:00:09 -07:00
adminandGitHub d40b9a9ba6 Clarify captured hero return destination lookup (#7768) 2026-06-22 16:59:49 -07:00
adminandGitHub dd5d34f7aa Scope holy wave command tests to shardok namespace (#7769) 2026-06-22 16:58:50 -07:00
adminandGitHub 5685c8430f Clarify required CommandFactory province fields (#7761) 2026-06-22 16:58:37 -07:00
adminandGitHub 723a195d89 Scope ambush odds tests to shardok namespace (#7760) 2026-06-22 16:58:22 -07:00
adminandGitHub 68a20cba27 Scope percentile roll tests to shardok namespace (#7759) 2026-06-22 16:53:03 -07:00
adminandGitHub 27802e54e6 Move game settings test include inside guard (#7758) 2026-06-22 16:49:18 -07:00
adminandGitHub 3e91ca3bc3 Qualify standard library types in ShardokGameController (#7756) 2026-06-22 16:49:07 -07:00
adminandGitHub 5a4e98bd71 Remove unsafe runtime validator lookups (#7752) 2026-06-22 16:44:57 -07:00
adminandGitHub 225fe66afa Qualify standard library types in ShardokEngine (#7753) 2026-06-22 16:44:27 -07:00
adminandGitHub 619f0ca3e9 Report alliance destination lookup failures explicitly (#7754) 2026-06-22 16:44:09 -07:00
adminandGitHub fa3f4cd06b Report chronicle attack faction name lookup failures explicitly (#7755) 2026-06-22 16:43:54 -07:00
adminandGitHub 7694d2e676 Handle empty conjunction results without unsafe get (#7757) 2026-06-22 16:43:24 -07:00
adminandGitHub ea6792f21b Report quest creation lookup failures explicitly (#7751) 2026-06-22 16:42:10 -07:00
adminandGitHub 5e4dace755 Report battalion suitability type lookup failures explicitly (#7742) 2026-06-22 16:33:14 -07:00
adminandGitHub 276dbf5d42 Qualify vectors in hex map helpers (#7743)
* Qualify vectors in hex map helpers

* Qualify ZOC vector parameters
2026-06-22 16:32:57 -07:00
adminandGitHub 0ca2685bf3 Use province ruling faction helper for command availability (#7736) 2026-06-22 16:32:33 -07:00
adminandGitHub 4bab97ec62 Report arm troops availability cost lookup failures explicitly (#7741) 2026-06-22 16:32:11 -07:00
adminandGitHub 47bd5f6291 Qualify std iterators in Eagle gRPC server (#7740) 2026-06-22 16:31:57 -07:00
adminandGitHub 7ee1441b8e Report march neighbor lookup failures explicitly (#7739) 2026-06-22 16:31:43 -07:00
adminandGitHub 034270a119 Use province ruling faction helper for diplomacy availability (#7738) 2026-06-22 16:31:29 -07:00
adminandGitHub 9e4bff03d9 Qualify string constant in FearCommand (#7737) 2026-06-22 16:31:11 -07:00
adminandGitHub 29b9954954 Qualify std algorithms in AI unit scoring (#7735) 2026-06-22 16:30:48 -07:00
adminandGitHub 3aa62b9eeb Report quest fulfillment lookup failures explicitly (#7734) 2026-06-22 16:30:28 -07:00
adminandGitHub d8bd976204 Report unaffiliated hero province lookup failures explicitly (#7744) 2026-06-22 16:30:07 -07:00
adminandGitHub 3ccb8f5dc8 Qualify shared pointers in Eagle gRPC server (#7745) 2026-06-22 16:29:54 -07:00
adminandGitHub cb05990260 Report hero utils faction lookup failures explicitly (#7746) 2026-06-22 16:29:20 -07:00
adminandGitHub 8a2c21c88c Report combat unit suitability lookup failures explicitly (#7748) 2026-06-22 16:28:47 -07:00
adminandGitHub 204cd81462 Remove stale std aliases from Unit header (#7749) 2026-06-22 16:28:30 -07:00
adminandGitHub 71505b5419 Report manage prisoners return province lookup failures explicitly (#7750) 2026-06-22 16:27:59 -07:00
adminandGitHub 50e958e2e3 Use province ruling faction helper in actions (#7724) 2026-06-22 14:58:25 -07:00
adminandGitHub 3ffab58cbf Report LLM lookup failures explicitly (#7723) 2026-06-22 14:58:12 -07:00
adminandGitHub febb9e2fe0 Report organize troops lookup failures explicitly (#7722) 2026-06-22 14:57:52 -07:00
adminandGitHub b1af1b7254 Report quest ruling faction lookup failures explicitly (#7725) 2026-06-22 14:57:07 -07:00
adminandGitHub 32a4da1826 Report alliance resolution lookup failures explicitly (#7726) 2026-06-22 14:56:31 -07:00
adminandGitHub cc44eef174 Report invitation resolution lookup failures explicitly (#7727) 2026-06-22 14:55:41 -07:00
adminandGitHub c608a5890a Report diplomacy origin province lookup failures explicitly (#7728) 2026-06-22 14:53:43 -07:00
adminandGitHub 52253908eb Report AI diplomacy faction lookup failures explicitly (#7729) 2026-06-22 14:53:27 -07:00
adminandGitHub a50956cdb0 Report AI ransom hero lookup failures explicitly (#7730) 2026-06-22 14:53:14 -07:00
adminandGitHub ee55e4b216 Move deploy transport off Bazel runners (#7732) 2026-06-22 14:52:27 -07:00
adminandGitHub 9d041926d7 Preserve iOS scene manifest for TestFlight builds (#7733) 2026-06-22 14:51:36 -07:00
adminandGitHub 72ea2de25c Version Addressables by Unity editor (#7731) 2026-06-22 08:33:15 -07:00
adminandGitHub 4b559c2278 Report validated command lookup failures explicitly (#7720) 2026-06-22 07:38:14 -07:00
adminandGitHub 327ecf1aed Qualify std algorithms in hex map utils (#7721) 2026-06-22 07:37:26 -07:00
adminandGitHub 8fc893ce08 Qualify iterators in game state guesser (#7719) 2026-06-22 07:36:38 -07:00
adminandGitHub 0ec0e10c0f Remove stale iterator aliases from APD (#7718) 2026-06-22 07:34:02 -07:00
adminandGitHub 38c7a01743 Qualify std types in fixed APD test (#7717) 2026-06-22 07:33:50 -07:00
adminandGitHub a481b5ce60 Report recruit heroes ruling hero explicitly (#7716) 2026-06-22 07:32:35 -07:00
adminandGitHub 7786db1040 Qualify std types in Shardok games manager (#7715) 2026-06-22 07:30:48 -07:00
adminandGitHub 200aa0d319 Report exile vassal lookup failures explicitly (#7714) 2026-06-22 07:30:27 -07:00
adminandGitHub c8bb543907 Remove stale std aliases from AI headers (#7713) 2026-06-22 07:30:08 -07:00
adminandGitHub de7ecf45d1 Avoid repeated eligible gift get (#7712) 2026-06-22 07:27:18 -07:00
adminandGitHub f25298ed9e Report command factory lookup failures explicitly (#7711) 2026-06-22 07:27:01 -07:00
adminandGitHub d4ea2fbd93 Report arm troops lookup failures explicitly (#7710) 2026-06-22 07:24:24 -07:00
adminandGitHub 79c51fb21a Qualify STL containers in game state helpers (#7709) 2026-06-22 07:23:48 -07:00
adminandGitHub c57c9b5f18 Qualify std helpers in Shardok production code (#7708) 2026-06-22 07:20:09 -07:00
adminandGitHub 823cd73d33 Report missing hero descriptions explicitly (#7707) 2026-06-22 07:19:50 -07:00
adminandGitHub 54db4b6e34 Report missing suppress beasts effect text (#7706) 2026-06-22 07:18:56 -07:00
adminandGitHub 112399d67c Qualify smart pointer helpers in Shardok tests (#7705) 2026-06-22 07:18:42 -07:00
adminandGitHub 01b80d8798 Report missing province ruling faction explicitly (#7704) 2026-06-22 07:18:14 -07:00
adminandGitHub f6a3f36a13 Remove unused vector aliases in Shardok tests (#7703) 2026-06-22 07:18:01 -07:00
adminandGitHub d674e23987 Report missing common unit faction explicitly (#7702) 2026-06-22 07:17:46 -07:00
adminandGitHub 51aebf5e40 Qualify std vector in Shardok tests (#7701) 2026-06-22 07:15:59 -07:00
adminandGitHub 2ec6d2f1d7 Report missing seek-more-leaders faction explicitly (#7700) 2026-06-22 07:15:23 -07:00
adminandGitHub 9f94917a2d Report required lookup failures explicitly (#7687) 2026-06-22 07:08:10 -07:00
adminandGitHub 06f2104750 Report new game lookup failures explicitly (#7688) 2026-06-22 07:07:53 -07:00
adminandGitHub b1e1e36236 Report empty in-memory history explicitly (#7690) 2026-06-22 07:06:02 -07:00
adminandGitHub 9eb2056ac6 Report map load failures with context (#7691) 2026-06-22 07:05:40 -07:00
adminandGitHub 0c3cb22e83 Report unknown hex terrain types explicitly (#7692) 2026-06-22 07:05:24 -07:00
adminandGitHub 92f27ba273 Report missing faction utility lookups explicitly (#7693) 2026-06-22 07:05:04 -07:00
adminandGitHub 47d682e7c1 Report unaffiliated phase lookup failures explicitly (#7694) 2026-06-22 07:04:23 -07:00
adminandGitHub 75d84b67ff Report missing action ruling factions explicitly (#7695) 2026-06-22 07:03:52 -07:00
adminandGitHub be8f5ca5da Report missing beast fallback explicitly (#7696) 2026-06-22 07:03:24 -07:00
adminandGitHub c7910a6183 Report missing withdrawing flee province explicitly (#7697) 2026-06-22 07:03:08 -07:00
renovate[bot]andGitHub 98afc28762 Update alpine_linux:3.24 Docker digest to 28bd5fe (#7698) 2026-06-22 06:17:57 -07:00
adminandGitHub 4cd1349a5a Fix AI victory score test std types (#7699) 2026-06-22 05:44:05 -07:00
adminandGitHub cebac64264 Avoid unsafe diplomacy hero fallback head (#7668) 2026-06-21 21:48:23 -07:00
adminandGitHub 172125e03b Avoid unsafe AI game arg head (#7667) 2026-06-21 21:48:09 -07:00
adminandGitHub 15006091b4 Report missing AI commands explicitly (#7670) 2026-06-21 21:46:19 -07:00
adminandGitHub 2175f1d957 Report missing diplomacy offers explicitly (#7671) 2026-06-21 21:45:56 -07:00
adminandGitHub d445cbfe25 Avoid unsafe ruling hero head (#7673) 2026-06-21 21:44:07 -07:00
adminandGitHub 5fc56c342d Avoid unsafe shuffle tail heads (#7674) 2026-06-21 21:43:53 -07:00
adminandGitHub e5350d03a8 Report missing recon targets explicitly (#7676) 2026-06-21 21:43:40 -07:00
adminandGitHub 097daf3dc1 Report missing province ruling heroes explicitly (#7678) 2026-06-21 21:43:25 -07:00
adminandGitHub 3c3dc85187 Avoid standard aliases in command factory headers (#7675)
* Avoid standard aliases in command factory headers

* Fix available commands std type qualification
2026-06-21 21:42:47 -07:00
adminandGitHub a775a667f6 Remove unused vector alias from unit filter (#7677) 2026-06-21 21:41:27 -07:00
adminandGitHub a1a7a69aeb Remove unused vector alias from game settings (#7679) 2026-06-21 21:41:08 -07:00
adminandGitHub ff549ab337 Qualify shared pointers in cache and settings headers (#7686)
* Qualify shared pointers in cache and settings headers

* Fix map info test shared pointer spelling
2026-06-21 21:40:51 -07:00
adminandGitHub 464651503e Avoid vector aliases in utility headers (#7672) 2026-06-21 21:36:38 -07:00
adminandGitHub 2f93250c8e Avoid vector aliases in AI headers (#7669) 2026-06-21 21:36:14 -07:00
adminandGitHub 22ce7ba750 Report missing tutorial ruling heroes explicitly (#7680) 2026-06-21 21:35:54 -07:00
adminandGitHub acbb09f6a1 Qualify vector usage in action point distances (#7681) 2026-06-21 21:35:40 -07:00
adminandGitHub 900dd23ae7 Use explicit backstory text id accessors (#7683) 2026-06-21 21:35:14 -07:00
adminandGitHub 0f391776aa Qualify vector usage in duel utilities (#7682) 2026-06-21 21:34:42 -07:00
adminandGitHub 817fb613ab Clean up library implementation std aliases (#7684) 2026-06-21 21:34:16 -07:00
adminandGitHub 2f57ea0160 Qualify std types in available commands header (#7685) 2026-06-21 21:33:59 -07:00
adminandGitHub f5d5aedf9d Avoid std aliases in map loader header (#7647) 2026-06-21 19:56:47 -07:00
adminandGitHub c5114e9bf1 Avoid unsafe OpenAI choice head (#7648) 2026-06-21 19:56:35 -07:00
adminandGitHub 7514aed437 Remove unused vector alias from unit filter header (#7649) 2026-06-21 19:56:19 -07:00
adminandGitHub f4d381c9d4 Report missing headshot output path (#7650) 2026-06-21 19:56:09 -07:00
adminandGitHub 8698036f7c Avoid unsafe migration version lookup (#7651) 2026-06-21 19:55:51 -07:00
adminandGitHub 21f04cfd46 Avoid string alias in shardok exception header (#7652) 2026-06-21 19:55:30 -07:00
adminandGitHub 074cf62cc9 Avoid unsafe recruit province head (#7653) 2026-06-21 19:52:34 -07:00
adminandGitHub a66f833c90 Avoid unsafe aftermath auto-resolve head (#7654) 2026-06-21 19:51:53 -07:00
adminandGitHub d704d23d39 Avoid unsafe aftermath hostile error head (#7655) 2026-06-21 19:48:34 -07:00
adminandGitHub e2570aec4a Use explicit vectors in implementation files (#7656) 2026-06-21 19:48:06 -07:00
adminandGitHub 6b97fde9ab Report missing tutorial battle explicitly (#7657) 2026-06-21 19:47:50 -07:00
adminandGitHub 30adf1bcbf Avoid vector aliases in APD headers (#7658) 2026-06-21 19:46:25 -07:00
adminandGitHub f205b49a54 Avoid string alias in action result helper header (#7659) 2026-06-21 19:46:06 -07:00
adminandGitHub 5c7e18696e Avoid unsafe quest chooser hero heads (#7660) 2026-06-21 19:45:50 -07:00
adminandGitHub c78b00b0fc Avoid vector alias in coords set header (#7661) 2026-06-21 19:45:19 -07:00
adminandGitHub b05d6e1e1b Avoid unsafe recon candidate head (#7662) 2026-06-21 19:45:05 -07:00
adminandGitHub da0bce655c Avoid standard aliases in shardok action headers (#7663) 2026-06-21 19:44:41 -07:00
adminandGitHub d4dcbcc9d5 Report missing uncontested conqueror explicitly (#7664) 2026-06-21 19:44:26 -07:00
adminandGitHub f132e5b87b Remove unused string alias from hero header (#7665) 2026-06-21 19:42:05 -07:00
adminandGitHub 872475e82b Avoid map alias in unit header (#7666) 2026-06-21 19:41:48 -07:00
adminandGitHub 66d062bfda Avoid unsafe pregenerated hero head (#7631) 2026-06-21 15:55:51 -07:00
adminandGitHub a514ceb62f Cache repeated loop bounds (#7632) 2026-06-21 15:55:38 -07:00
adminandGitHub b0d8a5bc41 Avoid unsafe province distance frontier lookup (#7633) 2026-06-21 15:55:19 -07:00
adminandGitHub 8f4bb5130e Avoid namespace import in map info implementation (#7634) 2026-06-21 15:55:03 -07:00
adminandGitHub 9386589558 Avoid unsafe attack decision unit head (#7638) 2026-06-21 15:54:47 -07:00
adminandGitHub 5945289a81 Use explicit std types in map loader (#7639) 2026-06-21 15:54:31 -07:00
adminandGitHub 53346cc85b Use explicit std types in game over response populator (#7640) 2026-06-21 15:26:45 -07:00
adminandGitHub a6fad3ac4e Use explicit string type in shardok controller (#7641) 2026-06-21 15:26:32 -07:00
adminandGitHub cb7e2b5d5c Use explicit std types in shardok server main (#7642) 2026-06-21 15:26:15 -07:00
adminandGitHub 0f764416a3 Avoid namespace import in AI performance runner (#7643) 2026-06-21 15:25:56 -07:00
adminandGitHub c9e0012bf7 Avoid namespace import in random battle runner (#7644) 2026-06-21 15:25:37 -07:00
adminandGitHub 457f2a677a Avoid vector alias in game update receiver header (#7645) 2026-06-21 15:25:10 -07:00
adminandGitHub ced56ee41f Avoid string alias in server configuration header (#7646) 2026-06-21 15:24:56 -07:00
adminandGitHub 4e910f1f22 Report missing migration save files explicitly (#7637) 2026-06-21 15:16:53 -07:00
adminandGitHub 34bceebace Remove unused registrar header string import (#7636) 2026-06-21 15:16:34 -07:00
adminandGitHub b4f802ba77 Avoid unsafe LLM update group bounds (#7635) 2026-06-21 15:16:15 -07:00
adminandGitHub 3d9d464870 Avoid unsafe battle aftermath claimant head (#7630) 2026-06-21 15:04:00 -07:00
adminandGitHub a7e21427f9 Avoid namespace imports in map info header (#7627) 2026-06-21 15:03:43 -07:00
adminandGitHub bdbf10294b Avoid namespace import in map info runner (#7629) 2026-06-21 15:02:57 -07:00
adminandGitHub 8588430cf0 Avoid unsafe TSV header lookup (#7628) 2026-06-21 15:01:15 -07:00
adminandGitHub d920bb5c17 Report utility TSV load failures explicitly (#7626) 2026-06-21 14:59:01 -07:00
adminandGitHub 64fe9bf59d Cache map starting position count in tests (#7625) 2026-06-21 14:58:23 -07:00
adminandGitHub 101b0bf41f Cache hex map terrain count (#7624) 2026-06-21 14:54:31 -07:00
adminandGitHub 019865b5ee Reserve converted victory conditions (#7623) 2026-06-21 14:54:16 -07:00
adminandGitHub 799d34a1f1 Avoid unsafe Gemini response heads (#7622) 2026-06-21 14:54:03 -07:00
adminandGitHub 951d533ed3 Avoid unsafe rate limit header heads (#7619) 2026-06-21 14:53:49 -07:00
adminandGitHub 459eb0ef7b Cache Eagle interface player counts (#7621) 2026-06-21 14:53:15 -07:00
adminandGitHub f710105bd9 Cache action result repeated field counts (#7620) 2026-06-21 14:53:01 -07:00
adminandGitHub 4998e3ce40 Cache changed unit counts in gtest helpers (#7618) 2026-06-21 14:48:32 -07:00
adminandGitHub d739b374a4 Cache AI battle protobuf unit counts (#7617) 2026-06-21 14:48:15 -07:00
adminandGitHub bd61ebcbb6 Use flatbuffer-sized production knowledge indices (#7616) 2026-06-21 14:47:53 -07:00
adminandGitHub 3963275092 Report hero backstory load failures explicitly (#7569) 2026-06-21 14:40:52 -07:00
adminandGitHub a796e0fe67 Avoid unsafe battle request error message lookup (#7583) 2026-06-21 14:40:35 -07:00
adminandGitHub 52ac61fa1d Qualify flatbuffer wrapper abort calls (#7606) 2026-06-21 14:40:13 -07:00
adminandGitHub 29ca7665b1 Use size_t for AI integration unit loops (#7607) 2026-06-21 14:39:25 -07:00
adminandGitHub d9bdb814f4 Use size_t for MCTS reserve unit loops (#7608) 2026-06-21 14:39:09 -07:00
adminandGitHub 3dfb094a08 Use size_t for optimized score test unit loops (#7609) 2026-06-21 14:38:51 -07:00
adminandGitHub 7ce1818a7f Use flatbuffer-sized unit knowledge indices (#7610) 2026-06-21 14:37:45 -07:00
adminandGitHub 6e6eff7991 Use flatbuffer-sized retreat factory knowledge index (#7615) 2026-06-21 14:37:04 -07:00
adminandGitHub aa9c98f081 Use flatbuffer-sized game start knowledge indices (#7611) 2026-06-21 14:36:00 -07:00
adminandGitHub cdf0ad45f0 Use flatbuffer-sized move test knowledge index (#7612) 2026-06-21 14:35:14 -07:00
adminandGitHub 1b97b6673f Use flatbuffer-sized melee test knowledge index (#7613) 2026-06-21 14:34:10 -07:00
adminandGitHub 721d432bc6 Use flatbuffer-sized flee factory knowledge index (#7614) 2026-06-21 14:33:01 -07:00
adminandGitHub e513a784eb Report missing start game heroes explicitly (#7571) 2026-06-21 14:30:42 -07:00
adminandGitHub ff8da78992 Report missing tutorial battle players explicitly (#7577) 2026-06-21 14:30:11 -07:00
adminandGitHub 84a69ce982 Match prisoner leader faction options (#7572) 2026-06-21 14:28:27 -07:00
adminandGitHub 7d743f8678 Report missing proto converter fields explicitly (#7575) 2026-06-21 14:28:13 -07:00
adminandGitHub 3f24e15392 Bind custom battle human updates explicitly (#7578) 2026-06-21 14:27:49 -07:00
adminandGitHub 0f86bb6874 Match engine command lookup explicitly (#7580) 2026-06-21 14:27:29 -07:00
adminandGitHub 05ee7a2ab2 Report unruled defend flee provinces explicitly (#7582) 2026-06-21 14:25:46 -07:00
adminandGitHub b9321433d0 Report fixed hero load failures explicitly (#7567) 2026-06-21 14:24:29 -07:00
adminandGitHub 62791401e9 Use streams for raise dead setup output (#7581) 2026-06-21 14:18:59 -07:00
adminandGitHub 4d28bba9cc Use streams for MCTS decision investigation output (#7579) 2026-06-21 14:18:42 -07:00
adminandGitHub 1f3660e1f3 Use streams for reserve combat phase output (#7576) 2026-06-21 14:18:24 -07:00
adminandGitHub 95ff44e205 Use streams for reserve setup diagnostics (#7574) 2026-06-21 14:18:08 -07:00
adminandGitHub 987a164092 Report missing hostile army neighbor explicitly (#7584) 2026-06-21 14:17:51 -07:00
adminandGitHub 1b5b11fd5b Match pending aftermath info explicitly (#7586) 2026-06-21 14:17:35 -07:00
adminandGitHub ddcde52e27 Bind validated command options explicitly (#7587) 2026-06-21 14:12:54 -07:00
adminandGitHub ea2da84535 Report missing declined quest hero explicitly (#7589) 2026-06-21 14:12:04 -07:00
adminandGitHub abed8ee574 Bind control weather option explicitly (#7590) 2026-06-21 14:09:50 -07:00
adminandGitHub 556c59a9c7 Report S3 credential load failures explicitly (#7566) 2026-06-21 14:09:39 -07:00
adminandGitHub ad2d085b24 Stabilize sqlite history upload snapshot test (#7605) 2026-06-21 14:08:50 -07:00
adminandGitHub d7209102b8 Report missing battalion types explicitly (#7592) 2026-06-21 13:25:39 -07:00
adminandGitHub 14e1ba6795 Avoid unwrapping province distances (#7594) 2026-06-21 13:25:25 -07:00
adminandGitHub 65b56081ce Report beast info load failures explicitly (#7595) 2026-06-21 13:25:00 -07:00
adminandGitHub eafb51cbfa Report name generator load failures explicitly (#7564) 2026-06-21 13:24:37 -07:00
adminandGitHub 3444ae296e Report missing ransom offer prisoner explicitly (#7597) 2026-06-21 13:24:23 -07:00
adminandGitHub 4f2fff5da6 Use streams for MCTS start fire test output (#7565) 2026-06-21 13:23:50 -07:00
adminandGitHub 144483a45d Use streams for start fire cost explanation (#7573) 2026-06-21 13:21:53 -07:00
adminandGitHub 53345b2bb0 Use streams for MCTS rain fire output (#7568) 2026-06-21 13:17:20 -07:00
adminandGitHub 5d6bc429b0 Use streams for start fire cost summary (#7570) 2026-06-21 13:17:00 -07:00
adminandGitHub a4faf61613 Report missing divine acting province explicitly (#7599) 2026-06-21 13:16:41 -07:00
adminandGitHub fdd516598d Report missing apprehended outlaw explicitly (#7601) 2026-06-21 13:16:29 -07:00
adminandGitHub 70da6990c4 Match optional date protos explicitly (#7602) 2026-06-21 13:13:12 -07:00
adminandGitHub e8b18d0805 Use streams for raise dead filter output (#7585) 2026-06-21 13:12:03 -07:00
adminandGitHub 1ca9e8678c Use streams for MCTS investigation intro output (#7588) 2026-06-21 13:11:29 -07:00
adminandGitHub 94670149fe Use streams for MCTS end-turn score output (#7591) 2026-06-21 13:11:14 -07:00
adminandGitHub 8e253c2972 Use streams for MCTS start-fire failure output (#7596) 2026-06-21 13:11:00 -07:00
adminandGitHub 6e15c6d9cb Use streams for MCTS start-fire success output (#7593) 2026-06-21 13:10:21 -07:00
adminandGitHub ca00e90ad6 Use streams for AI command mismatch diagnostics (#7600) 2026-06-21 13:10:07 -07:00
adminandGitHub b1d76ea661 Use streams for MCTS path comparison output (#7598) 2026-06-21 13:09:52 -07:00
adminandGitHub 059d428cfa Make Shardok player id conversions explicit (#7603) 2026-06-21 13:08:19 -07:00
adminandGitHub a4b50eb357 Tighten map info index handling (#7604) 2026-06-21 13:07:43 -07:00
adminandGitHub 0dfd5aa11d Use streams for critical fire score output (#7563) 2026-06-21 10:32:50 -07:00
adminandGitHub 2b63ccd145 Report name format parse failures explicitly (#7562) 2026-06-21 10:32:38 -07:00
adminandGitHub 97cec5ec38 Add Shardok Linux ARM64 PR build (#7561) 2026-06-21 10:29:17 -07:00
adminandGitHub 31fb5a3616 Avoid partial battalion access in riot command errors (#7560) 2026-06-21 10:27:28 -07:00
adminandGitHub 7b7130d67d Use resource handling for Shardok map info (#7552) 2026-06-21 10:25:49 -07:00
adminandGitHub 6707ad55ff Report missing beasts event explicitly (#7559) 2026-06-21 10:25:37 -07:00
adminandGitHub 89aa73b63a Use resource handling for image paths (#7554) 2026-06-21 10:25:21 -07:00
adminandGitHub bb7453a002 Avoid partial Option access in HeroNameCache (#7555) 2026-06-21 10:25:09 -07:00
adminandGitHub 8cea29dcb0 Avoid partial command access in early AI (#7556) 2026-06-21 10:24:55 -07:00
adminandGitHub d5d74ec8d6 Avoid partial command access in mid-game AI (#7557) 2026-06-21 10:24:32 -07:00
adminandGitHub 9b000b94ac Report missing Shardok map info explicitly (#7558) 2026-06-21 10:24:13 -07:00
adminandGitHub 82ed9fcc83 Include array in CombatDamage (#7553) 2026-06-21 10:23:51 -07:00
adminandGitHub cd53283935 Use resource handling for LLM usage loads (#7551) 2026-06-21 10:17:08 -07:00
adminandGitHub 3faddf76f8 Use resource handling in JsonUtils (#7550) 2026-06-21 10:15:43 -07:00
adminandGitHub 6e24ae2f4f Use streams for MCTS archery debug output (#7538) 2026-06-21 10:10:27 -07:00
adminandGitHub 66912580c8 Use streams in C++ debug headers (#7540) 2026-06-21 10:10:13 -07:00
adminandGitHub 219dd830e1 Use streams for MCTS score sequence output (#7541) 2026-06-21 10:09:51 -07:00
adminandGitHub 3d5cdcc78e Use streams for combat damage strings (#7542) 2026-06-21 10:09:32 -07:00
adminandGitHub ab1d43fae3 Use streams for MCTS minimax debug output (#7543) 2026-06-21 10:09:19 -07:00
adminandGitHub 2d51f3c23d Use resource handling for name format reads (#7548) 2026-06-21 10:09:03 -07:00
adminandGitHub f9447cf7aa Use resource handling for map info migration (#7549) 2026-06-21 10:08:42 -07:00
adminandGitHub c6ed0bfc7d Use streams for AI choice diagnostics (#7534) 2026-06-21 10:06:57 -07:00
adminandGitHub 2d4621b1a6 Use pattern matching for parser literals (#7547) 2026-06-21 10:05:48 -07:00
adminandGitHub 3bf929b4a5 Use streams for MCTS fire score analysis (#7544) 2026-06-21 10:02:45 -07:00
adminandGitHub ebed01ba86 Use streams for abstract MCTS diagnostics (#7545) 2026-06-21 10:02:29 -07:00
adminandGitHub 880d005347 Use streams for Alah fire score analysis (#7546) 2026-06-21 10:02:12 -07:00
adminandGitHub e8ef216de8 Use streams for common C++ diagnostics (#7539) 2026-06-21 10:01:47 -07:00
adminandGitHub baa453594a Delete old dropped games automatically (#7535) 2026-06-21 09:46:01 -07:00
adminandGitHub 9c767d5c49 Use streams for MCTS score placement diagnostics (#7537) 2026-06-21 09:45:15 -07:00
adminandGitHub e0892bd2fa Use streams for MCTS setup diagnostics (#7536) 2026-06-21 09:44:55 -07:00
adminandGitHub 315be7c465 Use streams for ID AI performance logs (#7533) 2026-06-21 09:42:33 -07:00
adminandGitHub 9d34dc0954 Use streams for AI debug dump messages (#7532) 2026-06-21 09:42:14 -07:00
adminandGitHub bdedd43e3c Use streams for score timing diagnostic (#7531) 2026-06-21 09:42:02 -07:00
adminandGitHub 97e779675e Use streams in gtest contains diagnostics (#7528) 2026-06-21 09:41:43 -07:00
adminandGitHub 2ef4216886 Use cerr for flatbuffer allocator failures (#7530) 2026-06-21 09:41:28 -07:00
adminandGitHub 8c9764a57e Use cerr for APD thread diagnostic (#7529) 2026-06-21 09:41:09 -07:00
adminandGitHub eb94eb1a6d Use streams in available commands test diagnostics (#7527) 2026-06-21 09:40:53 -07:00
adminandGitHub 12e3fb99eb Keep dropped games in existing storage (#7525) 2026-06-21 09:32:34 -07:00
adminandGitHub 2bd90a26a8 Remove client text getTexts returns (#7526) 2026-06-21 09:32:04 -07:00
adminandGitHub 24db3a4cb1 Use streams for AI command count logging (#7524) 2026-06-21 09:31:02 -07:00
adminandGitHub 0d05ea1730 Use streams for iterative deepening diagnostics (#7523) 2026-06-21 09:30:45 -07:00
adminandGitHub 9c13b3c1bf Use streams for AI flee debug output (#7522) 2026-06-21 09:27:34 -07:00
adminandGitHub 492d9d16eb Remove StringConstructionParser returns (#7507) 2026-06-21 09:20:30 -07:00
adminandGitHub c4cd0eb683 Remove broken action point distance timer (#7509) 2026-06-21 09:15:53 -07:00
adminandGitHub 287f7b9818 Fix random battle runner build (#7521) 2026-06-21 09:15:35 -07:00
adminandGitHub 15b5a3e0c1 Remove SQLite client text guard returns (#7505) 2026-06-21 09:15:15 -07:00
adminandGitHub cf93c43ef2 Remove AI time budget debug print (#7506) 2026-06-21 09:14:55 -07:00
adminandGitHub 380c24537b Remove stale undead morale diagnostic (#7508) 2026-06-21 09:12:31 -07:00
adminandGitHub 6200bc7e02 Remove SqliteHistory flush return (#7510) 2026-06-21 09:12:05 -07:00
adminandGitHub e92465c894 Use explicit chrono imports in distance test (#7511) 2026-06-21 09:11:52 -07:00
adminandGitHub 6f432d221a Remove GameController debug return (#7512) 2026-06-21 09:11:28 -07:00
adminandGitHub b8ddca4e76 Route action point cache stats through streams (#7513) 2026-06-21 09:11:12 -07:00
adminandGitHub f343ee9f7f Send battalion type parse errors to stderr (#7514) 2026-06-21 09:10:53 -07:00
adminandGitHub 9a8c4c6fdd Remove GameController post battle return (#7515) 2026-06-21 09:10:38 -07:00
adminandGitHub 96f2dc0454 Send hex map migrator errors to stderr (#7516) 2026-06-21 09:10:21 -07:00
adminandGitHub e0988ef186 Remove GamesManager cache invalidation returns (#7517) 2026-06-21 09:07:20 -07:00
adminandGitHub a154174fa1 Remove broken Shardok inspector target (#7496) 2026-06-21 09:05:50 -07:00
adminandGitHub 042861f065 Remove GamesManager S3 archive return (#7520) 2026-06-21 09:04:54 -07:00
adminandGitHub 0f776af050 Remove GamesManager flush marker returns (#7518) 2026-06-21 09:04:18 -07:00
adminandGitHub 166e50924f Remove GamesManager LLM response return (#7519) 2026-06-21 09:04:00 -07:00
adminandGitHub efb34ede56 Remove Postgres client text guard returns (#7504) 2026-06-21 09:02:56 -07:00
adminandGitHub 22b55eb223 Allow routing transposition table stats output (#7503) 2026-06-21 08:41:33 -07:00
adminandGitHub f5d9027206 Allow routing MCTS cache stats output (#7502) 2026-06-21 08:39:52 -07:00
adminandGitHub 9dcbeff21a Avoid endl flushes in AI integration tests (#7501) 2026-06-21 08:39:29 -07:00
adminandGitHub 0cc7c4d0a2 Remove SQLite download recovery return (#7500) 2026-06-21 08:36:29 -07:00
adminandGitHub b0ca65b720 Use unique_ptr in attacker strategy selector test (#7499) 2026-06-21 08:36:09 -07:00
adminandGitHub e3eb3eb7bf Own nonstandard hex map adjacency cache entries (#7498) 2026-06-21 08:34:22 -07:00
adminandGitHub b8b2c92227 Remove SQLite legacy guard returns (#7497) 2026-06-21 08:33:33 -07:00
adminandGitHub 424dde5da6 Remove SQLite migration pending return (#7495) 2026-06-21 08:33:00 -07:00
adminandGitHub ce7e636987 Remove SQLite replay empty range return (#7494) 2026-06-21 08:31:12 -07:00
adminandGitHub c51f61ba56 Remove expand command selector return (#7492) 2026-06-21 08:28:42 -07:00
adminandGitHub 91623bcf7a Remove broken corrupt game checker (#7491) 2026-06-21 08:28:14 -07:00
adminandGitHub 34e14a3521 Check hex map migrator parse results (#7490) 2026-06-21 08:27:52 -07:00
adminandGitHub 6c1a79b342 Remove GameController AI guard returns (#7489) 2026-06-21 08:27:25 -07:00
adminandGitHub 6af5014866 Remove OAuth handler early returns (#7488) 2026-06-21 08:26:59 -07:00
adminandGitHub 16f53dc1b1 Use make_shared for battalion types (#7487) 2026-06-21 08:26:40 -07:00
adminandGitHub df09fe0684 Remove authorization interceptor early returns (#7486) 2026-06-21 08:26:09 -07:00
adminandGitHub e765764cea Remove SQLite shardok row early returns (#7485) 2026-06-21 08:25:13 -07:00
adminandGitHub 2e63bcb5c3 Write flatbuffer allocator diagnostics to stderr (#7484) 2026-06-21 08:23:55 -07:00
adminandGitHub e231f0c34e Use streams for Shardok controller diagnostics (#7483) 2026-06-21 08:23:35 -07:00
adminandGitHub 8155fe7b3f Remove Postgres shardok row early returns (#7482) 2026-06-21 08:23:17 -07:00
adminandGitHub 6fe1bcdff2 Use streams for map info diagnostics (#7481) 2026-06-21 08:23:00 -07:00
adminandGitHub b6b488bd46 Remove changed faction writer early returns (#7480) 2026-06-21 08:15:09 -07:00
adminandGitHub d97cc0cadc Use streams for Shardok server diagnostics (#7479) 2026-06-21 08:13:19 -07:00
adminandGitHub 2a3c742be9 Remove SQLite to Postgres migration admin flow (#7478) 2026-06-21 08:11:06 -07:00
adminandGitHub cdee86dbcb Remove S3 startup save sync (#7475) 2026-06-21 08:10:47 -07:00
adminandGitHub 60264e2bfc Make Shardok numeric casts explicit (#7477) 2026-06-21 08:09:58 -07:00
adminandGitHub 19fb315500 Make remaining unit byte casts explicit (#7476) 2026-06-21 08:09:34 -07:00
adminandGitHub 66710659c9 Tighten Shardok ownership cleanup (#7472) 2026-06-21 07:57:14 -07:00
adminandGitHub de5a34c485 Make game state dumper casts explicit (#7474) 2026-06-21 07:54:29 -07:00
adminandGitHub e0c7ba3874 Make Shardok casts explicit (#7473) 2026-06-21 07:54:04 -07:00
adminandGitHub 9e58fbee56 Remove S3 sync early returns (#7471) 2026-06-21 05:59:23 -07:00
adminandGitHub 5315941f88 Remove sqlite postgres migration return (#7470) 2026-06-21 05:59:01 -07:00
adminandGitHub 3ffeb0250b Remove battle progress early return (#7469) 2026-06-21 05:58:39 -07:00
adminandGitHub 451e6bb406 Avoid unnecessary endl flushes (#7465) 2026-06-21 05:58:06 -07:00
adminandGitHub 58d680bd59 Remove tutorial hero factions early return (#7464) 2026-06-20 21:47:23 -07:00
adminandGitHub 79eb708adb Normalize Shardok header guards (#7463) 2026-06-20 21:44:58 -07:00
adminandGitHub 1282e62781 Remove AsyncS3Persister early returns (#7466) 2026-06-20 21:44:29 -07:00
adminandGitHub 4af501feee Make gtest helper casts explicit (#7467) 2026-06-20 21:44:07 -07:00
adminandGitHub ef7c24f6de Make game over response casts explicit (#7468) 2026-06-20 21:43:29 -07:00
adminandGitHub 62edd02a22 Remove attack decision early returns (#7462) 2026-06-20 21:43:10 -07:00
adminandGitHub 6f22edc8f3 Remove GameMigrator early returns (#7461) 2026-06-20 18:43:08 -07:00
adminandGitHub 27bba1c2da Replace GNU alignment attributes with alignas (#7460) 2026-06-20 17:59:59 -07:00
adminandGitHub c95bc22f0e Remove simple Unit early returns (#7459) 2026-06-20 17:45:04 -07:00
adminandGitHub 844bab253d Rename test settings helper global (#7458) 2026-06-20 17:42:44 -07:00
adminandGitHub b8372487cf Remove backstory recovery early return (#7457) 2026-06-20 17:40:46 -07:00
adminandGitHub 8cc38466e6 Replace AI client flee debug macro (#7456) 2026-06-20 17:40:25 -07:00
adminandGitHub 14935b1a52 Remove march availability early returns (#7455) 2026-06-20 17:37:29 -07:00
adminandGitHub f7b14cd9ba Replace coords set index check macro (#7454) 2026-06-20 17:36:03 -07:00
adminandGitHub 9d1e61ef8d Remove defend availability early return (#7453) 2026-06-20 17:33:01 -07:00
adminandGitHub 8c912c1b3e Replace SuppressBeastsCommand type test (#7450) 2026-06-20 17:32:41 -07:00
adminandGitHub 1ff6ae0195 Replace test type predicates (#7452) 2026-06-20 17:32:12 -07:00
adminandGitHub 641bf456fa Replace iterative deepening debug macro (#7451) 2026-06-20 17:28:10 -07:00
adminandGitHub f21eaabb15 Modernize disabled C++ logging toggles (#7449) 2026-06-20 17:25:13 -07:00
adminandGitHub 7d38336e67 Remove EngineImpl applyActionResults return (#7448) 2026-06-20 17:23:56 -07:00
adminandGitHub f37ec78d10 Remove auth early returns (#7447) 2026-06-20 17:23:02 -07:00
adminandGitHub eb0f92dcd0 Remove simple utility early returns (#7446) 2026-06-20 17:21:20 -07:00
adminandGitHub ee06235cf4 Remove quest chooser early returns (#7445) 2026-06-20 17:20:53 -07:00
adminandGitHub 48dfe9054d Replace simple isInstanceOf predicates (#7443) 2026-06-20 17:20:33 -07:00
adminandGitHub c7e39e67aa Remove simple availability early returns (#7444) 2026-06-20 17:20:07 -07:00
adminandGitHub 86e51a11b7 Replace AI evaluator control macros with constexpr (#7442) 2026-06-20 16:35:44 -07:00
adminandGitHub 3c0e98280d Rename reserved test settings include guard (#7441) 2026-06-20 16:35:15 -07:00
adminandGitHub c54c9f7955 Remove Gemini parser early returns (#7440) 2026-06-20 16:34:50 -07:00
adminandGitHub 614c404bf4 Replace test roll macros with constexpr constants (#7439) 2026-06-20 16:15:09 -07:00
adminandGitHub c1ebcd49fc Replace combat macros with constexpr constants (#7437) 2026-06-20 16:11:00 -07:00
adminandGitHub 49551614fe Default simple C++ special members (#7436) 2026-06-20 16:10:21 -07:00
adminandGitHub b2b8603f23 Generalize FunctionalRandom sequence helpers (#7438) 2026-06-20 16:09:41 -07:00
adminandGitHub 4a6ecda8f8 Remove SettingsLoader asInstanceOf generation (#7432) 2026-06-20 15:58:17 -07:00
adminandGitHub b65d3c25e1 Remove observer asInstanceOf cast (#7427) 2026-06-20 15:56:58 -07:00
adminandGitHub 65f5ec1c38 Remove domain asInstanceOf casts (#7426) 2026-06-20 15:56:43 -07:00
adminandGitHub b8b225eb84 Rename reserved AI evaluator identifiers (#7433) 2026-06-20 14:47:37 -07:00
adminandGitHub c20239b9e8 Normalize static const declarations (#7434) 2026-06-20 14:47:12 -07:00
adminandGitHub a8df7228b3 Mark single-argument constructors explicit (#7431) 2026-06-20 14:46:47 -07:00
adminandGitHub b8ebc0efd8 Remove remaining inline body semicolons (#7428) 2026-06-20 14:46:21 -07:00
adminandGitHub 5ee86faf80 Remove redundant command constructor semicolons (#7425) 2026-06-20 14:44:41 -07:00
adminandGitHub 3d6524b2fa Remove key asInstanceOf casts (#7423) 2026-06-20 14:21:03 -07:00
adminandGitHub 82b671b234 Remove redundant factory constructor semicolons (#7422) 2026-06-20 14:20:16 -07:00
adminandGitHub 3ad6de784f Remove JSON asInstanceOf casts (#7421) 2026-06-20 14:19:46 -07:00
adminandGitHub 6a9e1d7ad7 Remove redundant virtual from retreat command (#7420) 2026-06-20 14:17:46 -07:00
adminandGitHub 84f182d74f Fail iOS build when Addressables runtime data is missing (#7424) 2026-06-20 14:16:26 -07:00
adminandGitHub d08402c502 Remove stale unused casts from Shardok server main (#7417) 2026-06-20 14:11:14 -07:00
adminandGitHub b6a7c38a70 Tidy unused MCTS parameters (#7419) 2026-06-20 14:10:54 -07:00
adminandGitHub 9513c1e404 Remove final test asInstanceOf uses (#7418) 2026-06-20 14:09:33 -07:00
adminandGitHub d67d8b9784 Use inside in attack decision hostile army fixtures (#7415) 2026-06-20 14:04:49 -07:00
adminandGitHub fdc643bac0 Use inside in attack decision allied army fixtures (#7413) 2026-06-20 14:04:36 -07:00
adminandGitHub 9e7890a1a6 Use inside in province events drought fixtures (#7411) 2026-06-20 14:04:23 -07:00
adminandGitHub e9d876e5c9 Use nullptr in server configuration parser (#7416) 2026-06-20 14:03:10 -07:00
adminandGitHub 524c15b824 Use using for remaining C++ aliases (#7414) 2026-06-20 14:02:48 -07:00
adminandGitHub 0891ccb0af Tidy remaining C++ include guards (#7412) 2026-06-20 14:02:22 -07:00
adminandGitHub 179288fa25 Use inside in province events conflict fixtures (#7410) 2026-06-20 13:11:31 -07:00
adminandGitHub 4a8b6df484 Use inside in province events riot condition fixtures (#7409) 2026-06-20 13:07:09 -07:00
adminandGitHub 73b5e1a8cc Use inside for resolve battle flee province change (#7407) 2026-06-20 13:06:08 -07:00
adminandGitHub 8fa012cb73 Tidy unit guard (#7408) 2026-06-20 13:05:21 -07:00
adminandGitHub 8561c606eb Tidy fire utils guard (#7406) 2026-06-20 13:05:06 -07:00
adminandGitHub abed097cad Tidy hex map utils guard (#7405) 2026-06-20 13:04:45 -07:00
adminandGitHub ccd7e6db18 Use inside in resolve battle quest province checks (#7404) 2026-06-20 13:00:40 -07:00
adminandGitHub e6c648c03e Use inside in resolve battle captured hero checks (#7403) 2026-06-20 13:00:21 -07:00
adminandGitHub a4278cb6f2 Use inside in attack decision battalion fixtures (#7402) 2026-06-20 13:00:02 -07:00
adminandGitHub ce9ddc049e Use inside in attack decision status fixtures (#7401) 2026-06-20 12:59:33 -07:00
adminandGitHub 3646fd868a Use inside in province events riot fixtures (#7398) 2026-06-20 12:59:11 -07:00
adminandGitHub 288428c874 Use using for tile modifier alias (#7399) 2026-06-20 12:58:03 -07:00
adminandGitHub 1f30d13454 Tidy zone control guard (#7400) 2026-06-20 12:57:48 -07:00
adminandGitHub 6712a495ff Tidy terrain guard (#7397) 2026-06-20 12:57:25 -07:00
adminandGitHub 8ac26578e3 Use inside in attack decision helper fixtures (#7396) 2026-06-20 12:44:50 -07:00
adminandGitHub 049c7abb81 Use inside in fulfill quests fixtures (#7395) 2026-06-20 12:44:17 -07:00
adminandGitHub 123e0066d7 Use inside in attack command chooser fixtures (#7394) 2026-06-20 12:43:56 -07:00
adminandGitHub 3cff277132 Use inside in seek more leaders fixtures (#7393) 2026-06-20 12:43:33 -07:00
adminandGitHub ebb6a55ef8 Use inside in province move resolution fixture (#7392) 2026-06-20 12:43:05 -07:00
adminandGitHub a2d371fcfc Use inside in manage prisoners availability fixtures (#7390) 2026-06-20 12:42:45 -07:00
adminandGitHub b1d30c4f03 Use inside in captured hero availability fixtures (#7389) 2026-06-20 12:42:21 -07:00
adminandGitHub 67911c12f6 Use inside in break alliance availability test (#7386) 2026-06-20 12:41:58 -07:00
adminandGitHub 25db6649f3 Use inside for new year fixture helpers (#7385) 2026-06-20 12:41:40 -07:00
adminandGitHub 6a5d9df1a4 Use inside for battle flee army assertions (#7383) 2026-06-20 12:41:18 -07:00
adminandGitHub 8254600b5c Tidy hex map direction guard (#7391) 2026-06-20 08:45:01 -07:00
adminandGitHub 8b0d43a5ee Tidy coordinates guard (#7388) 2026-06-20 08:44:45 -07:00
adminandGitHub 99b1827048 Tidy shardok c types guard (#7387) 2026-06-20 08:44:20 -07:00
adminandGitHub 8368f4f762 Tidy shardok action aliases (#7382) 2026-06-20 08:43:49 -07:00
adminandGitHub eda94bd881 Tidy percentile roll odds aliases (#7384) 2026-06-20 08:27:17 -07:00
adminandGitHub 2c8b3e6089 Pattern match attack decision phase filters (#7381) 2026-06-20 08:22:27 -07:00
adminandGitHub d9376ee870 Tidy battalion type helpers (#7380) 2026-06-20 08:21:55 -07:00
adminandGitHub 94473eaac6 Use inside for province event epidemic assertions (#7379) 2026-06-20 08:21:15 -07:00
adminandGitHub ff2caaac1c Use inside for forced turn back province assertions (#7378) 2026-06-20 08:20:51 -07:00
adminandGitHub 2fc7d91b6b Use inside in prisoner exchange test (#7375) 2026-06-20 08:20:27 -07:00
adminandGitHub f74ef641d8 Use inside for defender victory battle assertions (#7377) 2026-06-20 08:19:58 -07:00
adminandGitHub 1aa23d2992 Use inside for food consumption battalion assertions (#7374) 2026-06-20 08:19:01 -07:00
adminandGitHub 2fd86301ce Use inside for new year battalion assertions (#7372) 2026-06-20 08:16:17 -07:00
adminandGitHub 2c2a6a083b Tidy battalion damage helpers (#7376) 2026-06-20 08:15:13 -07:00
adminandGitHub 3462d7e6b3 Tidy sequence random generator header (#7370) 2026-06-20 08:14:45 -07:00
adminandGitHub d9a56d5cc5 Tidy tile modifier integrity bounds (#7373) 2026-06-20 08:14:11 -07:00
adminandGitHub db8a50edab Use inside in defense decision phase test (#7371) 2026-06-20 08:01:50 -07:00
adminandGitHub e815438212 Scope AttackOrientation enum (#7369) 2026-06-20 07:58:42 -07:00
adminandGitHub a31ce39009 Tidy ActionCost value type (#7366) 2026-06-20 07:58:20 -07:00
adminandGitHub 8d8abf0f90 Use inside in attack decision availability test (#7365) 2026-06-20 07:57:56 -07:00
adminandGitHub c9faf440ce Use inside for captured defender battle assertions (#7368) 2026-06-20 07:57:38 -07:00
adminandGitHub 14aec4f6d0 Use inside for outlawed defender battle assertions (#7367) 2026-06-20 07:57:07 -07:00
adminandGitHub 5b11135abb Use inside in captured hero availability test (#7364) 2026-06-20 07:56:25 -07:00
adminandGitHub 3ce4ad3770 Use inside in manage prisoner availability test (#7363) 2026-06-20 07:54:10 -07:00
adminandGitHub 65bc952a5a Use inside in forced turn back test (#7362) 2026-06-20 07:53:54 -07:00
adminandGitHub 01be354271 Use inside for new year province assertions (#7361) 2026-06-20 07:53:31 -07:00
adminandGitHub 2634c7c3c8 Tidy random generator header (#7358) 2026-06-20 07:49:34 -07:00
adminandGitHub 30789f86f6 Tidy Shardok unit helpers (#7355) 2026-06-20 07:49:09 -07:00
adminandGitHub 404028bb59 Always package Windows Addressables runtime data (#7360) 2026-06-20 07:47:26 -07:00
adminandGitHub ea25b9ea79 Use inside for new year hero assertions (#7359) 2026-06-20 07:43:03 -07:00
adminandGitHub 423940ad3b Use inside in send supplies test (#7357) 2026-06-20 07:42:47 -07:00
adminandGitHub 44d8ba6c40 Use inside for province event assertions (#7356) 2026-06-20 07:42:32 -07:00
adminandGitHub 87ea1f1727 Use inside in recon resolution tests (#7354) 2026-06-20 07:41:47 -07:00
adminandGitHub 76c70166ee Scope DamageType enum (#7353) 2026-06-20 07:30:13 -07:00
adminandGitHub 40706f96a3 Use inside for action result test casts (#7351) 2026-06-20 07:28:40 -07:00
adminandGitHub f04bcd61f7 Clean up CombatDamage storage (#7349) 2026-06-20 06:07:28 -07:00
adminandGitHub d415e97672 Use inside for selected Scala test casts (#7350) 2026-06-20 06:05:15 -07:00
adminandGitHub e22a6d987c Add clang-tidy runner script (#7347) 2026-06-19 22:23:03 -07:00
adminandGitHub ba60421024 Tighten Shardok C++ library tests (#7346) 2026-06-19 22:18:25 -07:00
adminandGitHub 0dd7d75878 Improve Shardok C++ safety checks (#7345) 2026-06-19 22:11:02 -07:00
adminandGitHub e77afba638 Increase handle riot give toggle tap targets (#7344) 2026-06-19 21:59:32 -07:00
adminandGitHub 2d69336245 Increase resource control tap targets (#7343) 2026-06-19 21:59:19 -07:00
adminandGitHub 934f3c594e Increase remaining command tap targets (#7342) 2026-06-19 21:59:04 -07:00
adminandGitHub 4cb56f80c6 Increase diplomacy tap targets (#7341) 2026-06-19 18:28:12 -07:00
adminandGitHub 212f19ba40 Increase attack decision tap targets (#7340) 2026-06-19 18:27:17 -07:00
adminandGitHub 9e4f052653 Increase command panel info tap target (#7339) 2026-06-19 18:20:05 -07:00
adminandGitHub afa85afebf Increase prisoner action tap targets (#7338) 2026-06-19 18:19:52 -07:00
adminandGitHub c6514be984 Increase ransom offer button tap targets (#7337) 2026-06-19 16:58:11 -07:00
adminandGitHub 719323d3c7 Restore stale text request invariant (#7336) 2026-06-19 14:20:23 -07:00
adminandGitHub 47fd3733b2 Prune generated text after rewind (#7335)
* Prune text store entries after rewind

* Fix rewind controller test text store expectation
2026-06-19 14:12:12 -07:00
adminandGitHub d7a6241a90 Include Shardok rows in Postgres save exports (#7334) 2026-06-19 13:51:12 -07:00
adminandGitHub ce37e37fb6 Resubmit orphaned outstanding Shardok battles (#7333) 2026-06-19 13:38:54 -07:00
adminandGitHub 536af1f547 Advance saved games after load or rewind (#7332) 2026-06-19 13:26:28 -07:00
adminandGitHub 7c69a9da0e Guard raw rewinds into missing battle state (#7331) 2026-06-19 13:23:38 -07:00
adminandGitHub 548f72e6fc Avoid reflection in raw Postgres history JSON (#7330) 2026-06-19 12:57:26 -07:00
adminandGitHub 1d5aa0c7aa Support raw Postgres history recovery (#7329) 2026-06-19 11:57:34 -07:00
adminandGitHub c00631d6dc Support raw rewind for Postgres saves (#7328) 2026-06-19 11:07:09 -07:00
adminandGitHub f3e6285828 Generate pleas for evacuated secured prisoners (#7327) 2026-06-19 07:47:06 -07:00
adminandGitHub 866c39283d Fix admin saved game transfer for Postgres (#7326) 2026-06-19 07:46:42 -07:00
adminandGitHub 6a98cd8d6d Upgrade Unity client to 6000.5.0f1 (#7325) 2026-06-19 07:46:13 -07:00
adminandGitHub 28000e389a Remove no profession icon (#7324) 2026-06-18 22:07:20 -07:00
adminandGitHub a336ee086c Replace fixed hero headshots (#7323)
* Replace fixed hero headshots

* Rework selected fixed hero portraits
2026-06-18 07:25:37 -07:00
adminandGitHub 08338c1910 Add Warden prisoner join bonus (#7322) 2026-06-17 16:29:47 -07:00
adminandGitHub 3fd9147567 Add Warden Great People (#7321) 2026-06-17 15:55:14 -07:00
adminandGitHub 1cd7f58817 Document Warden follow-up priorities (#7320) 2026-06-17 15:28:19 -07:00
adminandGitHub ebeb7cf2b1 Use shackles icon for Warden evacuation (#7319) 2026-06-17 13:26:00 -07:00
adminandGitHub 5ff379df96 Expose Warden evacuation command in Shardok UI (#7318) 2026-06-17 12:37:07 -07:00
adminandGitHub 4150f1de11 Carry Warden securement metadata through battle resolution (#7316) 2026-06-17 11:18:56 -07:00
810 changed files with 26917 additions and 9347 deletions
+26
View File
@@ -22,6 +22,32 @@ common --per_file_copt=src/test/cpp/.*@-Wno-sign-compare
common --per_file_copt=src/test/cpp/.*@-Wno-unknown-warning-option
common --host_cxxopt="--std=c++23"
# C++ sanitizer configs for targeted Shardok safety checks.
# Example: bazel test --config=asan //src/test/cpp/net/eagle0/shardok/library/...
build:asan --compilation_mode=dbg
build:asan --strip=never
build:asan --copt=-fno-omit-frame-pointer
build:asan --copt=-Wno-macro-redefined
build:asan --copt=-fsanitize=address
build:asan --linkopt=-fsanitize=address
test:asan --test_env=ASAN_OPTIONS=detect_leaks=0:strict_init_order=1
build:ubsan --compilation_mode=dbg
build:ubsan --strip=never
build:ubsan --copt=-fno-omit-frame-pointer
build:ubsan --copt=-Wno-macro-redefined
build:ubsan --copt=-fsanitize=undefined
build:ubsan --linkopt=-fsanitize=undefined
test:ubsan --test_env=UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1
build:tsan --compilation_mode=dbg
build:tsan --strip=never
build:tsan --copt=-fno-omit-frame-pointer
build:tsan --copt=-Wno-macro-redefined
build:tsan --copt=-fsanitize=thread
build:tsan --linkopt=-fsanitize=thread
test:tsan --test_env=TSAN_OPTIONS=halt_on_error=1
common --javacopt="-Xlint:-options"
# Prefer protobuf's prebuilt protoc toolchain instead of building protoc from
+32
View File
@@ -0,0 +1,32 @@
Checks: >
-*,
bugprone-*,
performance-*,
readability-*,
modernize-*,
cppcoreguidelines-*,
-bugprone-easily-swappable-parameters,
-cppcoreguidelines-avoid-magic-numbers,
-cppcoreguidelines-macro-usage,
-cppcoreguidelines-owning-memory,
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
-cppcoreguidelines-pro-bounds-constant-array-index,
-cppcoreguidelines-pro-bounds-pointer-arithmetic,
-cppcoreguidelines-pro-type-const-cast,
-cppcoreguidelines-pro-type-reinterpret-cast,
-cppcoreguidelines-pro-type-union-access,
-cppcoreguidelines-pro-type-vararg,
-modernize-use-trailing-return-type,
-readability-convert-member-functions-to-static,
-readability-function-cognitive-complexity,
-readability-identifier-length,
-readability-magic-numbers
WarningsAsErrors: ''
HeaderFilterRegex: 'src/(main|test)/cpp/net/eagle0/(shardok|common)/.*'
FormatStyle: file
CheckOptions:
readability-braces-around-statements.ShortStatementLines: '1'
readability-function-size.LineThreshold: '160'
readability-function-size.StatementThreshold: '80'
readability-function-size.BranchThreshold: '20'
modernize-use-nullptr.NullMacros: 'NULL'
+1 -15
View File
@@ -255,7 +255,7 @@ jobs:
echo "=== All images pushed successfully ==="
deploy:
runs-on: [self-hosted, bazel]
runs-on: ubuntu-latest
needs: [build-all]
if: needs.build-all.outputs.skip_build != 'true' && (github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.push_images == 'true'))
environment: production
@@ -334,25 +334,11 @@ jobs:
ssh-keyscan -H "$DO_DROPLET_IP" >> ~/.ssh/known_hosts 2>/dev/null || true
- name: Download warmup binary
id: download-warmup
continue-on-error: true
uses: actions/download-artifact@v8
with:
name: warmup-binary
path: scripts/bin/
- name: Build warmup binary fallback
if: steps.download-warmup.outcome != 'success'
run: |
echo "::warning::warmup-binary artifact was unavailable; rebuilding warmup binary before deploy"
./ci/github_actions/ensure_bazel_installed.sh
bazel build \
--platforms=//:linux_x86_64 \
--extra_toolchains=@llvm_toolchain_linux//:cc-toolchain-x86_64-linux \
//ci:warmup_tar
mkdir -p scripts/bin
tar -xf bazel-bin/ci/warmup_tar.tar -C scripts/bin --strip-components=1
- name: Copy config files to droplet
run: |
# Create directory structure on remote
+3 -1
View File
@@ -177,10 +177,12 @@ jobs:
env:
DO_CDN_PAT: ${{ secrets.DO_CDN_PAT }}
run: |
UNITY_VERSION=$(grep "m_EditorVersion:" src/main/csharp/net/eagle0/clients/unity/eagle0/ProjectSettings/ProjectVersion.txt | head -1 | sed 's/m_EditorVersion: //')
UNITY_MAJOR_MINOR=$(echo "$UNITY_VERSION" | sed -E 's/^([0-9]+)\.([0-9]+).*/\1.\2/')
curl -s -X DELETE "https://api.digitalocean.com/v2/cdn/endpoints/8c98df29-6f0c-4704-8e82-ca40a2b81aa8/cache" \
-H "Authorization: Bearer $DO_CDN_PAT" \
-H "Content-Type: application/json" \
-d '{"files": ["addressables/iOS/*"]}' \
-d "{\"files\": [\"addressables/iOS/$UNITY_MAJOR_MINOR/*\"]}" \
--fail || echo "Warning: CDN purge failed (non-fatal)"
- name: Archive Build Log
+29 -3
View File
@@ -50,9 +50,11 @@ on:
- "scripts/notarize_wait.sh"
- "ci/github_actions/build_mac.sh"
- "ci/github_actions/build_unity_mac.sh"
- "ci/github_actions/ensure_unity_installed.sh"
- "ci/github_actions/ensure_bazel_installed.sh"
- ".github/actions/setup-bazel/**"
- "ci/github_actions/detect_addressables_changes.sh"
- "src/main/csharp/net/eagle0/clients/unity/eagle0/ProjectSettings/ProjectVersion.txt"
- "src/main/csharp/net/eagle0/clients/unity/eagle0/ProjectSettings/ProjectSettings.asset"
- ".bazelrc"
- "ci/mac/**"
@@ -123,11 +125,25 @@ jobs:
- name: Clean stale files
run: |
git clean -ffd
LIBRARY_DIR="src/main/csharp/net/eagle0/clients/unity/eagle0/Library"
VERSION_CACHE="$LIBRARY_DIR/.last_unity_version"
PROJECT_VERSION_FILE="src/main/csharp/net/eagle0/clients/unity/eagle0/ProjectSettings/ProjectVersion.txt"
CURRENT_VERSION=$(grep "m_EditorVersion:" "$PROJECT_VERSION_FILE" | head -1 | sed 's/m_EditorVersion: //')
# Nuke Library/ when Unity version changes to avoid import loops
if [ -f "$VERSION_CACHE" ]; then
CACHED_VERSION=$(cat "$VERSION_CACHE")
if [ "$CACHED_VERSION" != "$CURRENT_VERSION" ]; then
echo "Unity version changed ($CACHED_VERSION -> $CURRENT_VERSION) -- clearing Library/"
rm -rf "$LIBRARY_DIR"
fi
fi
# Only clear Bee/ when C# files were added/deleted/renamed (structural
# changes that stale the DAG). Content-only modifications are handled by
# Bee's incremental compilation. See persist_library.sh for background.
BEE_DIR="src/main/csharp/net/eagle0/clients/unity/eagle0/Library/Bee"
SHA_FILE="src/main/csharp/net/eagle0/clients/unity/eagle0/Library/.last_built_sha"
BEE_DIR="$LIBRARY_DIR/Bee"
SHA_FILE="$LIBRARY_DIR/.last_built_sha"
if [ -f "$SHA_FILE" ] && [ -d "$BEE_DIR" ]; then
LAST_SHA=$(cat "$SHA_FILE")
if git diff --diff-filter=ADR --name-only "$LAST_SHA" HEAD -- '*.cs' '*.csproj' '*.asmdef' 2>/dev/null | grep -q .; then
@@ -184,6 +200,12 @@ jobs:
mkdir -p src/main/csharp/net/eagle0/clients/unity/eagle0/Library
git rev-parse HEAD > src/main/csharp/net/eagle0/clients/unity/eagle0/Library/.last_built_sha
- name: Save Unity version for Library/ cache invalidation
if: success()
run: |
mkdir -p src/main/csharp/net/eagle0/clients/unity/eagle0/Library
grep "m_EditorVersion:" src/main/csharp/net/eagle0/clients/unity/eagle0/ProjectSettings/ProjectVersion.txt | head -1 | sed 's/m_EditorVersion: //' > src/main/csharp/net/eagle0/clients/unity/eagle0/Library/.last_unity_version
- name: Check if should deploy
id: check-deploy
run: |
@@ -245,6 +267,7 @@ jobs:
runs-on: [self-hosted, macOS, notarize]
outputs:
deployed_version: ${{ steps.deploy-mac.outputs.deployed_version }}
deployed_unity_major_minor: ${{ steps.deploy-mac.outputs.deployed_unity_major_minor }}
concurrency:
group: mac-deploy-${{ github.ref }}
cancel-in-progress: false
@@ -473,6 +496,9 @@ jobs:
rm "$SPARKLE_PRIVATE_KEY_PATH"
echo "deployed_version=$VERSION" >> $GITHUB_OUTPUT
UNITY_VERSION=$(grep "m_EditorVersion:" src/main/csharp/net/eagle0/clients/unity/eagle0/ProjectSettings/ProjectVersion.txt | head -1 | sed 's/m_EditorVersion: //')
UNITY_MAJOR_MINOR=$(echo "$UNITY_VERSION" | sed -E 's/^([0-9]+)\.([0-9]+).*/\1.\2/')
echo "deployed_unity_major_minor=$UNITY_MAJOR_MINOR" >> $GITHUB_OUTPUT
# Artifact cleanup is handled by the cleanup job on ubuntu-latest
- name: Cleanup build directory
@@ -493,7 +519,7 @@ jobs:
curl -s -X DELETE "https://api.digitalocean.com/v2/cdn/endpoints/8c98df29-6f0c-4704-8e82-ca40a2b81aa8/cache" \
-H "Authorization: Bearer $DO_CDN_PAT" \
-H "Content-Type: application/json" \
-d '{"files": ["mac/*", "addressables/StandaloneOSX/*"]}' \
-d '{"files": ["mac/*", "addressables/StandaloneOSX/${{ needs.deploy-mac.outputs.deployed_unity_major_minor }}/*"]}' \
--fail || echo "Warning: CDN purge failed (non-fatal)"
curl -X POST "https://admin.eagle0.net/notify-update?platform=mac&version=${{ needs.deploy-mac.outputs.deployed_version }}&required=false" \
+6
View File
@@ -20,6 +20,7 @@ on:
- 'ci/github_actions/ensure_bazel_installed.sh'
- 'scripts/build_eagle_ci.sh'
- 'scripts/build_shardok_ci.sh'
- 'scripts/build_shardok_linux_arm64_ci.sh'
- '.github/actions/setup-bazel/**'
- '.github/workflows/server_build.yml'
@@ -83,6 +84,7 @@ jobs:
'src/main/cpp/',
'src/main/protobuf/net/eagle0/shardok/',
'scripts/build_shardok_ci.sh',
'scripts/build_shardok_linux_arm64_ci.sh',
];
const shared = matches(sharedPatterns);
@@ -106,6 +108,10 @@ jobs:
name: 'Shardok server',
command: './scripts/build_shardok_ci.sh',
});
include.push({
name: 'Shardok Linux ARM64 server',
command: './scripts/build_shardok_linux_arm64_ci.sh',
});
}
core.setOutput('build_matrix', JSON.stringify({include}));
+3 -1
View File
@@ -196,7 +196,9 @@ jobs:
echo "Also tagged as: registry.digitalocean.com/eagle0/shardok-server:arm64-latest"
deploy-hetzner:
runs-on: [self-hosted, bazel]
# Keep this on a self-hosted Mac runner for IPv6 reachability to Hetzner,
# but do not require a Bazel runner slot for artifact transport/deploy work.
runs-on: [self-hosted, macOS, ARM64]
needs: [build-shardok-arm64]
if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.push_images == 'true')
environment: production
+1 -1
View File
@@ -287,7 +287,7 @@ jobs:
./ci/github_actions/detect_addressables_changes.sh "$BASE_SHA" "${{ github.sha }}"
- name: Build Windows unity
run: ./ci/github_actions/build_unity.sh "${{ env.EAGLE0_BUILD_DIR }}/eagle0WIN" "${{ steps.addressables.outputs.changed }}" "${{ steps.addressables.outputs.changed }}"
run: ./ci/github_actions/build_unity.sh "${{ env.EAGLE0_BUILD_DIR }}/eagle0WIN" true "${{ steps.addressables.outputs.changed }}"
- name: Save build SHA for Bee/ cache invalidation
if: success()
+20 -1
View File
@@ -29,6 +29,16 @@ If you catch yourself about to run `git push origin main` or `git push origin <b
If you catch yourself about to run `gh pr merge`, STOP. Only the user merges PRs.
## Commit History During Review
Prefer new follow-up commits over amending existing commits once a PR is open and the user is actively reviewing or
testing it. This preserves working interim states so the user can inspect, compare, or return to a known-good point
while improvements continue.
Use `git commit --amend` and force-push only when the user explicitly asks for history cleanup, when fixing metadata
before review has started, or when repairing a local commit that has not been pushed. If you believe squashing is the
right choice despite an open review, explain why and ask first.
## Worktree Usage
Prefer using the current worktree for small requested fixes. Create a separate worktree only when the user asks for one,
@@ -58,6 +68,15 @@ For PRs expected to trigger substantial Bazel work in CI, run the relevant Bazel
workstation is fast, and local Bazel runs can help hydrate the remote cache for the CI builders while still giving
earlier signal on failures.
## Cleanup PR Batching
When making mechanical cleanup changes, group 3-5 files per PR when the files are receiving the same kind of change
and can be reviewed as one pattern. Do not open one PR per file for nearly identical few-line cleanups, such as replacing
the same unsafe accessor pattern with the same contextual failure pattern across multiple Scala files.
Keep separate PRs for changes that involve different semantics, materially different risk, unrelated subsystems, or
files whose tests/validation strategy makes them better reviewed independently.
## GitHub CLI PR Bodies
When creating or editing PR descriptions with multiline bodies, write the body to a temporary markdown file and pass it
@@ -294,7 +313,7 @@ to be used for different players or game situations within the same server proce
**C# (Unity Client):**
- Located in `/src/main/csharp/net/eagle0/clients/unity/eagle0/`
- Uses Unity 6 (6000.4.10f1) with comprehensive protobuf integration (100+ .proto files)
- Uses Unity 6.5 (6000.5.0f1) with comprehensive protobuf integration (100+ .proto files)
- Key components: `EagleConnection.cs` (gRPC client), `EagleGameController.cs` (main game logic)
- Real-time bidirectional streaming with server via `PersistentClientConnection.cs`
- Strategic map UI in `Assets/Eagle/`, tactical battle UI in `Assets/Shardok/`
+1 -1
View File
@@ -282,7 +282,7 @@ oci.pull(
# Base image for Admin Server (Alpine for lightweight Go binary)
oci.pull(
name = "alpine_linux",
digest = "sha256:a2d49ea686c2adfe3c992e47dc3b5e7fa6e6b5055609400dc2acaeb241c829f4",
digest = "sha256:28bd5fe8b56d1bd048e5babf5b10710ebe0bae67db86916198a6eec434943f8b",
image = "docker.io/library/alpine",
platforms = ["linux/amd64"],
tag = "3.24",
+8 -8
View File
@@ -904,7 +904,7 @@
"@@rules_oci+//oci:extensions.bzl%oci": {
"general": {
"bzlTransitiveDigest": "H23W9dvwCQM6h+Wt8PjIahx08EkAU5eSbUxAyx9Mopc=",
"usagesDigest": "tbQ7wHHObzzrBzSRv652104GY3xxiaQDK5OUp2YeEbg=",
"usagesDigest": "k6DBHAkb5br/tpwCPUMJgqwb+7U4DXyCmBZQBgLnHJ8=",
"recordedInputs": [
"REPO_MAPPING:aspect_bazel_lib+,bazel_tools bazel_tools",
"REPO_MAPPING:bazel_features+,bazel_tools bazel_tools",
@@ -922,7 +922,7 @@
"scheme": "https",
"registry": "index.docker.io",
"repository": "library/eclipse-temurin",
"identifier": "sha256:db3011b348ad87ced055afe5145238d87a3d8f0e8f23eb4d0cd8cecb4659b085",
"identifier": "sha256:0beb258e10ad28d2bae0f74349a170bdfa67da0ef87c028cbf3d95c3e1170120",
"platform": "linux/amd64",
"target_name": "eclipse_temurin_25_jre_linux_amd64",
"bazel_tags": []
@@ -936,7 +936,7 @@
"scheme": "https",
"registry": "index.docker.io",
"repository": "library/eclipse-temurin",
"identifier": "sha256:db3011b348ad87ced055afe5145238d87a3d8f0e8f23eb4d0cd8cecb4659b085",
"identifier": "sha256:0beb258e10ad28d2bae0f74349a170bdfa67da0ef87c028cbf3d95c3e1170120",
"platforms": {
"@@platforms//cpu:x86_64": "@eclipse_temurin_25_jre_linux_amd64"
},
@@ -980,7 +980,7 @@
"scheme": "https",
"registry": "index.docker.io",
"repository": "library/ubuntu",
"identifier": "24.04",
"identifier": "26.04",
"platform": "linux/amd64",
"target_name": "ubuntu_24_04_linux_amd64",
"bazel_tags": []
@@ -993,7 +993,7 @@
"scheme": "https",
"registry": "index.docker.io",
"repository": "library/ubuntu",
"identifier": "24.04",
"identifier": "26.04",
"platform": "linux/arm64/v8",
"target_name": "ubuntu_24_04_linux_arm64_v8",
"bazel_tags": []
@@ -1007,7 +1007,7 @@
"scheme": "https",
"registry": "index.docker.io",
"repository": "library/ubuntu",
"identifier": "24.04",
"identifier": "26.04",
"platforms": {
"@@platforms//cpu:x86_64": "@ubuntu_24_04_linux_amd64",
"@@platforms//cpu:arm64": "@ubuntu_24_04_linux_arm64_v8"
@@ -1023,7 +1023,7 @@
"scheme": "https",
"registry": "index.docker.io",
"repository": "library/alpine",
"identifier": "sha256:a2d49ea686c2adfe3c992e47dc3b5e7fa6e6b5055609400dc2acaeb241c829f4",
"identifier": "sha256:28bd5fe8b56d1bd048e5babf5b10710ebe0bae67db86916198a6eec434943f8b",
"platform": "linux/amd64",
"target_name": "alpine_linux_linux_amd64",
"bazel_tags": []
@@ -1037,7 +1037,7 @@
"scheme": "https",
"registry": "index.docker.io",
"repository": "library/alpine",
"identifier": "sha256:a2d49ea686c2adfe3c992e47dc3b5e7fa6e6b5055609400dc2acaeb241c829f4",
"identifier": "sha256:28bd5fe8b56d1bd048e5babf5b10710ebe0bae67db86916198a6eec434943f8b",
"platforms": {
"@@platforms//cpu:x86_64": "@alpine_linux_linux_amd64"
},
+1 -3
View File
@@ -5,8 +5,7 @@ set -x
COMMAND_LOG="$(bazel info command_log)"
bazel build \
//src/main/cpp/net/eagle0/shardok:shardok-server \
//src/main/cpp/net/eagle0/shardok:shardok-inspector
//src/main/cpp/net/eagle0/shardok:shardok-server
STATUS="$?"
if [ $STATUS -eq 0 ]
@@ -26,7 +25,6 @@ echo "Setting up Shardok files..."
/bin/cp -R ./bazel-bin/src/main/cpp/net/eagle0/shardok/shardok-server ./shardok/
/bin/mkdir -p ./shardok/shardok-server.runfiles/net_eagle0/src/main/
/bin/cp -R ./bazel-bin/src/main/cpp/net/eagle0/shardok/shardok-server.runfiles/net_eagle0/src/main/resources ./shardok/shardok-server.runfiles/net_eagle0/src/main/
/bin/cp -R ./bazel-bin/src/main/cpp/net/eagle0/shardok/shardok-inspector ./shardok/
echo "tar & zip shardok files..."
/bin/tar cfh - shardok | /usr/bin/pigz > ./deploy/shardok.tar.gz
-13
View File
@@ -40,19 +40,6 @@ if [ -f "$INFO_PLIST" ]; then
/usr/libexec/PlistBuddy -c "Set :ITSAppUsesNonExemptEncryption false" "$INFO_PLIST" 2>/dev/null \
|| /usr/libexec/PlistBuddy -c "Add :ITSAppUsesNonExemptEncryption bool false" "$INFO_PLIST"
echo "Set ITSAppUsesNonExemptEncryption=false in Info.plist"
# Remove UIApplicationSceneManifest if present.
# Unity 6 generates this key, which opts the app into the iOS scene-based
# lifecycle. This breaks Application.deepLinkActivated because iOS routes
# deep link URLs through UISceneDelegate instead of UIApplicationDelegate,
# and Unity doesn't implement the scene delegate path.
# See: https://issuetracker.unity3d.com/issues/in-135632
if /usr/libexec/PlistBuddy -c "Print :UIApplicationSceneManifest" "$INFO_PLIST" 2>/dev/null; then
echo "Found UIApplicationSceneManifest in Info.plist — removing to fix deep link handling"
/usr/libexec/PlistBuddy -c "Delete :UIApplicationSceneManifest" "$INFO_PLIST"
else
echo "No UIApplicationSceneManifest in Info.plist (deep links should work)"
fi
fi
# Unity always generates "Unity-iPhone" as the main app scheme
+14
View File
@@ -59,6 +59,20 @@ if [ $UNITY_EXIT_CODE -ne 0 ]; then
exit $UNITY_EXIT_CODE
fi
if [ ! -f "$LOG_PATH" ]; then
echo ""
echo "ERROR: Unity exited successfully but did not write an editor log at $LOG_PATH"
exit 1
fi
if [ ! -f "$BUILD_PATH/Data/Raw/aa/settings.json" ]; then
echo ""
echo "ERROR: iOS player is missing Addressables runtime data at:"
echo " $BUILD_PATH/Data/Raw/aa/settings.json"
echo "Build Addressables before packaging the player so Addressables can initialize at runtime."
exit 1
fi
# Fail the build if any prefab references are broken — this produces a player
# that launches but has null Inspector fields, which is hard to debug.
if grep -q "Missing Prefab" "$LOG_PATH"; then
+8
View File
@@ -57,6 +57,14 @@ if [ ! -f "$LOG_PATH" ]; then
exit 1
fi
if [ ! -f "$BUILD_DIR/eagle0_Data/StreamingAssets/aa/settings.json" ]; then
echo ""
echo "ERROR: Windows player is missing Addressables runtime data at:"
echo " $BUILD_DIR/eagle0_Data/StreamingAssets/aa/settings.json"
echo "Build Addressables before packaging the player so Addressables can initialize at runtime."
exit 1
fi
# Fail the build if any prefab references are broken — this produces a player
# that launches but has null Inspector fields, which is hard to debug.
if grep -q "Missing Prefab" "$LOG_PATH"; then
+10 -2
View File
@@ -49,7 +49,7 @@ check_modules_installed() {
fi
;;
mac)
if ! has_il2cpp_variation "${unity_app_contents_path}/PlaybackEngines/MacStandaloneSupport/Variations"; then
if ! has_mac_il2cpp_support "$unity_path" "$unity_app_contents_path"; then
echo "✗ Mac IL2CPP module not installed for Unity ${UNITY_VERSION}"
return 1
fi
@@ -67,7 +67,7 @@ check_modules_installed() {
echo "✗ iOS module missing"
missing=1
fi
if ! has_il2cpp_variation "${unity_app_contents_path}/PlaybackEngines/MacStandaloneSupport/Variations"; then
if ! has_mac_il2cpp_support "$unity_path" "$unity_app_contents_path"; then
echo "✗ Mac IL2CPP module missing"
missing=1
fi
@@ -84,6 +84,14 @@ check_modules_installed() {
return 0
}
has_mac_il2cpp_support() {
local unity_path="$1"
local unity_app_contents_path="$2"
has_il2cpp_variation "${unity_app_contents_path}/PlaybackEngines/MacStandaloneSupport/Variations" ||
has_il2cpp_variation "${unity_path}/PlaybackEngines/MacStandaloneSupport/Variations"
}
has_il2cpp_variation() {
local variations_path="$1"
local variation
+9 -6
View File
@@ -13,7 +13,10 @@ set -euxo pipefail
BUILD_TARGET=$1
WORKSPACE=$(pwd)
UNITY_PROJECT="$WORKSPACE/src/main/csharp/net/eagle0/clients/unity/eagle0"
SERVER_DATA="$UNITY_PROJECT/ServerData/$BUILD_TARGET"
UNITY_VERSION=$(grep "m_EditorVersion:" "$UNITY_PROJECT/ProjectSettings/ProjectVersion.txt" | head -1 | sed 's/m_EditorVersion: //')
UNITY_MAJOR_MINOR=$(echo "$UNITY_VERSION" | sed -E 's/^([0-9]+)\.([0-9]+).*/\1.\2/')
ADDRESSABLES_PREFIX="addressables/$BUILD_TARGET/$UNITY_MAJOR_MINOR"
SERVER_DATA="$UNITY_PROJECT/ServerData/$BUILD_TARGET/$UNITY_MAJOR_MINOR"
# DigitalOcean Spaces configuration (same region as other eagle0 buckets)
DO_ENDPOINT="https://sfo3.digitaloceanspaces.com"
@@ -75,7 +78,7 @@ summarize_pending_uploads() {
sorted_uploads_file=$(mktemp)
trap 'rm -f "$sync_args_file" "$uploads_file" "$sorted_uploads_file"' RETURN
aws s3 sync "$SERVER_DATA" "s3://$DO_BUCKET/addressables/$BUILD_TARGET/" \
aws s3 sync "$SERVER_DATA" "s3://$DO_BUCKET/$ADDRESSABLES_PREFIX/" \
--endpoint-url "$DO_ENDPOINT" \
--acl public-read \
--size-only \
@@ -178,7 +181,7 @@ if [ ! -d "$SERVER_DATA" ]; then
fi
echo "Uploading Addressables bundles from $SERVER_DATA"
echo "Target: s3://$DO_BUCKET/addressables/$BUILD_TARGET/"
echo "Target: s3://$DO_BUCKET/$ADDRESSABLES_PREFIX/"
# Configure AWS CLI for DigitalOcean Spaces
export AWS_ACCESS_KEY_ID="$ACCESS_KEY_ID"
@@ -193,13 +196,13 @@ summarize_pending_uploads
#
# --delete removes files in destination that don't exist in source.
# --acl public-read makes files publicly accessible.
aws s3 sync "$SERVER_DATA" "s3://$DO_BUCKET/addressables/$BUILD_TARGET/" \
aws s3 sync "$SERVER_DATA" "s3://$DO_BUCKET/$ADDRESSABLES_PREFIX/" \
--endpoint-url "$DO_ENDPOINT" \
--acl public-read \
--size-only \
--delete
aws s3 sync "$SERVER_DATA" "s3://$DO_BUCKET/addressables/$BUILD_TARGET/" \
aws s3 sync "$SERVER_DATA" "s3://$DO_BUCKET/$ADDRESSABLES_PREFIX/" \
--endpoint-url "$DO_ENDPOINT" \
--acl public-read \
--exclude "*" \
@@ -208,4 +211,4 @@ aws s3 sync "$SERVER_DATA" "s3://$DO_BUCKET/addressables/$BUILD_TARGET/" \
--include "*.json"
echo "Addressables upload complete"
echo "Files available at: https://assets.eagle0.net/addressables/$BUILD_TARGET/"
echo "Files available at: https://assets.eagle0.net/$ADDRESSABLES_PREFIX/"
+12
View File
@@ -103,6 +103,18 @@ and protect fragile specialists.
a prisoner, add the Warden custody bonus to that prisoner's `factionBiases(rulingFactionId)`.
- For Shardok, start with a single-turn guard status before attempting more complex interception chains
**Current Follow-Up Priorities:**
- Teach the Shardok AI when to use the Warden evacuation command. The command is valuable when the Warden's side has
captured enemy heroes and can secure them by leaving the battle, especially when the battle outcome is uncertain or
trending against that side.
- Add the strategic passive custody bonus: each round, prisoners in a province guarded by a ruling-faction Warden should
get a small accumulated positive bias toward joining that faction. Prefer storing this as an addition to the existing
`factionBiases` entry rather than changing the global `roundsInType` multiplier, so the bonus only reflects rounds
actually spent under Warden custody.
- Make tactical AI more protective of heroes at risk of capture when a Warden may be present. Warden evacuation makes
captured heroes harder to recover, so the AI should treat exposing high-value heroes to capture as more dangerous than
before. This matters even when the AI does not have perfect knowledge that a Warden is in the battle.
### 2. HERALD (Morale & Communication Specialist)
**Fantasy:** The inspiring leader who rallies troops and carries messages across the battlefield.
Binary file not shown.
+17
View File
@@ -0,0 +1,17 @@
#!/usr/bin/env bash
set -euo pipefail
bazel_cmd=(bazel)
if [ -n "${BAZEL_OUTPUT_USER_ROOT:-}" ]; then
bazel_cmd+=("--output_user_root=${BAZEL_OUTPUT_USER_ROOT}")
fi
if [ -n "${BAZEL_OUTPUT_BASE:-}" ]; then
bazel_cmd+=("--output_base=${BAZEL_OUTPUT_BASE}")
fi
"${bazel_cmd[@]}" build \
--platforms=//:linux_arm64 \
--extra_toolchains=@llvm_toolchain_linux_arm64//:cc-toolchain-aarch64-linux \
"$@" \
//src/main/cpp/net/eagle0/shardok:shardok-server
+33
View File
@@ -0,0 +1,33 @@
#!/bin/bash
# Runs the repository-pinned clang-tidy binary from the workspace root.
#
# Bazel's `run` command executes clang-tidy from its runfiles tree, which makes
# workspace-relative source paths and .clang-tidy discovery fail. This wrapper
# builds the tool and then invokes the produced binary directly.
set -e
export PATH="/opt/homebrew/bin:/usr/local/bin:$PATH"
repo_root="$(git rev-parse --show-toplevel)"
if [ "$#" -eq 0 ]; then
echo "Usage: scripts/run-clang-tidy.sh [clang-tidy args] <files> [-- <compiler args>]"
echo ""
echo "Examples:"
echo " scripts/run-clang-tidy.sh --verify-config"
echo " scripts/run-clang-tidy.sh src/main/cpp/net/eagle0/shardok/library/CombatDamage.hpp -- -I$repo_root -std=c++23"
exit 2
fi
bazel build @llvm_toolchain//:clang-tidy
bazel_bin="$(bazel info bazel-bin)"
clang_tidy="$bazel_bin/external/toolchains_llvm++llvm+llvm_toolchain/clang-tidy"
if [ ! -x "$clang_tidy" ]; then
echo "Unable to find clang-tidy binary at $clang_tidy"
exit 1
fi
"$clang_tidy" --config-file="$repo_root/.clang-tidy" "$@"
+1
View File
@@ -1,6 +1,7 @@
#!/bin/zsh
/opt/homebrew/bin/s3cmd sync \
--acl-public \
--exclude="*dream_log.txt" \
--exclude="*.DS_Store" \
--delete-removed \
@@ -14,6 +14,7 @@
#include <cstdio>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <mutex>
#include "tools/cpp/runfiles/runfiles.h"
@@ -37,7 +38,7 @@ auto rloc(const string& execPath) -> string {
const std::unique_ptr<Runfiles> runfiles(Runfiles::Create(execPath, &error));
if (runfiles == nullptr) {
fprintf(stderr, "Error! %s\n", error.c_str());
std::cerr << "Error! " << error << '\n';
abort();
// error handling
}
@@ -78,9 +79,9 @@ auto FilesystemUtils::MapFilesDirectory() -> string {
void FilesystemUtils::MakeDirectoryIfNecessary(const string& directoryPath) {
if (fs::create_directories(directoryPath))
fprintf(stderr, "Directory %s created\n", directoryPath.c_str());
std::cerr << "Directory " << directoryPath << " created\n";
else
fprintf(stderr, "No new directory created for %s\n", directoryPath.c_str());
std::cerr << "No new directory created for " << directoryPath << '\n';
}
auto FilesystemUtils::SaveFilesDirectory() -> string {
@@ -140,11 +141,11 @@ auto FilesystemUtils::AtomicallySaveToPath(const string& path, const byte_vector
if (ostr.good()) {
const int err = rename(tempPath.c_str(), path.c_str());
if (err == -1) {
fprintf(stderr, "Failed to move file to %s! Errno %d\n", path.c_str(), errno);
std::cerr << "Failed to move file to " << path << "! Errno " << errno << '\n';
return false;
}
} else {
fprintf(stderr, "Failed writing to %s!\n", tempPath.c_str());
std::cerr << "Failed writing to " << tempPath << "!\n";
return false;
}
@@ -6,8 +6,8 @@
// Copyright © 2017 none. All rights reserved.
//
#ifndef FilesystemUtils_hpp
#define FilesystemUtils_hpp
#ifndef EAGLE0_COMMON_FILESYSTEM_UTILS_HPP
#define EAGLE0_COMMON_FILESYSTEM_UTILS_HPP
#include <string>
#include <vector>
@@ -36,4 +36,4 @@ public:
static auto LoadFromPath(const string& path) -> byte_vector;
};
#endif /* FilesystemUtils_hpp */
#endif // EAGLE0_COMMON_FILESYSTEM_UTILS_HPP
+3 -3
View File
@@ -6,8 +6,8 @@
// Copyright © 2018 none. All rights reserved.
//
#ifndef MapUtils_hpp
#define MapUtils_hpp
#ifndef EAGLE0_COMMON_MAP_UTILS_HPP
#define EAGLE0_COMMON_MAP_UTILS_HPP
#include <string>
#include <unordered_map>
@@ -19,4 +19,4 @@ auto IntForKey(const std::unordered_map<std::string, std::string>& map, const st
auto BoolForKey(const std::unordered_map<std::string, std::string>& map, const std::string& key)
-> bool;
#endif /* MapUtils_hpp */
#endif // EAGLE0_COMMON_MAP_UTILS_HPP
@@ -6,8 +6,8 @@
// Copyright © 2019 none. All rights reserved.
//
#ifndef ProtobufWarningSuppression_h
#define ProtobufWarningSuppression_h
#ifndef EAGLE0_COMMON_PROTOBUF_WARNING_SUPPRESSION_HPP
#define EAGLE0_COMMON_PROTOBUF_WARNING_SUPPRESSION_HPP
#define SUPPRESS_PROTOBUF_WARNINGS \
_Pragma("GCC diagnostic ignored \"-Wpragmas\"") \
@@ -19,4 +19,4 @@
"GCC diagnostic ignored \"-Wshorten-64-to-32\"") \
_Pragma("GCC diagnostic ignored \"-Wnested-anon-types\"")
#endif /* ProtobufWarningSuppression_h */
#endif // EAGLE0_COMMON_PROTOBUF_WARNING_SUPPRESSION_HPP
@@ -6,11 +6,13 @@
// Copyright (c) 2015 none. All rights reserved.
//
#ifndef __eagle0__ShardokRandomGenerator__
#define __eagle0__ShardokRandomGenerator__
#ifndef EAGLE0_COMMON_RANDOM_GENERATOR_HPP
#define EAGLE0_COMMON_RANDOM_GENERATOR_HPP
#include <cstddef>
#include <memory>
#include <random>
#include <vector>
class RandomGenerator;
@@ -22,6 +24,10 @@ private:
public:
RandomGenerator() = default;
RandomGenerator(const RandomGenerator&) = default;
auto operator=(const RandomGenerator&) -> RandomGenerator& = default;
RandomGenerator(RandomGenerator&&) = default;
auto operator=(RandomGenerator&&) -> RandomGenerator& = default;
virtual ~RandomGenerator() = default;
@@ -35,7 +41,7 @@ public:
template<class T>
auto RandomElement(const std::vector<T>& vec) -> T {
return vec[IntBelow((int)vec.size())];
return vec.at(static_cast<std::size_t>(IntBelow(static_cast<int>(vec.size()))));
}
static auto ChanceOpenEndedPercentileAtOrAbove(double value) -> double;
@@ -56,4 +62,4 @@ public:
auto IntBetween(int min, int max) -> int override; // inclusive, exclusive (min<=return<max)
};
#endif /* defined(__eagle0__ShardokRandomGenerator__) */
#endif // EAGLE0_COMMON_RANDOM_GENERATOR_HPP
@@ -6,10 +6,11 @@
// Copyright © 2018 none. All rights reserved.
//
#ifndef SequenceRandomGenerator_h
#define SequenceRandomGenerator_h
#ifndef EAGLE0_COMMON_SEQUENCE_RANDOM_GENERATOR_HPP
#define EAGLE0_COMMON_SEQUENCE_RANDOM_GENERATOR_HPP
#include <memory>
#include <utility>
#include <vector>
#include "src/main/cpp/net/eagle0/common/RandomGenerator.hpp"
@@ -24,22 +25,22 @@
// which produces: initial=2 (triggers open-ended), accumulated=52, final=2-52=-50
class SequenceRandomGenerator : public ::RandomGenerator {
private:
const std::vector<double> sequence;
std::vector<double> sequence;
size_t position = 0;
auto DoubleZeroToOne() -> double override {
const double nextVal = sequence[position];
const double nextVal = sequence.at(position);
position++;
if (position >= sequence.size()) { position = 0; }
return nextVal;
}
public:
explicit SequenceRandomGenerator(std::vector<double> s) : sequence(std::move(s)), position(0) {}
explicit SequenceRandomGenerator(std::vector<double> s) : sequence(std::move(s)) {}
static auto WithSequence(const std::vector<double>& s)
-> std::shared_ptr<SequenceRandomGenerator> {
return std::make_shared<SequenceRandomGenerator>(s);
}
};
#endif /* SequenceRandomGenerator_h */
#endif // EAGLE0_COMMON_SEQUENCE_RANDOM_GENERATOR_HPP
@@ -80,7 +80,7 @@ void EmitShardokLatencyTrace(
out << "}";
std::scoped_lock lock(LogMutex());
std::cerr << out.str() << std::endl;
std::cerr << out.str() << '\n';
}
ScopedShardokLatencyTrace::ScopedShardokLatencyTrace(
@@ -10,6 +10,7 @@
#include <condition_variable>
#include <functional>
#include <future>
#include <iostream>
#include <memory>
#include <mutex>
#include <queue>
@@ -171,7 +172,7 @@ public:
// Get detailed queue information for debugging
void debug_queue_state() const {
std::unique_lock<std::mutex> lock(const_cast<std::mutex&>(queue_mutex));
printf("ThreadPool: Queue size: %zu\n", tasks.size());
std::cout << "ThreadPool: Queue size: " << tasks.size() << '\n';
if (!tasks.empty()) {
// Create a copy to inspect priorities without modifying queue
auto queue_copy = tasks;
@@ -180,9 +181,9 @@ public:
priorities.push_back(queue_copy.top().priority);
queue_copy.pop();
}
printf("ThreadPool: Priorities in queue: ");
for (int p : priorities) { printf("%d ", p); }
printf("\n");
std::cout << "ThreadPool: Priorities in queue: ";
for (int p : priorities) { std::cout << p << ' '; }
std::cout << '\n';
}
}
@@ -197,4 +198,4 @@ public:
} // namespace eagle0::common
#endif // EAGLE0_THREADPOOL_HPP
#endif // EAGLE0_THREADPOOL_HPP
+4 -4
View File
@@ -6,14 +6,14 @@
// Copyright © 2018 none. All rights reserved.
//
#ifndef TsvParser_hpp
#define TsvParser_hpp
#ifndef EAGLE0_COMMON_TSV_PARSER_HPP
#define EAGLE0_COMMON_TSV_PARSER_HPP
#include <string>
#include <unordered_map>
#include <vector>
typedef std::unordered_map<std::string, std::string> StringMap;
using StringMap = std::unordered_map<std::string, std::string>;
// Parses a TSV, with the first column becoming map keys and subsequent columns becoming values
// for different maps in the returned vector.
@@ -23,4 +23,4 @@ public:
std::vector<StringMap> ParseColumnEntryTsv(std::string tsv);
};
#endif /* TsvParser_hpp */
#endif // EAGLE0_COMMON_TSV_PARSER_HPP
@@ -6,8 +6,8 @@
// Copyright © 2017 none. All rights reserved.
//
#ifndef byte_vector_h
#define byte_vector_h
#ifndef EAGLE0_COMMON_BYTE_VECTOR_HPP
#define EAGLE0_COMMON_BYTE_VECTOR_HPP
#include <cstdint>
#include <cstring>
@@ -184,4 +184,4 @@ inline auto operator<<(std::ostream& ostr, byte_vector vec) -> std::ostream& {
return ostr;
}
#endif /* byte_vector_h */
#endif // EAGLE0_COMMON_BYTE_VECTOR_HPP
@@ -9,9 +9,11 @@
#include <fstream>
#include <future>
#include <iomanip>
#include <iostream>
#include <limits>
#include <mutex>
#include <random>
#include <sstream>
#include <stdexcept>
#include <thread>
@@ -862,12 +864,12 @@ auto AbstractMCTSAI::LogSearchResults(
// Log selected command
const std::string selectedDesc =
bestChild->action ? bestChild->action->getDescription() : "Unknown";
printf("MCTS: Selected %s (visit:%d, reward:%.2f, lookahead:%.2f) from %zu options\n",
selectedDesc.c_str(),
bestChild->visitCount,
bestChild->averageReward,
bestChild->lookaheadScore,
rootNode->children.size());
std::ostringstream selectedLine;
selectedLine << std::fixed << std::setprecision(2) << "MCTS: Selected " << selectedDesc
<< " (visit:" << bestChild->visitCount << ", reward:" << bestChild->averageReward
<< ", lookahead:" << bestChild->lookaheadScore << ") from "
<< rootNode->children.size() << " options";
std::cout << selectedLine.str() << '\n';
// Log top 3 actions by visits
std::vector<const MCTSNode*> sortedChildren;
@@ -879,20 +881,17 @@ auto AbstractMCTSAI::LogSearchResults(
// Show all actions if there are <= 10, otherwise top 5
const size_t numToShow = sortedChildren.size() <= 10 ? sortedChildren.size() : 5;
printf("MCTS: Top %zu actions by visits (out of %zu total):\n",
numToShow,
sortedChildren.size());
std::cout << "MCTS: Top " << numToShow << " actions by visits (out of " << sortedChildren.size()
<< " total):\n";
for (size_t i = 0; i < numToShow; ++i) {
const auto* child = sortedChildren[i];
printf(" [%zu] visits:%d avgReward:%.2f immediate:%.2f lookahead:%.2f",
i,
child->visitCount,
child->averageReward,
child->immediateScore,
child->lookaheadScore);
std::ostringstream line;
line << std::fixed << std::setprecision(2) << " [" << i << "] visits:" << child->visitCount
<< " avgReward:" << child->averageReward << " immediate:" << child->immediateScore
<< " lookahead:" << child->lookaheadScore;
// Show the action's own description
if (child->action) { printf(" %s", child->action->getDescription().c_str()); }
if (child->action) { line << ' ' << child->action->getDescription(); }
// Show sequence preview
if (!child->children.empty()) {
@@ -905,16 +904,15 @@ auto AbstractMCTSAI::LogSearchResults(
}
}
if (bestNext && bestNext->action) {
printf(" -> %s", bestNext->action->getDescription().c_str());
line << " -> " << bestNext->action->getDescription();
}
}
printf("\n");
std::cout << line.str() << '\n';
}
printf("MCTS: Tree stats - max depth:%d, total nodes:%d, root visits:%d\n",
result.searchDepth,
result.nodesEvaluated,
rootNode->visitCount);
std::cout << "MCTS: Tree stats - max depth:" << result.searchDepth
<< ", total nodes:" << result.nodesEvaluated
<< ", root visits:" << rootNode->visitCount << '\n';
// Log best sequence from chosen action (following most-visited path)
std::vector<const MCTSNode*> bestSequence;
@@ -940,7 +938,10 @@ auto AbstractMCTSAI::LogSearchResults(
}
if (!bestSequence.empty()) {
printf("MCTS: Best sequence from chosen action (final: %.2f):\n", sequenceScore);
std::ostringstream sequenceLine;
sequenceLine << std::fixed << std::setprecision(2)
<< "MCTS: Best sequence from chosen action (final: " << sequenceScore << "):";
std::cout << sequenceLine.str() << '\n';
int displayedStep = 0;
for (size_t i = 0; i < bestSequence.size(); ++i) {
const auto* node = bestSequence[i];
@@ -949,29 +950,31 @@ auto AbstractMCTSAI::LogSearchResults(
if (i > 0 && node->parent && node->parent->IsChanceNode()) { continue; }
displayedStep++;
printf(" %d.", displayedStep);
if (node->action) { printf(" %s", node->action->getDescription().c_str()); }
std::ostringstream line;
line << " " << displayedStep << ".";
if (node->action) { line << ' ' << node->action->getDescription(); }
// If this is a chance node, display outcome probabilities and scores
if (node->IsChanceNode() && !node->outcomeProbabilities.empty()) {
printf(" [");
line << " [";
for (size_t j = 0; j < node->outcomeProbabilities.size(); ++j) {
if (j > 0) printf(", ");
if (j > 0) { line << ", "; }
const double prob = node->outcomeProbabilities[j] * 100;
// Show lookahead score for each outcome if child exists
if (j < node->children.size() && node->children[j]->visitCount > 0) {
printf("%.0f%%->%.1f", prob, node->children[j]->lookaheadScore);
line << std::fixed << std::setprecision(0) << prob << "%->"
<< std::setprecision(1) << node->children[j]->lookaheadScore;
} else {
printf("%.0f%%->?", prob);
line << std::fixed << std::setprecision(0) << prob << "%->?";
}
}
printf("]");
line << "]";
}
printf(" (visits:%d, immediate:%.2f, lookahead:%.2f)\n",
node->visitCount,
node->immediateScore,
node->lookaheadScore);
line << std::fixed << std::setprecision(2) << " (visits:" << node->visitCount
<< ", immediate:" << node->immediateScore << ", lookahead:" << node->lookaheadScore
<< ")";
std::cout << line.str() << '\n';
// For non-root nodes in the sequence, show what the top alternatives were
// Skip showing alternatives for chance nodes (they have outcome children, not action
@@ -991,17 +994,17 @@ auto AbstractMCTSAI::LogSearchResults(
});
// Show top 3 alternatives at this decision point
printf(" Alternatives at this node (%zu total):\n", siblings.size());
std::cout << " Alternatives at this node (" << siblings.size() << " total):\n";
const size_t topN = std::min(siblings.size(), size_t(3));
for (size_t j = 0; j < topN; ++j) {
const auto* alt = siblings[j];
printf(" [%zu] visits:%d immediate:%.2f lookahead:%.2f",
j,
alt->visitCount,
alt->immediateScore,
alt->lookaheadScore);
if (alt->action) { printf(" %s", alt->action->getDescription().c_str()); }
printf("\n");
std::ostringstream altLine;
altLine << std::fixed << std::setprecision(2) << " [" << j
<< "] visits:" << alt->visitCount
<< " immediate:" << alt->immediateScore
<< " lookahead:" << alt->lookaheadScore;
if (alt->action) { altLine << ' ' << alt->action->getDescription(); }
std::cout << altLine.str() << '\n';
}
}
}
@@ -1013,7 +1016,7 @@ auto AbstractMCTSAI::DumpTreeToFile(const MCTSNode* root, const std::string& fil
std::ofstream out(filepath);
if (!out) {
fprintf(stderr, "Failed to open dump file: %s\n", filepath.c_str());
std::cerr << "Failed to open dump file: " << filepath << '\n';
return;
}
@@ -1042,7 +1045,7 @@ auto AbstractMCTSAI::DumpTreeToFile(const MCTSNode* root, const std::string& fil
out << "\n=== End of Tree Dump ===\n";
out.close();
printf("MCTS: Tree dumped to %s\n", filepath.c_str());
std::cout << "MCTS: Tree dumped to " << filepath << '\n';
}
auto AbstractMCTSAI::DumpNodeRecursive(
@@ -141,9 +141,8 @@ public:
// Default implementation: no filtering, so filtered index = original index
[[nodiscard]] virtual size_t mapFilteredIndexToOriginal(
size_t filteredIndex,
const MCTSGameState& state) const {
const MCTSGameState& /*state*/) const {
// Default: no filtering, index stays the same
(void)state; // Suppress unused parameter warning
return filteredIndex;
}
@@ -158,4 +157,4 @@ public:
} // namespace mcts
} // namespace shardok
#endif // EAGLE0_MCTS_GAME_ENGINE_HPP
#endif // EAGLE0_MCTS_GAME_ENGINE_HPP
@@ -6,8 +6,11 @@
#define EAGLE0_ABSTRACT_MCTSNODE_HPP
#include <cmath>
#include <iomanip>
#include <iostream>
#include <limits>
#include <memory>
#include <sstream>
#include <vector>
#include "MCTSAction.hpp"
@@ -205,13 +208,13 @@ struct MCTSNode {
isMaximizingPlayer ? child->lookaheadScore : -child->lookaheadScore;
const double explorationValue =
explorationConstant * std::sqrt(std::log(visitCount) / child->visitCount);
printf(" UCB: %s lookahead=%.2f expl=%.2f (+%.2f) = %.2f [%s]\n",
isMaximizingPlayer ? "MAX" : "MIN",
child->lookaheadScore,
exploitationValue,
explorationValue,
value,
child->action ? child->action->getDescription().c_str() : "root");
std::ostringstream line;
line << std::fixed << std::setprecision(2)
<< " UCB: " << (isMaximizingPlayer ? "MAX" : "MIN")
<< " lookahead=" << child->lookaheadScore << " expl=" << exploitationValue
<< " (+" << explorationValue << ") = " << value << " ["
<< (child->action ? child->action->getDescription() : "root") << "]";
std::cout << line.str() << '\n';
}
if (value > bestValue) {
@@ -28,36 +28,6 @@ cc_binary(
deps = [":shardok-server-lib"],
)
cc_binary(
name = "shardok-inspector",
srcs = ["shardok_inspector.cpp"],
copts = COPTS,
data = [
"//src/main/resources/net/eagle0/shardok:battalion_types",
"//src/main/resources/net/eagle0/shardok:settings",
"//src/main/resources/net/eagle0/shardok/maps",
],
visibility = ["//visibility:public"],
deps = [
"//src/main/cpp/net/eagle0/common:byte_vector",
"//src/main/cpp/net/eagle0/common:filesystem_utils",
"//src/main/cpp/net/eagle0/common:tsv_parser",
"//src/main/cpp/net/eagle0/shardok/library:engine",
],
)
cc_binary(
name = "corrupt-game-checker",
srcs = ["CorruptGameChecker.cpp"],
copts = COPTS,
deps = [
"//src/main/cpp/net/eagle0/common:byte_vector",
"//src/main/cpp/net/eagle0/common:filesystem_utils",
"//src/main/cpp/net/eagle0/shardok/library:game_state_w",
"//src/main/protobuf/net/eagle0/common:shardok_internal_interface_cc_grpc",
],
)
cc_binary(
name = "hex-map-migrator",
srcs = ["HexMapMigrator.cpp"],
@@ -1,46 +0,0 @@
//
// Created by Dan Crosby on 1/9/22.
//
#include "src/main/cpp/net/eagle0/common/byte_vector.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/GameStateW.hpp"
#include "src/main/protobuf/net/eagle0/common/shardok_internal_interface.pb.h"
#include "src/main/protobuf/net/eagle0/shardok/storage/game.pb.h"
auto main(int argc, char** argv) -> int {
char* path = argv[1];
printf("Checking file %s\n", path);
auto bytes = byte_vector::FromPath(path);
net::eagle0::shardok::storage::Game game;
game.ParseFromArray(bytes.data(), bytes.size());
printf("The game ID is %s\n", game.game_id().c_str());
printf("The size is %lu\n", bytes.size());
const int arCount = game.action_result_size();
printf("There are %d results\n", arCount);
for (int arIndex = 0; arIndex < arCount; arIndex++) {
shardok::GameStateW gameState =
shardok::GameStateW::FromByteString(game.action_result(arIndex).state_after_fb());
const auto* hexMap = gameState->hex_map();
for (int terrainIndex = 0; terrainIndex < hexMap->terrain()->size(); terrainIndex++) {
const auto* terrain = hexMap->terrain()->Get(terrainIndex);
if (terrain->type() == net::eagle0::shardok::storage::fb::Terrain_::Type_STILL_WATER)
continue;
if (terrain->type() == net::eagle0::shardok::storage::fb::Terrain_::Type_RIVER)
continue;
if (terrain->modifier().bridge().present()) {
printf("Caught one! arIndex %d, terrainIndex %d\n", arIndex, terrainIndex);
printf("Action result was %s\n",
game.action_result(arIndex).action_result().DebugString().c_str());
exit(0);
}
}
}
}
@@ -2,7 +2,7 @@
// Created by Dan Crosby on 3/19/20.
//
#include <cstdio>
#include <iostream>
#include "src/main/cpp/net/eagle0/common/FilesystemUtils.hpp"
#include "src/main/cpp/net/eagle0/common/byte_vector.hpp"
@@ -14,7 +14,7 @@ void ModifyMap(HexMapProto& map);
int main(const int argc, char** argv) {
if (argc < 3) {
printf("Usage: %s input_path output_path\n", argv[0]);
std::cerr << "Usage: " << argv[0] << " input_path output_path\n";
return -1;
}
@@ -22,21 +22,25 @@ int main(const int argc, char** argv) {
const std::string outputPath = argv[2];
const byte_vector mapProtoBytes = byte_vector::FromPath(inputPath);
net::eagle0::shardok::common::HexMap mapProto;
mapProto.ParseFromArray(mapProtoBytes.data(), (int)mapProtoBytes.size());
if (!mapProto.ParseFromArray(mapProtoBytes.data(), static_cast<int>(mapProtoBytes.size()))) {
std::cerr << "Failed to parse hex map from " << inputPath << '\n';
return -1;
}
ModifyMap(mapProto);
const std::string outputString = mapProto.SerializeAsString();
if (FilesystemUtils::AtomicallySaveToPath(outputPath, byte_vector(outputString))) return 0;
printf("Failed to save.\n");
std::cerr << "Failed to save.\n";
return -1;
}
void ModifyMap(HexMapProto& map) {
for (int i = 0; i < map.terrain_size(); i++) {
const int terrainCount = map.terrain_size();
for (int i = 0; i < terrainCount; i++) {
const auto existingTerrainType = map.terrain(i).type();
const auto newTerrain =
(net::eagle0::shardok::common::Terrain_Type)(existingTerrainType + 2);
static_cast<net::eagle0::shardok::common::Terrain_Type>(existingTerrainType + 2);
map.mutable_terrain(i)->set_type(newTerrain);
}
}
@@ -4,50 +4,52 @@
#include "MapInfoCalculator.hpp"
#include <cstddef>
#include <cstdlib>
#include <iostream>
#include "src/main/cpp/net/eagle0/shardok/ai/AIWaterCrossingCalculator.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/fb_helpers/HexMapHelpers.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/util/HexMapUtils.hpp"
#include "src/main/cpp/net/eagle0/shardok/util/MapLoader.hpp"
using namespace shardok;
using std::endl;
auto LoadMapFb(const string& mapName) -> Wrapper<HexMap> {
auto LoadMapFb(const std::string& mapName) -> shardok::Wrapper<shardok::HexMap> {
const HexMapProto mapProto = LoadMap(mapName);
flatbuffers::FlatBufferBuilder fbb{};
const auto hexMapOffset = fb::ConvertHexMapProto(fbb, mapProto);
const auto hexMapOffset = shardok::fb::ConvertHexMapProto(fbb, mapProto);
fbb.Finish(hexMapOffset);
return Wrapper<HexMap>(fbb);
return shardok::Wrapper<shardok::HexMap>(fbb);
}
auto CalculateMap(
const string& mapName,
const std::shared_ptr<ActionPointDistancesCache>& apdCache,
const BattalionTypeSPtr& battalionType) -> OneMapInfo {
const std::string& mapName,
const std::shared_ptr<shardok::ActionPointDistancesCache>& apdCache,
const shardok::BattalionTypeSPtr& battalionType) -> OneMapInfo {
auto hexMap = LoadMapFb(mapName);
const auto mapId = ActionPointDistancesCache::GetMapId(hexMap);
const auto mapId = shardok::ActionPointDistancesCache::GetMapId(hexMap);
const CoordsSet criticalTileLocations = GetCriticalTileLocations(hexMap);
const shardok::CoordsSet criticalTileLocations = shardok::GetCriticalTileLocations(hexMap);
OneMapInfo info{
.castleCount = criticalTileLocations.size(),
.name = mapName,
.positionsRequiringCrossing = {}};
for (unsigned int i = 0; i < hexMap->attacker_starting_positions()->size(); i++) {
for (std::size_t i = 0; i < hexMap->attacker_starting_positions()->size(); i++) {
const auto* positionList = hexMap->attacker_starting_positions()->Get(i);
if (positionList->positions()->size() < 1) continue;
if (positionList->positions()->size() != 10) {
printf("Should have 10 starting positions!");
abort();
const auto* positions = positionList->positions();
if (positions->empty()) continue;
if (positions->size() != 10) {
std::cerr << "Should have 10 starting positions!" << '\n';
std::abort();
}
int positionsRequiringCrossing = 0;
for (const auto* startingPosition : *positionList->positions()) {
for (const Coords& castlePosition : criticalTileLocations) {
if (!CanReach(
for (const auto* startingPosition : *positions) {
for (const shardok::Coords& castlePosition : criticalTileLocations) {
if (!shardok::CanReach(
*startingPosition,
castlePosition,
hexMap,
@@ -56,11 +58,11 @@ auto CalculateMap(
battalionType)) {
++positionsRequiringCrossing;
break;
};
}
}
}
info.positionsRequiringCrossing[i] = positionsRequiringCrossing;
info.positionsRequiringCrossing[static_cast<int>(i)] = positionsRequiringCrossing;
}
return info;
}
}
@@ -12,19 +12,15 @@
#include "src/main/cpp/net/eagle0/shardok/library/action_point_distances/ActionPointDistancesCache.hpp"
using namespace shardok;
using std::map;
using std::string;
struct OneMapInfo {
struct alignas(64) OneMapInfo {
uint32_t castleCount;
string name;
map<int, int> positionsRequiringCrossing;
} __attribute__((aligned(64)));
std::string name;
std::map<int, int> positionsRequiringCrossing;
};
auto CalculateMap(
const string& mapName,
const std::shared_ptr<ActionPointDistancesCache>& apdCache,
const BattalionTypeSPtr& battalionType) -> OneMapInfo;
const std::string& mapName,
const std::shared_ptr<shardok::ActionPointDistancesCache>& apdCache,
const shardok::BattalionTypeSPtr& battalionType) -> OneMapInfo;
#endif // EAGLE0_MAPINFOCALCULATOR_HPP
@@ -2,37 +2,42 @@
// Created by Dan Crosby on 4/18/22.
//
#include <fstream>
#include <iostream>
#include <memory>
#include <string>
#include <vector>
#include "MapInfoCalculator.hpp"
#include "src/main/cpp/net/eagle0/common/FilesystemUtils.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/settings/GameSettings.hpp"
#include "src/main/cpp/net/eagle0/shardok/util/BattalionTypeRegistrar.hpp"
#include "src/main/cpp/net/eagle0/shardok/util/MapLoader.hpp"
using namespace shardok;
using std::endl;
auto main(const int argc, char** argv) -> int {
FilesystemUtils::SetExecPath(argv[0]);
std::ostream* outputStreamPtr = &std::cout;
std::ofstream outputFile{};
if (argc > 1) {
const string outputPath = argv[1];
outputStreamPtr = new std::ofstream(outputPath);
const std::string outputPath = argv[1];
outputFile.open(outputPath);
outputStreamPtr = &outputFile;
}
std::ostream& outputStream = *outputStreamPtr;
const auto gameSettings = std::make_shared<GameSettings>();
const auto gameSettings = std::make_shared<shardok::GameSettings>();
auto setter = gameSettings->GetSetter();
BattalionTypeRegistrar::RegisterBattalionTypes(setter);
const BattalionTypeSPtr& battalionType = gameSettings->GetGetter().GetBattalionType(
shardok::BattalionTypeRegistrar::RegisterBattalionTypes(setter);
const shardok::BattalionTypeSPtr& battalionType = gameSettings->GetGetter().GetBattalionType(
net::eagle0::shardok::storage::fb::BattalionTypeId_LIGHT_INFANTRY);
vector<OneMapInfo> mapInfos{};
for (const string& mapName : GetMapNames()) {
std::shared_ptr<ActionPointDistancesCache> apdCache =
std::make_shared<ActionPointDistancesCache>();
const std::vector<std::string> mapNames = GetMapNames();
std::vector<OneMapInfo> mapInfos{};
mapInfos.reserve(mapNames.size());
for (const std::string& mapName : mapNames) {
std::shared_ptr<shardok::ActionPointDistancesCache> apdCache =
std::make_shared<shardok::ActionPointDistancesCache>();
mapInfos.push_back(CalculateMap(mapName, apdCache, battalionType));
}
@@ -41,29 +46,29 @@ auto main(const int argc, char** argv) -> int {
bool firstMap = true;
for (const OneMapInfo& mapInfo : mapInfos) {
if (firstMap) {
outputStream << endl;
outputStream << '\n';
firstMap = false;
} else {
outputStream << "," << endl;
outputStream << "," << '\n';
}
outputStream << " {" << endl;
outputStream << R"( "name": ")" << mapInfo.name << "\"," << endl;
outputStream << " {" << '\n';
outputStream << R"( "name": ")" << mapInfo.name << "\"," << '\n';
outputStream << " \"castleCount\": " << mapInfo.castleCount << "," << endl;
outputStream << " \"castleCount\": " << mapInfo.castleCount << "," << '\n';
outputStream << " \"positions\": {";
bool firstPosition = true;
for (const auto& [position, count] : mapInfo.positionsRequiringCrossing) {
if (firstPosition) {
outputStream << endl;
outputStream << '\n';
firstPosition = false;
} else {
outputStream << "," << endl;
outputStream << "," << '\n';
}
outputStream << " \"" << position << "\": " << count;
}
outputStream << endl << " }" << endl << " }";
outputStream << '\n' << " }" << '\n' << " }";
}
outputStream << endl << "]" << endl;
outputStream << '\n' << "]" << '\n';
}
@@ -2,10 +2,17 @@
// Created by Dan Crosby on 3/29/21.
//
#include <algorithm>
#include <array>
#include <climits>
#include <iostream>
#include <memory>
#include <numeric>
#include <mutex>
#include <random>
#include <stdexcept>
#include <thread>
#include <utility>
#include <vector>
#include "src/main/cpp/net/eagle0/common/FilesystemUtils.hpp"
#include "src/main/cpp/net/eagle0/common/UnitConversions.hpp"
@@ -14,8 +21,6 @@
#include "src/main/protobuf/net/eagle0/common/random_units.pb.h"
using net::eagle0::common::RandomUnits;
using std::shared_ptr;
using std::vector;
constexpr int THREAD_COUNT = 16;
@@ -28,96 +33,130 @@ std::random_device rd;
std::mt19937 g(rd());
#endif
vector<string> fileNames{};
std::mutex randomMutex;
std::vector<std::string> fileNames{};
using namespace shardok;
ShardokGamesManager* manager;
std::unique_ptr<shardok::ShardokGamesManager> manager;
auto randInt(const int lowInc, const int hiInc) {
std::lock_guard<std::mutex> lock(randomMutex);
return std::uniform_int_distribution(lowInc, hiInc)(g);
}
void runBattle(const vector<shared_ptr<const UnitProto>>& units) {
auto mapName = fileNames[randInt(0, (int)fileNames.size() - 1)];
auto month = randInt(1, 12);
void runBattle(const std::vector<shardok::Unit>& units) {
if (fileNames.empty()) { throw std::runtime_error("No map files found"); }
if (units.size() < 2) { throw std::runtime_error("Need at least two units to run a battle"); }
auto p1UnitCount = randInt(1, 20);
vector<UnitProto> p1Units{};
const auto mapName = fileNames[randInt(0, static_cast<int>(fileNames.size()) - 1)];
const auto month = randInt(1, 12);
const auto maxP1UnitCount = std::min<size_t>(20, units.size() - 1);
const auto p1UnitCount = randInt(1, static_cast<int>(maxP1UnitCount));
std::vector<shardok::Unit> p1Units{};
p1Units.reserve(p1UnitCount);
for (int i = 0; i < p1UnitCount; i++) {
auto unit = *units[i];
unit.set_player_id(0);
p1Units.push_back(unit);
auto unit = units[i];
unit.mutate_player_id(0);
p1Units.push_back(std::move(unit));
}
auto p2UnitCount = randInt(1, 20);
vector<UnitProto> p2Units{};
const auto maxP2UnitCount = std::min<size_t>(20, units.size() - p1UnitCount);
const auto p2UnitCount = randInt(1, static_cast<int>(maxP2UnitCount));
std::vector<shardok::Unit> p2Units{};
p2Units.reserve(p2UnitCount);
for (int i = p1UnitCount; i < p1UnitCount + p2UnitCount; i++) {
auto unit = *units[i];
unit.set_player_id(1);
p2Units.push_back(unit);
auto unit = units[i];
unit.mutate_player_id(1);
p2Units.push_back(std::move(unit));
}
printf("map name is %s, %d attacking %d\n", mapName.c_str(), p1UnitCount, p2UnitCount);
std::cout << "map name is " << mapName << ", " << p1UnitCount << " attacking " << p2UnitCount
<< '\n';
vector<VictoryConditionProto> attVictoryConditions{
std::vector<VictoryConditionProto> attVictoryConditions{
VictoryConditionProto::VICTORY_CONDITION_HOLDS_CRITICAL_TILES,
VictoryConditionProto::VICTORY_CONDITION_LAST_PLAYER_STANDING};
vector<VictoryConditionProto> defVictoryConditions{
std::vector<VictoryConditionProto> defVictoryConditions{
VictoryConditionProto::VICTORY_CONDITION_LAST_PLAYER_STANDING,
VictoryConditionProto::VICTORY_CONDITION_WIN_AFTER_MAX_ROUNDS};
auto p1 = PlayerInfoWithUnits(0, true, 5, false, 10000, attVictoryConditions, {}, p1Units);
auto p2 = PlayerInfoWithUnits(1, false, 7, false, 10000, defVictoryConditions, {}, p2Units);
std::vector<shardok::PlayerInfoWithUnits> playerInfos;
playerInfos.reserve(2);
playerInfos.emplace_back(
0,
true,
5,
false,
10000,
attVictoryConditions,
std::vector<shardok::PlayerId>{},
p1Units,
false);
playerInfos.emplace_back(
1,
true,
7,
true,
10000,
defVictoryConditions,
std::vector<shardok::PlayerId>{},
p2Units,
false);
string gameId = std::to_string(randInt(INT_MIN, INT_MAX));
const std::string gameId = std::to_string(randInt(INT_MIN, INT_MAX));
manager->LockedCreateSpecifiedGame(gameId, {p1, p2}, mapName, "", month, "");
manager->UnlockedCreateSpecifiedGame(
gameId,
playerInfos,
mapName,
"",
month,
TutorialBattleConfigProto{},
"");
}
auto LoadUnits() -> vector<shared_ptr<const UnitProto>> {
auto LoadUnits() -> std::vector<shardok::Unit> {
const auto bytes = byte_vector::FromPath("/tmp/random_heroes.bin");
RandomUnits commonUnits;
commonUnits.ParseFromArray(bytes.data(), (int)bytes.size());
printf("Loaded %d units\n", commonUnits.units_size());
if (!commonUnits.ParseFromArray(bytes.data(), static_cast<int>(bytes.size()))) {
throw std::runtime_error("Failed to parse /tmp/random_heroes.bin");
}
std::cout << "Loaded " << commonUnits.units_size() << " units\n";
vector<shared_ptr<const UnitProto>> units{};
std::vector<shardok::Unit> units{};
units.reserve(commonUnits.units_size());
for (const auto& commonUnit : commonUnits.units()) {
// FIXME: assumes 2 players
units.push_back(std::make_shared<const UnitProto>(ConvertUnit(commonUnit, 0, {0, 1})));
units.push_back(shardok::ConvertUnit(commonUnit, 0, {0, 1}));
}
std::shuffle(units.begin(), units.end(), g);
return units;
}
[[noreturn]] void runBattlesThread(const vector<shared_ptr<const UnitProto>>& units) {
[[noreturn]] void runBattlesThread(const std::vector<shardok::Unit>& units) {
while (true) { runBattle(units); }
}
auto main(int argc, char** argv) -> int {
auto main(int /*argc*/, char** argv) -> int {
#if FIXED_SEED
std::srand(4564564);
g.seed(378473);
#else
std::srand(std::time(nullptr));
#endif
FilesystemUtils::SetExecPath(argv[0]);
auto randomUnits = LoadUnits();
printf("Map file directory is %s\n", FilesystemUtils::MapFilesDirectory().c_str());
std::cout << "Map file directory is " << FilesystemUtils::MapFilesDirectory() << '\n';
fileNames = FilesystemUtils::FilesInDirectory(FilesystemUtils::MapFilesDirectory(), ".e0m");
for (auto& fileName : fileNames) { fileName.resize(fileName.size() - 4); }
manager = new ShardokGamesManager(nullptr, std::vector<std::string>());
manager = std::make_unique<shardok::ShardokGamesManager>(std::vector<std::string>());
std::thread threads[THREAD_COUNT];
std::array<std::thread, THREAD_COUNT> threads;
for (int i = 0; i < THREAD_COUNT; i++) {
threads[i] = std::thread(runBattlesThread, randomUnits);
@@ -16,9 +16,6 @@ namespace shardok {
constexpr double kOverpowerRatio = 2.0;
constexpr double kBraveWaterCostMultiplier = 1.2;
using std::pair;
using std::shared_ptr;
DIST_T NormalizedCostWhenBraving(const DIST_T cost) {
if (cost >= static_cast<double>(ActionPointDistances::IMPOSSIBLE) / kBraveWaterCostMultiplier)
return ActionPointDistances::IMPOSSIBLE;
@@ -156,6 +153,7 @@ auto GenerateTargetPriorities(
vector<TargetAndAttackLocations>{});
vector<TargetAndDistance> targetsWithDistance;
targetsWithDistance.reserve(targets.size());
// Get APDs directly from cache (now with built-in thread-local optimization)
const auto& battType = battalionTypeGetter(unit->battalion().type());
@@ -235,4 +233,4 @@ auto GenerateTargetPriorities(
return allTargetsUnitsAndDistances;
}
} // namespace shardok
} // namespace shardok
@@ -2,8 +2,8 @@
// Created by Dan Crosby on 6/19/23.
//
#ifndef EAGLE0_AIATTACKGROUPS_HPP
#define EAGLE0_AIATTACKGROUPS_HPP
#ifndef EAGLE0_SHARDOK_AI_AI_ATTACK_GROUPS_HPP
#define EAGLE0_SHARDOK_AI_AI_ATTACK_GROUPS_HPP
#include <functional>
#include <vector>
@@ -20,7 +20,6 @@ namespace shardok {
using GameState = net::eagle0::shardok::storage::fb::GameState;
using Unit = net::eagle0::shardok::storage::fb::Unit;
using net::eagle0::shardok::storage::fb::PlayerInfo;
using std::vector;
using BattalionTypeGetter = std::function<BattalionTypeSPtr(BattalionTypeId)>;
@@ -33,9 +32,9 @@ struct TargetAndAttackLocations {
struct TargetPriorityList {
UnitId attackingUnitId;
vector<TargetAndAttackLocations> priorityOrder;
std::vector<TargetAndAttackLocations> priorityOrder;
TargetPriorityList(const UnitId uid, const vector<TargetAndAttackLocations>& po)
TargetPriorityList(const UnitId uid, const std::vector<TargetAndAttackLocations>& po)
: attackingUnitId(uid),
priorityOrder(po) {}
};
@@ -64,17 +63,17 @@ auto EffectiveDistance(
// Chooses a list of targets in priority order for each unit.
auto GenerateTargetPriorities(
const vector<const Unit*>& occupants,
const std::vector<const Unit*>& occupants,
const HexMap* map,
const CoordsSet& targets,
PlayerId attackerId,
const vector<const Unit*>& remainingUnits,
const std::vector<const Unit*>& remainingUnits,
const APDCache& apdCache,
const ALCache& alCache,
const BattalionTypeGetter& battalionTypeGetter,
ActionPoints braveWaterCost,
bool isLateGame = false) -> vector<TargetPriorityList>;
bool isLateGame = false) -> std::vector<TargetPriorityList>;
} // namespace shardok
#endif // EAGLE0_AIATTACKGROUPS_HPP
#endif // EAGLE0_SHARDOK_AI_AI_ATTACK_GROUPS_HPP
@@ -17,10 +17,8 @@
namespace shardok {
using Unit = net::eagle0::shardok::storage::fb::Unit;
using std::shared_ptr;
using std::vector;
class AttackLocations {
class alignas(128) AttackLocations {
private:
const MapIndex rowCount;
const MapIndex columnCount;
@@ -147,9 +145,9 @@ public:
adjacentLocations += cs;
allLocations += cs;
}
} __attribute__((aligned(128)));
};
struct AttackLocationsCache {
struct alignas(128) AttackLocationsCache {
private:
const MapIndex rowCount;
const MapIndex columnCount;
@@ -165,8 +163,8 @@ public:
auto CachedLocations(const Coords& singleTarget, bool lateGame) -> const AttackLocations&;
auto CachedLocations(const CoordsSet& targets, bool lateGame) -> AttackLocations;
auto CachedLocations(const vector<const Unit*>& enemies, bool lateGame) -> AttackLocations;
} __attribute__((aligned(128)));
auto CachedLocations(const std::vector<const Unit*>& enemies, bool lateGame) -> AttackLocations;
};
using ALCache = std::unique_ptr<AttackLocationsCache>;
@@ -33,6 +33,7 @@ auto AIAttackerStrategySelector::BestAttackerStrategy(
bool canFlee = false;
vector<const Unit*> attackerUnits{};
attackerUnits.reserve(gameState->units()->size());
CoordsSet defenderPositions(gameState->hex_map());
for (const Unit* unit : *gameState->units()) {
if (unit->status() != net::eagle0::shardok::storage::fb::UnitStatus_NORMAL_UNIT &&
@@ -131,4 +132,4 @@ auto AIAttackerStrategySelector::BestAttackerStrategy(
return chosenStrategy;
}
} // namespace shardok
} // namespace shardok
@@ -2,8 +2,8 @@
// Created by Dan Crosby on 1/9/22.
//
#ifndef EAGLE0_AIATTACKERSTRATEGYSELECTOR_HPP
#define EAGLE0_AIATTACKERSTRATEGYSELECTOR_HPP
#ifndef EAGLE0_SHARDOK_AI_AI_ATTACKER_STRATEGY_SELECTOR_HPP
#define EAGLE0_SHARDOK_AI_AI_ATTACKER_STRATEGY_SELECTOR_HPP
#include "src/main/cpp/net/eagle0/shardok/ai/AIAttackLocations.hpp"
#include "src/main/cpp/net/eagle0/shardok/ai/AICommonTypes.hpp"
@@ -32,4 +32,4 @@ public:
};
} // namespace shardok
#endif // EAGLE0_AIATTACKERSTRATEGYSELECTOR_HPP
#endif // EAGLE0_SHARDOK_AI_AI_ATTACKER_STRATEGY_SELECTOR_HPP
@@ -8,6 +8,8 @@
#include <chrono>
#include <cmath>
#include <future>
#include <iomanip>
#include <iostream>
#include <limits>
#include "AICommandFilter.hpp"
@@ -22,11 +24,11 @@ namespace shardok {
// No need to forward declare internal functions - use the public interface instead
// Helper constants and static variables
static const std::vector<double> _averageSequence = {0.5};
static const auto _averageGenerator = std::make_shared<SequenceRandomGenerator>(_averageSequence);
static const std::vector<double> kAverageSequence = {0.5};
static const auto kAverageGenerator = std::make_shared<SequenceRandomGenerator>(kAverageSequence);
#define MULTITHREAD true
#define LOGGING_ 0
constexpr bool kMultithread = true;
constexpr bool kLogging = false;
// Helper function to determine if a command type is deterministic
static auto IsDeterministic(const CommandType type) -> bool {
@@ -237,15 +239,16 @@ auto AICommandEvaluator::EvaluateWithRandomness(
return lookaheadFuture.get();
};
#if MULTITHREAD
auto launchPolicy = remainingLookahead == 1 ? std::launch::async : std::launch::deferred;
returnValue.lookaheadScore = std::async(launchPolicy, lookaheadLambda);
#else
std::promise<EvaluationResult> p;
returnValue.lookaheadScore = p.get_future();
auto lambdaResult = lookaheadLambda();
p.set_value(lambdaResult);
#endif
if constexpr (kMultithread) {
auto launchPolicy =
remainingLookahead == 1 ? std::launch::async : std::launch::deferred;
returnValue.lookaheadScore = std::async(launchPolicy, lookaheadLambda);
} else {
std::promise<EvaluationResult> p;
returnValue.lookaheadScore = p.get_future();
auto lambdaResult = lookaheadLambda();
p.set_value(lambdaResult);
}
}
return returnValue;
@@ -302,30 +305,31 @@ auto AICommandEvaluator::FindBestCommand(
minDistToEnemies = 0.0; // No enemies found
}
#if LOGGING_
// Log command count and distance metrics for performance analysis
const auto allCommandCount = guessedDescriptors->size();
const auto filteredCommandCount = filteredIndices.size();
const int currentRound = gameState->current_round();
if constexpr (kLogging) {
// Log command count and distance metrics for performance analysis
const auto allCommandCount = guessedDescriptors->size();
const auto filteredCommandCount = filteredIndices.size();
const int currentRound = gameState->current_round();
printf("AI_COMMAND_COUNT: Round %d, Player %d, Defender %d, MinDist %.1f, Commands %zu -> %zu "
"(%.1f%% filtered)\n",
currentRound,
static_cast<int>(pid),
isDefender ? 1 : 0,
minDistToEnemies,
allCommandCount,
filteredCommandCount,
100.0 * (allCommandCount - filteredCommandCount) / allCommandCount);
#endif
const auto flags = std::cout.flags();
const auto precision = std::cout.precision();
std::cout << "AI_COMMAND_COUNT: Round " << currentRound << ", Player "
<< static_cast<int>(pid) << ", Defender " << (isDefender ? 1 : 0) << ", MinDist "
<< std::fixed << std::setprecision(1) << minDistToEnemies << ", Commands "
<< allCommandCount << " -> " << filteredCommandCount << " ("
<< 100.0 * (allCommandCount - filteredCommandCount) / allCommandCount
<< "% filtered)\n";
std::cout.flags(flags);
std::cout.precision(precision);
}
const auto commandCount = filteredIndices.size();
// Structure to hold all command evaluation data
struct CommandEvaluation {
size_t index;
CommandType type;
ScoreValue immediateScore;
size_t index{};
CommandType type{};
ScoreValue immediateScore{};
bool immediateCompleted = true;
std::vector<std::future<EvaluationResult>> lookaheadFutures;
};
@@ -352,7 +356,7 @@ auto AICommandEvaluator::FindBestCommand(
originalIndex,
remainingLookahead,
maxRepeatCount,
_averageGenerator,
kAverageGenerator,
guessedEngine,
attackerStrategy,
allCastleCoords,
@@ -418,6 +422,7 @@ auto AICommandEvaluator::FindBestCommand(
} else {
ScoreValue sum = 0.0;
const int sampleCount = std::max(1, maxRepeatCount);
commandEvaluations[index].lookaheadFutures.reserve(sampleCount);
for (int repeatIteration = 0; repeatIteration < sampleCount; repeatIteration++) {
// In each iteration, use a double from [0, 1] as the random roll
auto sequence =
@@ -428,7 +433,7 @@ auto AICommandEvaluator::FindBestCommand(
originalIndex,
remainingLookahead,
maxRepeatCount,
std::make_shared<SequenceRandomGenerator>(sequence),
std::make_shared<SequenceRandomGenerator>(std::move(sequence)),
guessedEngine,
attackerStrategy,
allCastleCoords,
@@ -507,26 +512,30 @@ auto AICommandEvaluator::EvaluateCommand(
}
const auto& guessedDescriptor = guessedDescriptors->at(commandIndex);
const auto guessedCommandType = guessedDescriptor->GetCommandType();
if (const auto guessedCommandType = guessedDescriptor->GetCommandType();
guessedCommandType == net::eagle0::shardok::common::END_TURN_COMMAND) {
if (guessedCommandType == net::eagle0::shardok::common::END_TURN_COMMAND) {
std::promise<EvaluationResult> p;
p.set_value(EvaluationResult{.score = currentUtility, .completed = true});
return p.get_future();
} else if (IsDeterministic(guessedCommandType)) {
}
if (IsDeterministic(guessedCommandType)) {
auto evaluation = EvaluateWithRandomness(
pid,
isDefender,
commandIndex,
remainingLookahead,
maxRepeatCount,
_averageGenerator,
kAverageGenerator,
guessedEngine,
attackerStrategy,
allCastleCoords,
deadline);
return std::move(evaluation.lookaheadScore);
} else if (guessedDescriptor->HasOdds()) {
}
if (guessedDescriptor->HasOdds()) {
const auto successChancePercentile = guessedDescriptor->GetOddsPercentile();
const double successChance = static_cast<double>(successChancePercentile) / 100.0;
@@ -571,47 +580,47 @@ auto AICommandEvaluator::EvaluateCommand(
.score = std::lerp(failure.score, success.score, successChance),
.completed = true};
});
} else {
// For non-deterministic commands without odds, use multiple attempts
std::vector<std::future<EvaluationResult>> lookaheadFutures;
const int sampleCount = std::max(1, maxRepeatCount);
lookaheadFutures.reserve(sampleCount);
for (int repeatIteration = 0; repeatIteration < sampleCount; repeatIteration++) {
auto sequence = std::vector{RandomnessSampleForRepeat(repeatIteration, sampleCount)};
auto evaluation = EvaluateWithRandomness(
pid,
isDefender,
commandIndex,
remainingLookahead,
maxRepeatCount,
std::make_shared<SequenceRandomGenerator>(sequence),
guessedEngine,
attackerStrategy,
allCastleCoords,
deadline);
lookaheadFutures.push_back(std::move(evaluation.lookaheadScore));
}
// Return a future that computes the average when needed
return std::async(
std::launch::deferred,
[lookaheadFutures = std::move(lookaheadFutures),
sampleCount]() mutable -> EvaluationResult {
ScoreValue total = 0.0;
bool completed = true;
for (auto& future : lookaheadFutures) {
const auto result = future.get();
if (!result.completed) { completed = false; }
total += result.score;
}
if (!completed) { return EvaluationResult{.score = 0.0, .completed = false}; }
return EvaluationResult{
.score = total / static_cast<ScoreValue>(sampleCount),
.completed = true};
});
}
// For non-deterministic commands without odds, use multiple attempts
std::vector<std::future<EvaluationResult>> lookaheadFutures;
const int sampleCount = std::max(1, maxRepeatCount);
lookaheadFutures.reserve(sampleCount);
for (int repeatIteration = 0; repeatIteration < sampleCount; repeatIteration++) {
auto sequence = std::vector{RandomnessSampleForRepeat(repeatIteration, sampleCount)};
auto evaluation = EvaluateWithRandomness(
pid,
isDefender,
commandIndex,
remainingLookahead,
maxRepeatCount,
std::make_shared<SequenceRandomGenerator>(std::move(sequence)),
guessedEngine,
attackerStrategy,
allCastleCoords,
deadline);
lookaheadFutures.push_back(std::move(evaluation.lookaheadScore));
}
// Return a future that computes the average when needed
return std::async(
std::launch::deferred,
[lookaheadFutures = std::move(lookaheadFutures),
sampleCount]() mutable -> EvaluationResult {
ScoreValue total = 0.0;
bool completed = true;
for (auto& future : lookaheadFutures) {
const auto result = future.get();
if (!result.completed) { completed = false; }
total += result.score;
}
if (!completed) { return EvaluationResult{.score = 0.0, .completed = false}; }
return EvaluationResult{
.score = total / static_cast<ScoreValue>(sampleCount),
.completed = true};
});
}
} // namespace shardok
@@ -3,8 +3,8 @@
// Separated from AIScoreCalculator to isolate pure state scoring from lookahead logic.
//
#ifndef EAGLE0_AICOMMANDEVALUATOR_HPP
#define EAGLE0_AICOMMANDEVALUATOR_HPP
#ifndef EAGLE0_SHARDOK_AI_AI_COMMAND_EVALUATOR_HPP
#define EAGLE0_SHARDOK_AI_AI_COMMAND_EVALUATOR_HPP
#include <chrono>
#include <future>
@@ -57,11 +57,11 @@ public:
/// Find the best command among all available commands at the given depth.
struct IndexAndScore {
size_t index;
CommandType type;
ScoreValue lookaheadScore;
ScoreValue immediateScore;
bool completed;
size_t index{};
CommandType type{};
ScoreValue lookaheadScore{};
ScoreValue immediateScore{};
bool completed{};
};
[[nodiscard]] auto FindBestCommand(
@@ -81,8 +81,8 @@ private:
BattalionTypeGetter battalionTypeGetter_; // Store by value
struct ImmediateAndLookaheadScore {
ScoreValue immediateScore;
bool completed;
ScoreValue immediateScore{};
bool completed{};
std::future<EvaluationResult> lookaheadScore;
};
@@ -114,4 +114,4 @@ private:
} // namespace shardok
#endif // EAGLE0_AICOMMANDEVALUATOR_HPP
#endif // EAGLE0_SHARDOK_AI_AI_COMMAND_EVALUATOR_HPP
@@ -2,8 +2,8 @@
// Filter obviously bad commands to reduce search space for AI
//
#ifndef EAGLE0_AICOMMANDFILTER_HPP
#define EAGLE0_AICOMMANDFILTER_HPP
#ifndef EAGLE0_SHARDOK_AI_AI_COMMAND_FILTER_HPP
#define EAGLE0_SHARDOK_AI_AI_COMMAND_FILTER_HPP
#include <memory>
#include <vector>
@@ -116,4 +116,4 @@ private:
} // namespace shardok
#endif // EAGLE0_AICOMMANDFILTER_HPP
#endif // EAGLE0_SHARDOK_AI_AI_COMMAND_FILTER_HPP
@@ -3,8 +3,8 @@
// Common type definitions used across AI utility functions
//
#ifndef EAGLE0_AICOMMONTYPES_HPP
#define EAGLE0_AICOMMONTYPES_HPP
#ifndef EAGLE0_SHARDOK_AI_AI_COMMON_TYPES_HPP
#define EAGLE0_SHARDOK_AI_AI_COMMON_TYPES_HPP
#include <functional>
@@ -20,4 +20,4 @@ using BattalionTypeGetter = std::function<BattalionTypeSPtr(BattalionTypeId)>;
} // namespace shardok
#endif // EAGLE0_AICOMMONTYPES_HPP
#endif // EAGLE0_SHARDOK_AI_AI_COMMON_TYPES_HPP
@@ -2,8 +2,8 @@
// AI System Types and Configuration
//
#ifndef EAGLE0_AI_CONFIG_HPP
#define EAGLE0_AI_CONFIG_HPP
#ifndef EAGLE0_SHARDOK_AI_AI_CONFIG_HPP
#define EAGLE0_SHARDOK_AI_AI_CONFIG_HPP
namespace shardok {
@@ -22,4 +22,4 @@ enum class ScoringCalculatorType {
} // namespace shardok
#endif // EAGLE0_AI_CONFIG_HPP
#endif // EAGLE0_SHARDOK_AI_AI_CONFIG_HPP
@@ -32,6 +32,7 @@ auto AIDefenderStrategySelector::BestDefenderStrategy(
bool canFlee = false;
vector<UnitId> attackerUnitIdsRequiringWaterCrossing{};
attackerUnitIdsRequiringWaterCrossing.reserve(gameState->units()->size());
for (const auto& player : *gameState->player_infos()) {
if (!player->is_defender()) {
const auto& unitIdsRequiringWaterCrossing = UnitIdsRequiringWaterCrossing(
@@ -2,8 +2,8 @@
// Created by Dan Crosby on 8/30/23.
//
#ifndef EAGLE0_AIDEFENDERSTRATEGYSELECTOR_HPP
#define EAGLE0_AIDEFENDERSTRATEGYSELECTOR_HPP
#ifndef EAGLE0_SHARDOK_AI_AI_DEFENDER_STRATEGY_SELECTOR_HPP
#define EAGLE0_SHARDOK_AI_AI_DEFENDER_STRATEGY_SELECTOR_HPP
#include "src/main/cpp/net/eagle0/shardok/ai/AIAttackLocations.hpp"
#include "src/main/cpp/net/eagle0/shardok/ai/AICommonTypes.hpp"
@@ -26,4 +26,4 @@ public:
};
} // namespace shardok
#endif // EAGLE0_AIDEFENDERSTRATEGYSELECTOR_HPP
#endif // EAGLE0_SHARDOK_AI_AI_DEFENDER_STRATEGY_SELECTOR_HPP
@@ -55,12 +55,12 @@ auto DefenderDistanceBuf(
const bool includeUndead) -> double {
const auto &locationsToAttackMe = alCache->CachedLocations(defenderLocation, lateGame);
struct WithoutAndWith {
struct alignas(8) WithoutAndWith {
int without;
int with;
WithoutAndWith(const int a, const int b) : without(a), with(b) {}
} __attribute__((aligned(8)));
};
vector<WithoutAndWith> pointCosts{};
pointCosts.reserve(attackerUnits.size());
@@ -2,8 +2,8 @@
// Created by Dan Crosby on 5/10/21.
//
#ifndef EAGLE0_AIDISTANCEDEBUF_HPP
#define EAGLE0_AIDISTANCEDEBUF_HPP
#ifndef EAGLE0_SHARDOK_AI_AI_DISTANCE_DEBUF_HPP
#define EAGLE0_SHARDOK_AI_AI_DISTANCE_DEBUF_HPP
#include "AIAttackLocations.hpp"
#include "src/main/cpp/net/eagle0/shardok/ai/AICommonTypes.hpp"
@@ -13,14 +13,11 @@
namespace shardok {
using std::shared_ptr;
using std::vector;
auto DefenderDistanceBuf(
const Coords &defenderLocation,
const HexMap *hexMap,
const MapId &mapId,
const vector<const Unit *> &attackerUnits,
const std::vector<const Unit *> &attackerUnits,
const APDCache &apdCache,
const ALCache &alCache,
const BattalionTypeGetter &battalionTypeGetter,
@@ -29,4 +26,4 @@ auto DefenderDistanceBuf(
bool includeUndead) -> double;
} // namespace shardok
#endif // EAGLE0_AIDISTANCEDEBUF_HPP
#endif // EAGLE0_SHARDOK_AI_AI_DISTANCE_DEBUF_HPP
@@ -2,22 +2,34 @@
// Temporary AI experiment switches used by benchmark-only EXPERIMENTAL runs.
//
#ifndef EAGLE0_AI_EXPERIMENT_CONFIG_HPP
#define EAGLE0_AI_EXPERIMENT_CONFIG_HPP
#ifndef EAGLE0_SHARDOK_AI_AI_EXPERIMENT_CONFIG_HPP
#define EAGLE0_SHARDOK_AI_AI_EXPERIMENT_CONFIG_HPP
#include <charconv>
#include <cstdlib>
#include <string>
#include <string_view>
#include <system_error>
namespace shardok {
inline auto CurrentAIExperimentId() -> int {
const char* rawValue = std::getenv("SHARDOK_SCORING_EXPERIMENT_ID");
if (rawValue == nullptr || std::string(rawValue).empty()) { return 0; }
return std::atoi(rawValue);
if (rawValue == nullptr) { return 0; }
const std::string_view value(rawValue);
if (value.empty()) { return 0; }
int experimentId = 0;
const auto parseResult =
std::from_chars(value.data(), value.data() + value.size(), experimentId);
if (parseResult.ec != std::errc{} || parseResult.ptr != value.data() + value.size()) {
return 0;
}
return experimentId;
}
inline auto IsAIExperiment(const int id) -> bool { return CurrentAIExperimentId() == id; }
} // namespace shardok
#endif // EAGLE0_AI_EXPERIMENT_CONFIG_HPP
#endif // EAGLE0_SHARDOK_AI_AI_EXPERIMENT_CONFIG_HPP
@@ -8,12 +8,29 @@
#include "AIFleeDecisionCalculator.hpp"
#include <iomanip>
#include <iostream>
#include "src/main/cpp/net/eagle0/shardok/ai/AIScoreUtilities.hpp"
#include "src/main/cpp/net/eagle0/shardok/ai/AIUnitScoreCalculator.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/util/HexMapUtils.hpp"
namespace shardok {
namespace {
void WritePercent(std::ostream& output, const double value) {
const auto flags = output.flags();
const auto precision = output.precision();
output << std::fixed << std::setprecision(1) << value;
output.flags(flags);
output.precision(precision);
}
} // namespace
auto AIFleeDecisionCalculator::GetFleeCommandIndex(
const CommandList::const_iterator& fleeCommand,
const CommandListSPtr& availableCommands) -> size_t {
@@ -144,15 +161,14 @@ auto AIFleeDecisionCalculator::EvaluateFleeVsFight(
const int fleeSuccessChance = (*fleeCommand)->GetOddsPercentile();
if (enableDebugLogging) {
printf("AI FinalRound: Evaluating flee (odds=%d%%)...\n", fleeSuccessChance);
std::cout << "AI FinalRound: Evaluating flee (odds=" << fleeSuccessChance << "%)...\n";
}
// Check if flee odds are good enough to attempt
if (fleeSuccessChance >= minimumFleeOddsThreshold) {
if (enableDebugLogging) {
printf("AI FinalRound: Good flee odds (%d%% >= %d%%), choosing flee\n",
fleeSuccessChance,
minimumFleeOddsThreshold);
std::cout << "AI FinalRound: Good flee odds (" << fleeSuccessChance
<< "% >= " << minimumFleeOddsThreshold << "%), choosing flee\n";
}
return FleeDecision{
true,
@@ -166,9 +182,9 @@ auto AIFleeDecisionCalculator::EvaluateFleeVsFight(
// If combat situation is hopeless, even bad flee odds are better than certain death
if (combatWinChance <= 0.05 && fleeSuccessChance >= desperateFleeThreshold) {
if (enableDebugLogging) {
printf("AI FinalRound: Combat hopeless (%.1f%%), desperate flee attempt (%d%%)\n",
combatWinChance * 100,
fleeSuccessChance);
std::cout << "AI FinalRound: Combat hopeless (";
WritePercent(std::cout, combatWinChance * 100);
std::cout << "%), desperate flee attempt (" << fleeSuccessChance << "%)\n";
}
return FleeDecision{
true,
@@ -188,20 +204,21 @@ auto AIFleeDecisionCalculator::EvaluateFleeVsFight(
const double adjustedCombatThreshold = combatWinChance * FLEE_VS_COMBAT_MARGIN;
if (enableDebugLogging) {
printf("AI FinalRound: Flee=%d%%, Combat=%.1f%%, Threshold=%.1f%% -> ",
fleeSuccessChance,
combatWinChance * 100,
adjustedCombatThreshold * 100);
std::cout << "AI FinalRound: Flee=" << fleeSuccessChance << "%, Combat=";
WritePercent(std::cout, combatWinChance * 100);
std::cout << "%, Threshold=";
WritePercent(std::cout, adjustedCombatThreshold * 100);
std::cout << "% -> ";
}
if (fleeChance > adjustedCombatThreshold) {
if (enableDebugLogging) { printf("FLEE (better odds)\n"); }
if (enableDebugLogging) { std::cout << "FLEE (better odds)\n"; }
return FleeDecision{
true,
GetFleeCommandIndex(fleeCommand, availableCommands),
"Flee has better expected outcome"};
} else {
if (enableDebugLogging) { printf("FIGHT (better expected outcome)\n"); }
if (enableDebugLogging) { std::cout << "FIGHT (better expected outcome)\n"; }
// Return 0 to indicate we should use standard command selection
return FleeDecision{
false,
@@ -6,8 +6,8 @@
// and flee vs fight evaluation for final round scenarios
//
#ifndef AIFleeDecisionCalculator_hpp
#define AIFleeDecisionCalculator_hpp
#ifndef EAGLE0_SHARDOK_AI_AI_FLEE_DECISION_CALCULATOR_HPP
#define EAGLE0_SHARDOK_AI_AI_FLEE_DECISION_CALCULATOR_HPP
#include "src/main/cpp/net/eagle0/shardok/library/ShardokCommand.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/ShardokEngine.hpp"
@@ -63,4 +63,4 @@ private:
} // namespace shardok
#endif /* AIFleeDecisionCalculator_hpp */
#endif // EAGLE0_SHARDOK_AI_AI_FLEE_DECISION_CALCULATOR_HPP
@@ -3,8 +3,8 @@
// Provides O(1) weights based on command type and context
//
#ifndef EAGLE0_AI_HEURISTIC_WEIGHTING_HPP
#define EAGLE0_AI_HEURISTIC_WEIGHTING_HPP
#ifndef EAGLE0_SHARDOK_AI_AI_HEURISTIC_WEIGHTING_HPP
#define EAGLE0_SHARDOK_AI_AI_HEURISTIC_WEIGHTING_HPP
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-redundant-constexpr-static-def"
@@ -37,4 +37,4 @@ public:
} // namespace shardok
#endif // EAGLE0_AI_HEURISTIC_WEIGHTING_HPP
#endif // EAGLE0_SHARDOK_AI_AI_HEURISTIC_WEIGHTING_HPP
@@ -2,11 +2,8 @@
// Created by Dan Crosby on 8/31/23.
//
#ifndef EAGLE0_AIMINIMUMDISTANCEANDTARGET_HPP
#define EAGLE0_AIMINIMUMDISTANCEANDTARGET_HPP
#include <utility>
#include <vector>
#ifndef EAGLE0_SHARDOK_AI_AI_MINIMUM_DISTANCE_AND_TARGET_HPP
#define EAGLE0_SHARDOK_AI_AI_MINIMUM_DISTANCE_AND_TARGET_HPP
#include "src/main/cpp/net/eagle0/shardok/library/action_point_distances/ActionPointDistances.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/map/CoordsSet.hpp"
@@ -15,7 +12,6 @@
namespace shardok {
using net::eagle0::shardok::storage::fb::Coords;
using std::shared_ptr;
struct CoordsAndDistance {
Coords coords;
@@ -34,4 +30,4 @@ auto MinimumDistance(
} // namespace shardok
#endif // EAGLE0_AIMINIMUMDISTANCEANDTARGET_HPP
#endif // EAGLE0_SHARDOK_AI_AI_MINIMUM_DISTANCE_AND_TARGET_HPP
@@ -2,8 +2,8 @@
// Created by Dan Crosby on 4/7/21.
//
#ifndef EAGLE0_AISCOREUTILITIES_HPP
#define EAGLE0_AISCOREUTILITIES_HPP
#ifndef EAGLE0_SHARDOK_AI_AI_SCORE_UTILITIES_HPP
#define EAGLE0_SHARDOK_AI_AI_SCORE_UTILITIES_HPP
#include <vector>
@@ -16,8 +16,6 @@ namespace shardok {
using ScoreValue = double;
using std::vector;
using GameState = net::eagle0::shardok::storage::fb::GameState;
using PlayerInfo = net::eagle0::shardok::storage::fb::PlayerInfo;
using Unit = net::eagle0::shardok::storage::fb::Unit;
@@ -31,4 +29,4 @@ auto PlayerInfoForPid(const GameStateW &, PlayerId pid) -> const PlayerInfo *;
} // namespace shardok
#endif // EAGLE0_AISCOREUTILITIES_HPP
#endif // EAGLE0_SHARDOK_AI_AI_SCORE_UTILITIES_HPP
@@ -2,8 +2,8 @@
// Created by Dan Crosby on 1/10/22.
//
#ifndef EAGLE0_AISTRATEGY_HPP
#define EAGLE0_AISTRATEGY_HPP
#ifndef EAGLE0_SHARDOK_AI_AI_STRATEGY_HPP
#define EAGLE0_SHARDOK_AI_AI_STRATEGY_HPP
#include "src/main/cpp/net/eagle0/shardok/ai/AIAttackGroups.hpp"
@@ -34,4 +34,4 @@ auto CrossRiversStrategy(const CoordsSet& targetLocations) -> AIStrategy;
} // namespace shardok
#endif // EAGLE0_AISTRATEGY_HPP
#endif // EAGLE0_SHARDOK_AI_AI_STRATEGY_HPP
@@ -116,15 +116,6 @@ auto CalculateTimeBudget(
const auto clampedBudgetMs = std::clamp(budgetMs, 200.0, maxBudgetMs);
const auto remainingBudget = std::chrono::milliseconds(static_cast<int64_t>(clampedBudgetMs));
// TEMPORARY DEBUG OUTPUT
printf("[DEBUG CalculateTimeBudget] numCommands=%zu, msPerCommand=%.2f, budgetMs=%.2f, "
"clampedBudgetMs=%.2f, isClose=%d\n",
numCommands,
msPerCommand,
budgetMs,
clampedBudgetMs,
isClose);
// Get minimum depth requirement
const size_t minDepth = settingsGetter.Backing().min_lookahead_turns();
@@ -134,4 +125,4 @@ auto CalculateTimeBudget(
.isCloseToEnemy = isClose};
}
} // namespace shardok
} // namespace shardok
@@ -2,8 +2,8 @@
// Created by Dan Crosby on 07/04/25.
//
#ifndef EAGLE0_AITIMEBUDGET_HPP
#define EAGLE0_AITIMEBUDGET_HPP
#ifndef EAGLE0_SHARDOK_AI_AI_TIME_BUDGET_HPP
#define EAGLE0_SHARDOK_AI_AI_TIME_BUDGET_HPP
#include <atomic>
#include <chrono>
@@ -25,6 +25,11 @@ class AIEvaluationCounter {
public:
AIEvaluationCounter();
~AIEvaluationCounter();
AIEvaluationCounter(const AIEvaluationCounter&) = delete;
auto operator=(const AIEvaluationCounter&) -> AIEvaluationCounter& = delete;
AIEvaluationCounter(AIEvaluationCounter&&) = delete;
auto operator=(AIEvaluationCounter&&) -> AIEvaluationCounter& = delete;
static int GetCurrentCount();
};
@@ -41,11 +46,11 @@ struct AITimeBudget {
// If isAllAiBattle is true, uses allAiBattleTimeBudgetMaximum instead of the normal maximum.
auto CalculateTimeBudget(
PlayerId playerId,
const GameSettingsSPtr &settings,
const GameStateW &state,
const GameSettingsSPtr& settings,
const GameStateW& state,
size_t numCommands,
bool isAllAiBattle) -> AITimeBudget;
} // namespace shardok
#endif // EAGLE0_AITIMEBUDGET_HPP
#endif // EAGLE0_SHARDOK_AI_AI_TIME_BUDGET_HPP
@@ -11,10 +11,6 @@
namespace shardok {
using std::begin;
using std::end;
using std::shared_ptr;
constexpr double kProfessionValue = 200;
constexpr double kVigorScoreMultiplier = 5.0;
constexpr double kCastleMultiplierBonus = 1.0;
@@ -280,7 +276,7 @@ auto GetRangedAttackBonus(
const int meteorRange,
const double minVigorToCast,
const ActionPoints archeryActionPointCost) -> double {
vector<double> rangedAttackValues{};
double bestRangedAttackValue = 0.0;
const auto &enemyUnits = isAttacker ? defenderUnits : attackerUnits;
const auto &friendlyUnits = isAttacker ? attackerUnits : defenderUnits;
@@ -296,7 +292,7 @@ auto GetRangedAttackBonus(
net::eagle0::shardok::storage::fb::MultiroundMagicState_NONE)) {
if (unit->attached_hero().profession_info().meteor_cast_state() ==
net::eagle0::shardok::storage::fb::MultiroundMagicState_NONE) {
rangedAttackValues.push_back(lightningValue(unit));
bestRangedAttackValue = std::max(bestRangedAttackValue, lightningValue(unit));
} else {
const double mv = meteorValue(
unit,
@@ -306,14 +302,14 @@ auto GetRangedAttackBonus(
friendlyUnits,
meteorRange,
minVigorToCast);
rangedAttackValues.push_back(mv);
bestRangedAttackValue = std::max(bestRangedAttackValue, mv);
}
}
if (attackLocations.EngineerLocations().Contains(unitLocation) && unit->has_attached_hero() &&
unit->attached_hero().profession_info().profession() ==
net::eagle0::shardok::storage::fb::Profession_ENGINEER) {
rangedAttackValues.push_back(reduceValue(unit, terrain));
bestRangedAttackValue = std::max(bestRangedAttackValue, reduceValue(unit, terrain));
}
if (attackLocations.NecromancerLocations().Contains(unitLocation) &&
@@ -321,23 +317,24 @@ auto GetRangedAttackBonus(
unit->attached_hero().profession_info().profession() ==
net::eagle0::shardok::storage::fb::Profession_NECROMANCER &&
unit->attached_hero().control_info().controlled_unit_id() != -1) {
rangedAttackValues.push_back(fearValue(enemyUnits));
bestRangedAttackValue = std::max(bestRangedAttackValue, fearValue(enemyUnits));
}
if (attackLocations.ArcheryLocations().Contains(unitLocation) &&
unit->volleys_remaining() > 0) {
rangedAttackValues.push_back(archeryValue(enemyUnits, canShootNow));
bestRangedAttackValue =
std::max(bestRangedAttackValue, archeryValue(enemyUnits, canShootNow));
}
// Longbowmen in castle can archer adjacent
if (attackLocations.AdjacentLocations().Contains(unitLocation) &&
unit->volleys_remaining() > 0 && terrain->modifier().castle().present() &&
unit->battalion().type() == net::eagle0::shardok::storage::fb::BattalionTypeId_LONGBOWMEN) {
rangedAttackValues.push_back(archeryValue(enemyUnits, canShootNow));
bestRangedAttackValue =
std::max(bestRangedAttackValue, archeryValue(enemyUnits, canShootNow));
}
if (rangedAttackValues.empty()) return 0.0;
return *std::max_element(begin(rangedAttackValues), end(rangedAttackValues));
return bestRangedAttackValue;
}
auto UnitValue(
@@ -2,8 +2,8 @@
// Created by Dan Crosby on 4/7/21.
//
#ifndef EAGLE0_AIUNITSCORECALCULATOR_HPP
#define EAGLE0_AIUNITSCORECALCULATOR_HPP
#ifndef EAGLE0_SHARDOK_AI_AI_UNIT_SCORE_CALCULATOR_HPP
#define EAGLE0_SHARDOK_AI_AI_UNIT_SCORE_CALCULATOR_HPP
#include "src/main/cpp/net/eagle0/shardok/library/action_point_distances/ActionPointDistances.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/map/CoordsSet.hpp"
@@ -14,9 +14,6 @@
namespace shardok {
using net::eagle0::shardok::storage::fb::Unit;
using std::shared_ptr;
using std::vector;
using ScoreValue = double;
class AttackLocations;
@@ -29,8 +26,8 @@ auto GetRangedAttackBonus(
const Terrain *terrain,
const AttackLocations &attackLocations,
int roundsRemaining,
const vector<const Unit *> &attackerUnits,
const vector<const Unit *> &defenderUnits,
const std::vector<const Unit *> &attackerUnits,
const std::vector<const Unit *> &defenderUnits,
int meteorRange,
double minVigorToCast,
ActionPoints archeryActionPointCost = 0) -> double;
@@ -38,10 +35,10 @@ auto GetRangedAttackBonus(
auto UnitValue(
const Unit *unit,
bool isAttacker,
const vector<const Unit *> &attackerUnits,
const std::vector<const Unit *> &attackerUnits,
bool attackerWantsCastles,
bool includeCastleBonus,
const vector<const Unit *> &defenderUnits,
const std::vector<const Unit *> &defenderUnits,
const HexMap *map,
int roundsRemaining,
const AttackLocations &locationsThisSideCanAttackFrom,
@@ -53,4 +50,4 @@ auto UnitValue(
} // namespace shardok
#endif // EAGLE0_AIUNITSCORECALCULATOR_HPP
#endif // EAGLE0_SHARDOK_AI_AI_UNIT_SCORE_CALCULATOR_HPP
@@ -2,8 +2,8 @@
// Created by Dan Crosby on 12/29/21.
//
#ifndef EAGLE0_AIWATERCROSSINGCALCULATOR_HPP
#define EAGLE0_AIWATERCROSSINGCALCULATOR_HPP
#ifndef EAGLE0_SHARDOK_AI_AI_WATER_CROSSING_CALCULATOR_HPP
#define EAGLE0_SHARDOK_AI_AI_WATER_CROSSING_CALCULATOR_HPP
#include "src/main/cpp/net/eagle0/shardok/ai/AICommonTypes.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/GameStateW.hpp"
@@ -23,7 +23,8 @@ static inline void AssertValid(const Coords& c, const HexMap* hexMap) {
if (!valid) {
std::stringstream str;
str << "Invalid coords " << (int)c.row() << ", " << (int)c.column();
str << "Invalid coords " << static_cast<int>(c.row()) << ", "
<< static_cast<int>(c.column());
throw ShardokInternalErrorException(str.str());
}
@@ -85,4 +86,4 @@ auto WaterCrossingScore(
} // namespace shardok
#endif // EAGLE0_AIWATERCROSSINGCALCULATOR_HPP
#endif // EAGLE0_SHARDOK_AI_AI_WATER_CROSSING_CALCULATOR_HPP
@@ -2,8 +2,8 @@
// Created by Dan Crosby on 1/5/22.
//
#ifndef EAGLE0_AIWATERCROSSINGCOMMANDCHOOSER_HPP
#define EAGLE0_AIWATERCROSSINGCOMMANDCHOOSER_HPP
#ifndef EAGLE0_SHARDOK_AI_AI_WATER_CROSSING_COMMAND_CHOOSER_HPP
#define EAGLE0_SHARDOK_AI_AI_WATER_CROSSING_COMMAND_CHOOSER_HPP
#include <utility>
@@ -44,4 +44,4 @@ public:
} // namespace shardok
#endif // EAGLE0_AIWATERCROSSINGCOMMANDCHOOSER_HPP
#endif // EAGLE0_SHARDOK_AI_AI_WATER_CROSSING_COMMAND_CHOOSER_HPP
@@ -5,6 +5,8 @@
#include "IterativeDeepeningAI.hpp"
#include <algorithm>
#include <iomanip>
#include <iostream>
#include <limits>
#include <numeric>
#include <utility>
@@ -19,7 +21,21 @@
namespace shardok {
#define DEBUG_ITERATIVE_DEEPENING_TIMINGS 1
constexpr bool kDebugIterativeDeepeningTimings = true;
namespace {
void WriteFixed(std::ostream& output, const double value, const int precision) {
const auto flags = output.flags();
const auto originalPrecision = output.precision();
output << std::fixed << std::setprecision(precision) << value;
output.flags(flags);
output.precision(originalPrecision);
}
} // namespace
IterativeDeepeningAI::IterativeDeepeningAI(
const PlayerId playerId,
@@ -54,9 +70,9 @@ auto IterativeDeepeningAI::IterativeSearch(
// DEBUG: Clear TT to see if that's causing the suspicious depth reaching
// g_transpositionTable.clear(); // Uncomment to test without cross-search caching
if (commands->empty()) {
#if DEBUG_ITERATIVE_DEEPENING_TIMINGS
printf("ID AI: Commands are empty, returning early\n");
#endif
if (kDebugIterativeDeepeningTimings) {
std::cout << "ID AI: Commands are empty, returning early\n";
}
result.searchCompleted = true;
return result;
}
@@ -168,23 +184,23 @@ auto IterativeDeepeningAI::IterativeSearch(
// Log if best command changed from previous depth
if (currentDepth > 1 && currentBestCommand != previousBestCommand) {
#if DEBUG_ITERATIVE_DEEPENING_TIMINGS
printf("ID AI: Best command changed at depth %lu:\n", currentDepth);
printf(" Depth %lu best: command %zu (score %.2f) - type: %s\n",
currentDepth - 1,
previousBestCommand,
scoresByDepth[previousBestCommand][currentDepth - 1],
net::eagle0::shardok::common::CommandType_Name(
(*commands)[previousBestCommand]->GetCommandType())
.c_str());
printf(" Depth %lu best: command %zu (score %.2f) - type: %s\n",
currentDepth,
currentBestCommand,
currentBestScore,
net::eagle0::shardok::common::CommandType_Name(
(*commands)[currentBestCommand]->GetCommandType())
.c_str());
#endif
if (kDebugIterativeDeepeningTimings) {
std::cout << "ID AI: Best command changed at depth " << currentDepth << ":\n"
<< " Depth " << currentDepth - 1 << " best: command "
<< previousBestCommand << " (score ";
WriteFixed(std::cout, scoresByDepth[previousBestCommand][currentDepth - 1], 2);
std::cout << ") - type: "
<< net::eagle0::shardok::common::CommandType_Name(
(*commands)[previousBestCommand]->GetCommandType())
<< '\n'
<< " Depth " << currentDepth << " best: command "
<< currentBestCommand << " (score ";
WriteFixed(std::cout, currentBestScore, 2);
std::cout << ") - type: "
<< net::eagle0::shardok::common::CommandType_Name(
(*commands)[currentBestCommand]->GetCommandType())
<< '\n';
}
}
previousBestCommand = currentBestCommand;
@@ -239,9 +255,9 @@ auto IterativeDeepeningAI::IterativeSearch(
static_cast<double>(initialBudgetMs.count());
if (budgetUsedPercent > 0.5) {
printf("ID AI: Stopping after depth %lu - used %.1f%% of time budget\n",
currentDepth,
budgetUsedPercent * 100);
std::cout << "ID AI: Stopping after depth " << currentDepth << " - used ";
WriteFixed(std::cout, budgetUsedPercent * 100, 1);
std::cout << "% of time budget\n";
completionReason = EvaluationCompletionReason::NOT_ENOUGH_TIME_TO_CONTINUE;
break;
}
@@ -271,9 +287,9 @@ auto IterativeDeepeningAI::IterativeSearch(
// Validation: if completion reason is RAN_OUT_OF_COMMANDS, evaluation should be 100%
if (completionReason == EvaluationCompletionReason::RAN_OUT_OF_COMMANDS &&
result.commandCountEvaluated < result.availableCommandCount) {
printf("ERROR: Completion reason RAN_OUT_OF_COMMANDS but evaluation %lu/%zu < 100%%\n",
result.commandCountEvaluated,
result.availableCommandCount);
std::cerr << "ERROR: Completion reason RAN_OUT_OF_COMMANDS but evaluation "
<< result.commandCountEvaluated << '/' << result.availableCommandCount
<< " < 100%\n";
}
// Print TranspositionTable statistics
@@ -2,8 +2,8 @@
// Created by Dan Crosby on 07/04/25.
//
#ifndef EAGLE0_ITERATIVEDEEPENINGAI_HPP
#define EAGLE0_ITERATIVEDEEPENINGAI_HPP
#ifndef EAGLE0_SHARDOK_AI_ITERATIVE_DEEPENING_AI_HPP
#define EAGLE0_SHARDOK_AI_ITERATIVE_DEEPENING_AI_HPP
#include <chrono>
#include <future>
@@ -111,4 +111,4 @@ private:
} // namespace shardok
#endif // EAGLE0_ITERATIVEDEEPENINGAI_HPP
#endif // EAGLE0_SHARDOK_AI_ITERATIVE_DEEPENING_AI_HPP
@@ -8,8 +8,6 @@
#include "ShardokAIClient.hpp"
#define DEBUG_FLEE_DECISIONS
// Enable to dump game state and debug tree to /tmp for debugging
// #define ENABLE_MCTS_DEBUG_DUMP
@@ -17,9 +15,12 @@
#include <chrono>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <sstream>
#endif
#include <iomanip>
#include <iostream>
#include <set>
#include <unordered_map>
@@ -43,6 +44,7 @@ using net::eagle0::shardok::api::ActionResultView;
using net::eagle0::shardok::api::GameStateView;
static constexpr bool kPerformanceLogging = true;
static constexpr bool kDebugFleeDecisions = true;
void ApplyUpdate(GameStateView & /*currentView*/, const ActionResultView & /*update*/) {}
@@ -175,25 +177,20 @@ auto ShardokAIClient::StandardChooseCommandIndex(
// Verify that the AI's guessed state produces the same available commands as reality
if (commandCount != realAvailableCommands->size()) {
fprintf(stderr,
"AI command count mismatch: guessed=%zu real=%zu player=%d round=%d\n",
commandCount,
realAvailableCommands->size(),
playerId,
guessedState->current_round());
std::cerr << "AI command count mismatch: guessed=" << commandCount
<< " real=" << realAvailableCommands->size() << " player=" << playerId
<< " round=" << guessedState->current_round() << "\n";
const auto maxDump = std::max(commandCount, realAvailableCommands->size());
for (size_t i = 0; i < maxDump; i++) {
const auto *realName = i < realAvailableCommands->size()
? net::eagle0::shardok::common::CommandType_Name(
(*realAvailableCommands)[i]->GetCommandType())
.c_str()
: "(none)";
const auto *guessedName = i < commandCount
? net::eagle0::shardok::common::CommandType_Name(
(*guessedCommands)[i]->GetCommandType())
.c_str()
: "(none)";
const auto realName = i < realAvailableCommands->size()
? net::eagle0::shardok::common::CommandType_Name(
(*realAvailableCommands)[i]->GetCommandType())
: "(none)";
const auto guessedName = i < commandCount
? net::eagle0::shardok::common::CommandType_Name(
(*guessedCommands)[i]->GetCommandType())
: "(none)";
const int realActor = i < realAvailableCommands->size()
? (*realAvailableCommands)[i]->GetActorUnitId()
: -1;
@@ -207,17 +204,10 @@ auto ShardokAIClient::StandardChooseCommandIndex(
: -1;
const int guessedRow = i < commandCount ? (*guessedCommands)[i]->GetTargetRow() : -1;
const int guessedCol = i < commandCount ? (*guessedCommands)[i]->GetTargetColumn() : -1;
fprintf(stderr,
" [%zu] real: %s unit=%d (%d,%d) guessed: %s unit=%d (%d,%d)\n",
i,
realName,
realActor,
realRow,
realCol,
guessedName,
guessedActor,
guessedRow,
guessedCol);
std::cerr << " [" << i << "] real: " << realName << " unit=" << realActor << " ("
<< realRow << "," << realCol << ") guessed: " << guessedName
<< " unit=" << guessedActor << " (" << guessedRow << "," << guessedCol
<< ")\n";
}
// Find positions in real commands that are missing from guessed commands
@@ -239,45 +229,34 @@ auto ShardokAIClient::StandardChooseCommandIndex(
for (const auto &[unitId, positions] : realPositions) {
for (const auto &[row, col] : positions) {
if (!guessedPositions[unitId].contains({row, col})) {
fprintf(stderr,
" Missing from guessed: unit=%d pos=(%d,%d)",
unitId,
row,
col);
std::cerr << " Missing from guessed: unit=" << unitId << " pos=(" << row << ","
<< col << ")";
const Coords missingCoords(row, col);
const auto *occupant = guessedState.GetOccupant(missingCoords);
if (occupant) {
fprintf(stderr,
" -> guessed occupant: unit_id=%d player=%d status=%d "
"loc=(%d,%d) hidden=%d battalion_size=%d\n",
occupant->unit_id(),
occupant->player_id(),
static_cast<int>(occupant->status()),
occupant->location().row(),
occupant->location().column(),
occupant->hidden(),
occupant->battalion().size());
std::cerr << " -> guessed occupant: unit_id=" << occupant->unit_id()
<< " player=" << occupant->player_id()
<< " status=" << static_cast<int>(occupant->status()) << " loc=("
<< occupant->location().row() << ","
<< occupant->location().column()
<< ") hidden=" << occupant->hidden()
<< " battalion_size=" << occupant->battalion().size() << "\n";
} else {
fprintf(stderr, " -> no occupant in guessed state\n");
std::cerr << " -> no occupant in guessed state\n";
}
}
}
}
// Dump all guessed state units for full picture
fprintf(stderr, " Guessed state units (%u total):\n", guessedState->units()->size());
std::cerr << " Guessed state units (" << guessedState->units()->size() << " total):\n";
for (size_t i = 0; i < guessedState->units()->size(); i++) {
const auto *u = guessedState->units()->Get(static_cast<unsigned int>(i));
fprintf(stderr,
" unit_id=%d player=%d status=%d loc=(%d,%d) hidden=%d "
"starting_pos_idx=%d\n",
u->unit_id(),
u->player_id(),
static_cast<int>(u->status()),
u->location().row(),
u->location().column(),
u->hidden(),
u->starting_position_index());
std::cerr << " unit_id=" << u->unit_id() << " player=" << u->player_id()
<< " status=" << static_cast<int>(u->status()) << " loc=("
<< u->location().row() << "," << u->location().column()
<< ") hidden=" << u->hidden()
<< " starting_pos_idx=" << u->starting_position_index() << "\n";
}
throw ShardokInternalErrorException(
@@ -357,9 +336,9 @@ auto ShardokAIClient::StandardChooseCommandIndex(
// Write the flatbuffer game state to file using SaveTo method
if (guessedState.SaveTo(statePath)) {
printf("Game state dumped to: %s\n", statePath.c_str());
std::cout << "Game state dumped to: " << statePath << '\n';
} else {
printf("Failed to dump game state to: %s\n", statePath.c_str());
std::cout << "Failed to dump game state to: " << statePath << '\n';
}
#endif // ENABLE_MCTS_DEBUG_DUMP
@@ -397,19 +376,17 @@ auto ShardokAIClient::StandardChooseCommandIndex(
if constexpr (kPerformanceLogging) {
if (result.commandCountEvaluated < result.availableCommandCount) {
printf("ID AI: Depth %d - evaluated %lu/%zu commands\n",
result.depthAchieved,
result.commandCountEvaluated,
result.availableCommandCount);
std::cout << "ID AI: Depth " << result.depthAchieved << " - evaluated "
<< result.commandCountEvaluated << '/' << result.availableCommandCount
<< " commands\n";
}
const auto chosenCommandType =
(*realAvailableCommands)[result.chosenIndex]->GetCommandType();
printf("ID AI: Search complete - achieved depth %d for best command %zu (%s)\n",
result.depthAchieved,
result.chosenIndex,
net::eagle0::shardok::common::CommandType_Name(chosenCommandType).c_str());
std::cout << "ID AI: Search complete - achieved depth " << result.depthAchieved
<< " for best command " << result.chosenIndex << " ("
<< net::eagle0::shardok::common::CommandType_Name(chosenCommandType) << ")\n";
fflush(stdout);
std::cout << std::flush;
}
return result;
@@ -468,12 +445,7 @@ auto ShardokAIClient::FinalRoundAttackerChooseCommandIndex(
maxRounds,
minimumFleeOddsThreshold,
desperateFleeThreshold,
#ifdef DEBUG_FLEE_DECISIONS
true // Enable debug logging
#else
false
#endif
);
kDebugFleeDecisions);
if (fleeDecision.shouldFlee) {
CommandChoiceResults results{};
@@ -516,7 +488,7 @@ auto ShardokAIClient::ChooseCommandIndex(
totalChoices++;
if (totalChoices % 1000 == 0) {
printf("TYPES CHOSEN:\n");
std::cout << "TYPES CHOSEN:\n";
vector<std::pair<int, CommandType>> choices{};
for (int i = 0; i <= net::eagle0::shardok::common::CommandType_MAX; i++) {
if (typeChosenCount[i] != 0) {
@@ -527,9 +499,9 @@ auto ShardokAIClient::ChooseCommandIndex(
std::ranges::sort(choices);
std::ranges::reverse(choices);
for (const auto &[index, choice] : choices) {
printf("%5d %s\n", index, CommandType_Name(choice).c_str());
std::cout << std::setw(5) << index << ' ' << CommandType_Name(choice) << '\n';
}
printf("\n\n");
std::cout << "\n\n";
}
return results;
@@ -539,7 +511,7 @@ auto ShardokAIClient::ChooseCommandIndex(const ShardokEngine &engine) const
-> CommandChoiceResults {
if (const auto &availableCommands = engine.GetAvailableCommandsForAIPlayer(playerId);
availableCommands->empty()) {
printf("no commands for player %d\n", playerId);
std::cout << "no commands for player " << playerId << '\n';
throw ShardokInternalErrorException(
"Asked to choose a command, but there are none available");
} else {
@@ -6,8 +6,8 @@
// Copyright © 2017 none. All rights reserved.
//
#ifndef ShardokAIClient_hpp
#define ShardokAIClient_hpp
#ifndef EAGLE0_SHARDOK_AI_SHARDOK_AI_CLIENT_HPP
#define EAGLE0_SHARDOK_AI_SHARDOK_AI_CLIENT_HPP
#include <vector>
@@ -95,4 +95,4 @@ public:
};
} // namespace shardok
#endif /* ShardokAIClient_hpp */
#endif // EAGLE0_SHARDOK_AI_SHARDOK_AI_CLIENT_HPP
@@ -4,9 +4,9 @@
#include "TranspositionTable.hpp"
#include <cinttypes>
#include <cstdio>
#include <cstring>
#include <iomanip>
#include <iostream>
namespace shardok {
@@ -18,7 +18,7 @@ TranspositionTable::TranspositionTable() : table(TABLE_SIZE) {
clear();
}
uint64_t TranspositionTable::hashGameState(const GameStateW& state) const {
auto TranspositionTable::hashGameState(const GameStateW& state) const -> uint64_t {
// The FlatBuffer is contiguous in memory and units are sorted by ID,
// so we can just hash the raw bytes for order-independent hashing
// Use ComputeFNV1aHash to avoid creating a string copy
@@ -100,15 +100,23 @@ void TranspositionTable::clear() {
current_age = 0;
}
void TranspositionTable::printStats() const {
printf("TranspositionTable Stats:\n");
printf(" Probes: %" PRIu64 "\n", stats.probes.load());
printf(" Hits: %" PRIu64 " (%.1f%%)\n", stats.hits.load(), stats.hitRate());
printf(" Stores: %" PRIu64 "\n", stats.stores.load());
printf(" Collisions: %" PRIu64 "\n", stats.collisions.load());
printf(" Table size: %zu entries (%.1f MB)\n",
TABLE_SIZE,
(TABLE_SIZE * sizeof(TTEntry)) / (1024.0 * 1024.0));
void TranspositionTable::printStats() const { printStats(std::cout); }
void TranspositionTable::printStats(std::ostream& output) const {
const auto flags = output.flags();
const auto precision = output.precision();
output << "TranspositionTable Stats:\n"
<< " Probes: " << stats.probes.load() << '\n'
<< " Hits: " << stats.hits.load() << " (" << std::fixed << std::setprecision(1)
<< stats.hitRate() << "%)\n"
<< " Stores: " << stats.stores.load() << '\n'
<< " Collisions: " << stats.collisions.load() << '\n'
<< " Table size: " << TABLE_SIZE << " entries ("
<< (TABLE_SIZE * sizeof(TTEntry)) / (1024.0 * 1024.0) << " MB)\n";
output.flags(flags);
output.precision(precision);
}
} // namespace shardok
} // namespace shardok
@@ -2,12 +2,13 @@
// TranspositionTable.hpp - Cache for game state evaluations to avoid redundant calculations
//
#ifndef EAGLE0_TRANSPOSITIONTABLE_HPP
#define EAGLE0_TRANSPOSITIONTABLE_HPP
#ifndef EAGLE0_SHARDOK_AI_TRANSPOSITION_TABLE_HPP
#define EAGLE0_SHARDOK_AI_TRANSPOSITION_TABLE_HPP
#include <atomic>
#include <cstdint>
#include <optional>
#include <ostream>
#include <vector>
#include "src/main/cpp/net/eagle0/shardok/library/GameStateW.hpp"
@@ -27,8 +28,8 @@ public:
std::atomic<uint64_t> stores{0};
std::atomic<uint64_t> collisions{0};
double hitRate() const {
uint64_t p = probes.load();
[[nodiscard]] auto hitRate() const -> double {
const uint64_t p = probes.load();
return p > 0 ? (100.0 * hits.load() / p) : 0.0;
}
@@ -59,7 +60,7 @@ private:
std::atomic<uint16_t> current_age{0};
// Hash function for FlatBuffer game state
uint64_t hashGameState(const GameStateW& state) const;
[[nodiscard]] auto hashGameState(const GameStateW& state) const -> uint64_t;
public:
TranspositionTable();
@@ -77,10 +78,11 @@ public:
void incrementAge() { current_age++; }
// Get statistics
const Stats& getStats() const { return stats; }
[[nodiscard]] auto getStats() const -> const Stats& { return stats; }
// Print statistics to stdout
void printStats() const;
void printStats(std::ostream& output) const;
};
// Global instance for the AI to use
@@ -88,4 +90,4 @@ extern TranspositionTable g_transpositionTable;
} // namespace shardok
#endif // EAGLE0_TRANSPOSITIONTABLE_HPP
#endif // EAGLE0_SHARDOK_AI_TRANSPOSITION_TABLE_HPP
@@ -2,8 +2,8 @@
// Shardok-specific MCTS AI that wraps the abstract implementation
//
#ifndef EAGLE0_SHARDOK_MCTSAI_HPP
#define EAGLE0_SHARDOK_MCTSAI_HPP
#ifndef EAGLE0_SHARDOK_AI_MCTS_SHARDOK_MCTSAI_HPP
#define EAGLE0_SHARDOK_AI_MCTS_SHARDOK_MCTSAI_HPP
#include <memory>
#include <vector>
@@ -68,4 +68,4 @@ private:
} // namespace shardok
#endif // EAGLE0_SHARDOK_MCTSAI_HPP
#endif // EAGLE0_SHARDOK_AI_MCTS_SHARDOK_MCTSAI_HPP
@@ -2,8 +2,8 @@
// Shardok-specific action adapter for MCTS
//
#ifndef EAGLE0_SHARDOK_ACTION_HPP
#define EAGLE0_SHARDOK_ACTION_HPP
#ifndef EAGLE0_SHARDOK_AI_MCTS_ADAPTERS_SHARDOK_ACTION_HPP
#define EAGLE0_SHARDOK_AI_MCTS_ADAPTERS_SHARDOK_ACTION_HPP
#include <memory>
#include <string>
@@ -58,4 +58,4 @@ private:
} // namespace shardok::mcts
#endif // EAGLE0_SHARDOK_ACTION_HPP
#endif // EAGLE0_SHARDOK_AI_MCTS_ADAPTERS_SHARDOK_ACTION_HPP
@@ -6,6 +6,8 @@
#include <algorithm>
#include <chrono>
#include <iomanip>
#include <iostream>
#include <numeric>
#include "ShardokAction.hpp"
@@ -35,6 +37,23 @@ auto LegalActionsCacheKey(const uint64_t stateHash, const bool rootIsDefender) -
return rootIsDefender ? stateHash ^ kDefenderRoleHash : stateHash;
}
auto SortActionsByWeight(
std::vector<std::unique_ptr<MCTSAction>> actions,
const std::vector<double>& weights) -> std::vector<std::unique_ptr<MCTSAction>> {
std::vector<size_t> sortedIndices(actions.size());
std::iota(sortedIndices.begin(), sortedIndices.end(), 0);
std::sort(sortedIndices.begin(), sortedIndices.end(), [&weights](size_t a, size_t b) {
return weights[a] > weights[b];
});
std::vector<std::unique_ptr<MCTSAction>> sortedActions;
sortedActions.reserve(actions.size());
for (size_t idx : sortedIndices) { sortedActions.push_back(std::move(actions[idx])); }
return sortedActions;
}
} // namespace
// Shared cache for legal actions (uses lock-free parallel hash map for thread safety)
@@ -280,19 +299,7 @@ std::vector<std::unique_ptr<MCTSAction>> ShardokGameEngine::getLegalActions(
// Sort actions by weight (descending) to ensure MCTS explores high-value actions first
const std::vector<double> weights = getActionWeights(actions, state);
std::vector<size_t> sortedIndices(actions.size());
std::iota(sortedIndices.begin(), sortedIndices.end(), 0);
std::sort(sortedIndices.begin(), sortedIndices.end(), [&weights](size_t a, size_t b) {
return weights[a] > weights[b];
});
std::vector<std::unique_ptr<MCTSAction>> sortedActions;
sortedActions.reserve(actions.size());
for (size_t idx : sortedIndices) { sortedActions.push_back(std::move(actions[idx])); }
return sortedActions;
return SortActionsByWeight(std::move(actions), weights);
}
cacheMisses_.fetch_add(1, std::memory_order_relaxed);
@@ -353,20 +360,7 @@ std::vector<std::unique_ptr<MCTSAction>> ShardokGameEngine::getLegalActions(
// get explored deeply. Original indices are preserved in ShardokAction::getIndex()
const std::vector<double> weights = getActionWeights(actions, state);
// Create index vector for sorting
std::vector<size_t> sortedIndices(actions.size());
std::iota(sortedIndices.begin(), sortedIndices.end(), 0);
// Sort indices by weight (descending)
std::sort(sortedIndices.begin(), sortedIndices.end(), [&weights](size_t a, size_t b) {
return weights[a] > weights[b];
});
// Reorder actions according to sorted indices
std::vector<std::unique_ptr<MCTSAction>> sortedActions;
sortedActions.reserve(actions.size());
for (size_t idx : sortedIndices) { sortedActions.push_back(std::move(actions[idx])); }
actions = std::move(sortedActions);
actions = SortActionsByWeight(std::move(actions), weights);
const auto actionsEnd = std::chrono::high_resolution_clock::now();
timeInLegalActionsComputation_.fetch_add(
@@ -509,7 +503,9 @@ size_t ShardokGameEngine::mapFilteredIndexToOriginal(
return shardokAction->getIndex();
}
void ShardokGameEngine::reportCacheStatistics() const {
void ShardokGameEngine::reportCacheStatistics() const { reportCacheStatistics(std::cout); }
void ShardokGameEngine::reportCacheStatistics(std::ostream& output) const {
const uint64_t hits = cacheHits_.load(std::memory_order_relaxed);
const uint64_t misses = cacheMisses_.load(std::memory_order_relaxed);
const uint64_t hashTime = timeInHashComputation_.load(std::memory_order_relaxed);
@@ -523,27 +519,29 @@ void ShardokGameEngine::reportCacheStatistics() const {
const double avgActionsTimeUs =
misses > 0 ? static_cast<double>(actionsTime) / static_cast<double>(misses) : 0.0;
printf("Legal Actions Cache Stats:\n");
printf(" Lookups: %llu hits, %llu misses, %.1f%% hit rate, %zu entries\n",
static_cast<unsigned long long>(hits),
static_cast<unsigned long long>(misses),
hitRate * 100.0,
legalActionsCache_.size());
printf(" Timing: %.2f us avg hash, %.2f us avg actions (on miss)\n",
avgHashTimeUs,
avgActionsTimeUs);
printf(" Total time: %.2f ms in hash, %.2f ms in actions\n",
hashTime / 1000.0,
actionsTime / 1000.0);
// Calculate if transposition table is worth it
const double timeWithCache = hashTime + actionsTime;
const double timeWithoutCache =
avgActionsTimeUs * static_cast<double>(totalLookups); // All lookups recompute
const double savings = (timeWithoutCache - timeWithCache) / timeWithoutCache * 100.0;
printf(" Cache savings: %.1f%% vs. no cache (%.2f ms saved)\n",
savings,
(timeWithoutCache - timeWithCache) / 1000.0);
const auto flags = output.flags();
const auto precision = output.precision();
output << "Legal Actions Cache Stats:\n"
<< " Lookups: " << hits << " hits, " << misses << " misses, " << std::fixed
<< std::setprecision(1) << hitRate * 100.0 << "% hit rate, "
<< legalActionsCache_.size() << " entries\n"
<< " Timing: " << std::setprecision(2) << avgHashTimeUs << " us avg hash, "
<< avgActionsTimeUs << " us avg actions (on miss)\n"
<< " Total time: " << hashTime / 1000.0 << " ms in hash, " << actionsTime / 1000.0
<< " ms in actions\n"
<< " Cache savings: " << std::setprecision(1) << savings << "% vs. no cache ("
<< std::setprecision(2) << (timeWithoutCache - timeWithCache) / 1000.0
<< " ms saved)\n";
output.flags(flags);
output.precision(precision);
}
}
@@ -2,13 +2,14 @@
// Shardok-specific game engine adapter for MCTS
//
#ifndef EAGLE0_SHARDOK_GAME_ENGINE_HPP
#define EAGLE0_SHARDOK_GAME_ENGINE_HPP
#ifndef EAGLE0_SHARDOK_AI_MCTS_ADAPTERS_SHARDOK_GAME_ENGINE_HPP
#define EAGLE0_SHARDOK_AI_MCTS_ADAPTERS_SHARDOK_GAME_ENGINE_HPP
#include <atomic>
#include <functional>
#include <gtl/phmap.hpp>
#include <memory>
#include <ostream>
#include <vector>
#include "src/main/cpp/net/eagle0/common/mcts/abstract/MCTSGameEngine.hpp"
@@ -92,6 +93,7 @@ public:
// Report transposition table statistics
void reportCacheStatistics() const;
void reportCacheStatistics(std::ostream& output) const;
// Reset cache statistics
void resetCacheStatistics();
@@ -141,4 +143,4 @@ public:
} // namespace mcts
} // namespace shardok
#endif // EAGLE0_SHARDOK_GAME_ENGINE_HPP
#endif // EAGLE0_SHARDOK_AI_MCTS_ADAPTERS_SHARDOK_GAME_ENGINE_HPP
@@ -2,8 +2,8 @@
// Shardok-specific game state adapter for MCTS
//
#ifndef EAGLE0_SHARDOK_GAME_STATE_HPP
#define EAGLE0_SHARDOK_GAME_STATE_HPP
#ifndef EAGLE0_SHARDOK_AI_MCTS_ADAPTERS_SHARDOK_GAME_STATE_HPP
#define EAGLE0_SHARDOK_AI_MCTS_ADAPTERS_SHARDOK_GAME_STATE_HPP
#include <memory>
@@ -80,4 +80,4 @@ private:
} // namespace mcts
} // namespace shardok
#endif // EAGLE0_SHARDOK_GAME_STATE_HPP
#endif // EAGLE0_SHARDOK_AI_MCTS_ADAPTERS_SHARDOK_GAME_STATE_HPP
@@ -2,8 +2,8 @@
// Factory for creating Shardok-specific MCTS components
//
#ifndef EAGLE0_SHARDOK_MCTS_FACTORY_HPP
#define EAGLE0_SHARDOK_MCTS_FACTORY_HPP
#ifndef EAGLE0_SHARDOK_AI_MCTS_ADAPTERS_SHARDOK_MCTS_FACTORY_HPP
#define EAGLE0_SHARDOK_AI_MCTS_ADAPTERS_SHARDOK_MCTS_FACTORY_HPP
#include <functional>
#include <memory>
@@ -67,4 +67,4 @@ public:
} // namespace mcts
} // namespace shardok
#endif // EAGLE0_SHARDOK_MCTS_FACTORY_HPP
#endif // EAGLE0_SHARDOK_AI_MCTS_ADAPTERS_SHARDOK_MCTS_FACTORY_HPP
@@ -2,10 +2,8 @@
// Created by dancrosby on 3/4/20.
//
#ifndef EAGLE0_AISCORECALCULATOR_HPP
#define EAGLE0_AISCORECALCULATOR_HPP
#include <future>
#ifndef EAGLE0_SHARDOK_AI_SCORE_AI_SCORE_CALCULATOR_HPP
#define EAGLE0_SHARDOK_AI_SCORE_AI_SCORE_CALCULATOR_HPP
#include "src/main/cpp/net/eagle0/shardok/ai/AIAttackLocations.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/GameStateW.hpp"
@@ -16,8 +14,6 @@
namespace shardok {
using shardok::PlayerId;
using std::future;
using std::vector;
using ScoreValue = double;
@@ -53,4 +49,4 @@ public:
} // namespace shardok
#endif // EAGLE0_AISCORECALCULATOR_HPP
#endif // EAGLE0_SHARDOK_AI_SCORE_AI_SCORE_CALCULATOR_HPP
@@ -160,6 +160,8 @@ auto AttackerHoldsCriticalTilesVictoryScore(
ActionPoints braveWaterCost) -> ScoreValue {
vector<const Unit*> playerUnits{};
vector<const Unit*> claimablePlayerUnits{};
playerUnits.reserve(gameState->units()->size());
claimablePlayerUnits.reserve(gameState->units()->size());
for (const Unit* unit : *gameState->units()) {
if (unit->status() == net::eagle0::shardok::storage::fb::UnitStatus_NORMAL_UNIT &&
unit->player_id() == player->player_id()) {
@@ -260,6 +262,8 @@ auto LastPlayerStandingVictoryScore(
vector<const Unit*> playerUnits{};
vector<const Unit*> otherUnits{};
playerUnits.reserve(gameState->units()->size());
otherUnits.reserve(gameState->units()->size());
for (const Unit* unit : *gameState->units()) {
if (unit->status() != net::eagle0::shardok::storage::fb::UnitStatus_NORMAL_UNIT) continue;
@@ -2,8 +2,8 @@
// Created by Dan Crosby on 4/11/21.
//
#ifndef EAGLE0_AIVICTORYCONDITIONSCORECALCULATOR_HPP
#define EAGLE0_AIVICTORYCONDITIONSCORECALCULATOR_HPP
#ifndef EAGLE0_SHARDOK_AI_SCORE_AI_VICTORY_CONDITION_SCORE_CALCULATOR_HPP
#define EAGLE0_SHARDOK_AI_SCORE_AI_VICTORY_CONDITION_SCORE_CALCULATOR_HPP
#include <vector>
@@ -20,7 +20,6 @@ namespace shardok {
using GameState = net::eagle0::shardok::storage::fb::GameState;
using net::eagle0::shardok::storage::fb::PlayerInfo;
using std::vector;
using ScoreValue = double;
@@ -48,4 +47,4 @@ auto LastPlayerStandingVictoryScore(
} // namespace shardok
#endif // EAGLE0_AIVICTORYCONDITIONSCORECALCULATOR_HPP
#endif // EAGLE0_SHARDOK_AI_SCORE_AI_VICTORY_CONDITION_SCORE_CALCULATOR_HPP
@@ -3,8 +3,8 @@
// Uses bounded linear scoring tuned for MCTS exploration/exploitation balance
//
#ifndef EAGLE0_MCTSOPTIMIZEDAISCORECALCULATOR_HPP
#define EAGLE0_MCTSOPTIMIZEDAISCORECALCULATOR_HPP
#ifndef EAGLE0_SHARDOK_AI_SCORE_MCTS_OPTIMIZED_AI_SCORE_CALCULATOR_HPP
#define EAGLE0_SHARDOK_AI_SCORE_MCTS_OPTIMIZED_AI_SCORE_CALCULATOR_HPP
#include <memory>
@@ -29,4 +29,4 @@ using ALCache = std::unique_ptr<AttackLocationsCache>;
} // namespace shardok
#endif // EAGLE0_MCTSOPTIMIZEDAISCORECALCULATOR_HPP
#endif // EAGLE0_SHARDOK_AI_SCORE_MCTS_OPTIMIZED_AI_SCORE_CALCULATOR_HPP
@@ -8,6 +8,9 @@
#include <atomic>
#include <chrono>
#include <cmath>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <unordered_map>
#include "private/AIScoreCalculatorSharedUtilities.hpp"
@@ -41,9 +44,6 @@ class StandardAIScoreCalculator;
// Anonymous namespace for helper functions that don't need access to scorer
namespace {
#define LOGGING_ 0
#define PERFORMANCE_LOGGING_ 0
// Performance logging for AttackerScoreForState
struct AttackerScorePerformanceLogger {
static constexpr int LOG_INTERVAL = 100000;
@@ -60,11 +60,12 @@ struct AttackerScorePerformanceLogger {
if (callCount.load() % LOG_INTERVAL == 0) {
double intervalAvg = intervalTime.load() / LOG_INTERVAL;
double overallAvg = totalTime.load() / callCount.load();
printf("AttackerScoreForState: %d calls, last %d avg: %.1f µs, overall avg: %.1f µs\n",
callCount.load(),
LOG_INTERVAL,
intervalAvg * 1000000.0,
overallAvg * 1000000.0);
std::ostringstream message;
message << std::fixed << std::setprecision(1)
<< "AttackerScoreForState: " << callCount.load() << " calls, last "
<< LOG_INTERVAL << " avg: " << intervalAvg * 1000000.0
<< " µs, overall avg: " << overallAvg * 1000000.0 << " µs\n";
std::cout << message.str();
intervalTime.store(0.0); // Reset for next interval
}
}
@@ -143,6 +144,10 @@ private:
public:
AttackerScoreTimer() : startTime(std::chrono::high_resolution_clock::now()) {}
AttackerScoreTimer(const AttackerScoreTimer &) = delete;
auto operator=(const AttackerScoreTimer &) -> AttackerScoreTimer & = delete;
AttackerScoreTimer(AttackerScoreTimer &&) = delete;
auto operator=(AttackerScoreTimer &&) -> AttackerScoreTimer & = delete;
~AttackerScoreTimer() {
auto endTime = std::chrono::high_resolution_clock::now();
@@ -2,8 +2,8 @@
// Standard implementation of AIScoreCalculator
//
#ifndef EAGLE0_STANDARDAISCORECALCULATOR_HPP
#define EAGLE0_STANDARDAISCORECALCULATOR_HPP
#ifndef EAGLE0_SHARDOK_AI_SCORE_STANDARD_AI_SCORE_CALCULATOR_HPP
#define EAGLE0_SHARDOK_AI_SCORE_STANDARD_AI_SCORE_CALCULATOR_HPP
#include <memory>
@@ -34,4 +34,4 @@ using ALCache = std::unique_ptr<AttackLocationsCache>;
} // namespace shardok
#endif // EAGLE0_STANDARDAISCORECALCULATOR_HPP
#endif // EAGLE0_SHARDOK_AI_SCORE_STANDARD_AI_SCORE_CALCULATOR_HPP
@@ -3,8 +3,8 @@
// This file is private to the ai/score package
//
#ifndef EAGLE0_AI_SCORE_CALCULATOR_SHARED_UTILITIES_HPP
#define EAGLE0_AI_SCORE_CALCULATOR_SHARED_UTILITIES_HPP
#ifndef EAGLE0_SHARDOK_AI_SCORE_PRIVATE_AI_SCORE_CALCULATOR_SHARED_UTILITIES_HPP
#define EAGLE0_SHARDOK_AI_SCORE_PRIVATE_AI_SCORE_CALCULATOR_SHARED_UTILITIES_HPP
#include <vector>
@@ -92,4 +92,4 @@ auto AttackerMultiplierForTargetDistance(
} // namespace score_calculator_internal
} // namespace shardok
#endif // EAGLE0_AI_SCORE_CALCULATOR_SHARED_UTILITIES_HPP
#endif // EAGLE0_SHARDOK_AI_SCORE_PRIVATE_AI_SCORE_CALCULATOR_SHARED_UTILITIES_HPP
@@ -47,19 +47,19 @@ auto AbstractAIScoreCalculator::CalculateUnitsScoreComponents(
// Memoization cache for EffectiveDistance calls
EffectiveDistanceCache distanceCache;
// Early return for empty game states
const size_t estimatedUnitCount = cachedUnits->size();
if (estimatedUnitCount == 0) { return UnitsScoreComponents{0.0, 0.0}; }
std::vector<const Unit *> attackerUnits{};
std::vector<const Unit *> defenderUnits{};
// Pre-allocate vectors based on estimated unit ratios to avoid reallocations
const size_t estimatedUnitCount = cachedUnits->size();
attackerUnits.reserve(estimatedUnitCount - 1);
defenderUnits.reserve(estimatedUnitCount - 1);
attackerUnits.reserve(estimatedUnitCount);
defenderUnits.reserve(estimatedUnitCount);
double attackerUnitsValue = 0;
double defenderUnitsValue = 0;
// Early return for empty game states
if (cachedUnits->size() == 0) { return UnitsScoreComponents{0.0, 0.0}; }
auto occupants = Occupants(*cachedUnits, cachedRowCount, cachedColumnCount);
for (const Unit *unit : *cachedUnits) {
@@ -3,8 +3,8 @@
// This file is private to the ai/score package
//
#ifndef EAGLE0_ABSTRACT_AI_SCORE_CALCULATOR_HPP
#define EAGLE0_ABSTRACT_AI_SCORE_CALCULATOR_HPP
#ifndef EAGLE0_SHARDOK_AI_SCORE_PRIVATE_ABSTRACT_AI_SCORE_CALCULATOR_HPP
#define EAGLE0_SHARDOK_AI_SCORE_PRIVATE_ABSTRACT_AI_SCORE_CALCULATOR_HPP
#include <memory>
#include <unordered_map>
@@ -178,4 +178,4 @@ private:
} // namespace shardok
#endif // EAGLE0_ABSTRACT_AI_SCORE_CALCULATOR_HPP
#endif // EAGLE0_SHARDOK_AI_SCORE_PRIVATE_ABSTRACT_AI_SCORE_CALCULATOR_HPP
@@ -21,7 +21,7 @@ std::unique_ptr<BattleConfigProto> AiBattleConfigLoader::LoadFromJsonFile(
const std::string& filepath) {
std::ifstream file(filepath);
if (!file.is_open()) {
std::cerr << "Failed to open config file: " << filepath << std::endl;
std::cerr << "Failed to open config file: " << filepath << '\n';
return nullptr;
}
@@ -41,7 +41,7 @@ std::unique_ptr<BattleConfigProto> AiBattleConfigLoader::LoadFromJsonString(
google::protobuf::util::JsonStringToMessage(jsonString, config.get(), options);
if (!status.ok()) {
std::cerr << "Failed to parse JSON config: " << status.message() << std::endl;
std::cerr << "Failed to parse JSON config: " << status.message() << '\n';
return nullptr;
}
@@ -103,7 +103,7 @@ std::string AiBattleConfigLoader::ToJsonString(const BattleConfigProto& config)
const auto status = google::protobuf::util::MessageToJsonString(config, &jsonString, options);
if (!status.ok()) {
std::cerr << "Failed to convert config to JSON: " << status.message() << std::endl;
std::cerr << "Failed to convert config to JSON: " << status.message() << '\n';
return "";
}
@@ -2,8 +2,8 @@
// AI Battle Simulator Configuration Loader
//
#ifndef EAGLE0_AI_BATTLE_CONFIG_HPP
#define EAGLE0_AI_BATTLE_CONFIG_HPP
#ifndef EAGLE0_SHARDOK_AI_BATTLE_SIMULATOR_AI_BATTLE_CONFIG_HPP
#define EAGLE0_SHARDOK_AI_BATTLE_SIMULATOR_AI_BATTLE_CONFIG_HPP
#include <memory>
#include <string>
@@ -44,4 +44,4 @@ public:
} // namespace ai_battle_simulator
} // namespace shardok
#endif // EAGLE0_AI_BATTLE_CONFIG_HPP
#endif // EAGLE0_SHARDOK_AI_BATTLE_SIMULATOR_AI_BATTLE_CONFIG_HPP
@@ -8,6 +8,7 @@
#include <iostream>
#include <sstream>
#include <stdexcept>
#include <utility>
#include "src/main/cpp/net/eagle0/common/FilesystemUtils.hpp"
#include "src/main/cpp/net/eagle0/common/RandomGenerator.hpp"
@@ -332,6 +333,7 @@ GameStateW AiBattleSimulator::CreateInitialGameState() const {
// Create player info protos
std::vector<net::eagle0::shardok::common::PlayerInfo> playerInfoProtos;
playerInfoProtos.reserve(2);
// Attacker info
net::eagle0::shardok::common::PlayerInfo attackerInfo;
@@ -342,7 +344,7 @@ GameStateW AiBattleSimulator::CreateInitialGameState() const {
net::eagle0::shardok::common::VICTORY_CONDITION_LAST_PLAYER_STANDING);
attackerInfo.add_victory_conditions(
net::eagle0::shardok::common::VICTORY_CONDITION_HOLDS_CRITICAL_TILES);
playerInfoProtos.push_back(attackerInfo);
playerInfoProtos.push_back(std::move(attackerInfo));
// Defender info
net::eagle0::shardok::common::PlayerInfo defenderInfo;
@@ -353,13 +355,15 @@ GameStateW AiBattleSimulator::CreateInitialGameState() const {
net::eagle0::shardok::common::VICTORY_CONDITION_LAST_PLAYER_STANDING);
defenderInfo.add_victory_conditions(
net::eagle0::shardok::common::VICTORY_CONDITION_WIN_AFTER_MAX_ROUNDS);
playerInfoProtos.push_back(defenderInfo);
playerInfoProtos.push_back(std::move(defenderInfo));
// Create units from config
std::vector<net::eagle0::shardok::storage::fb::Unit> units;
units.reserve(config_.attacker().units_size() + config_.defender().units_size());
// Attacker units
for (int i = 0; i < config_.attacker().units_size(); ++i) {
const int attackerUnitCount = config_.attacker().units_size();
for (int i = 0; i < attackerUnitCount; ++i) {
const auto& unitConfig = config_.attacker().units(i);
net::eagle0::shardok::storage::fb::Unit unit{};
@@ -449,12 +453,13 @@ GameStateW AiBattleSimulator::CreateInitialGameState() const {
unit.mutable_opponent_knowledge()->Mutate(0, 0);
unit.mutable_opponent_knowledge()->Mutate(1, 0);
units.push_back(unit);
units.push_back(std::move(unit));
}
// Defender units
int defenderUnitIdStart = config_.attacker().units_size();
for (int i = 0; i < config_.defender().units_size(); ++i) {
const int defenderUnitIdStart = attackerUnitCount;
const int defenderUnitCount = config_.defender().units_size();
for (int i = 0; i < defenderUnitCount; ++i) {
const auto& unitConfig = config_.defender().units(i);
net::eagle0::shardok::storage::fb::Unit unit{};
@@ -544,7 +549,7 @@ GameStateW AiBattleSimulator::CreateInitialGameState() const {
unit.mutable_opponent_knowledge()->Mutate(0, 0);
unit.mutable_opponent_knowledge()->Mutate(1, 0);
units.push_back(unit);
units.push_back(std::move(unit));
}
// Create game state
@@ -678,8 +683,9 @@ BattleResult AiBattleSimulator::RunConfiguredSetupPhase(ShardokEngine& engine) {
playerConfig,
int unitIdOffset) {
std::vector<UnitPlacementInfo> placements;
placements.reserve(playerConfig.units_size());
for (int i = 0; i < playerConfig.units_size(); ++i) {
const int unitCount = playerConfig.units_size();
placements.reserve(unitCount);
for (int i = 0; i < unitCount; ++i) {
const auto& unitConfig = playerConfig.units(i);
if (!unitConfig.has_fixed_position()) {
throw std::invalid_argument(
@@ -744,7 +750,8 @@ BattleResult AiBattleSimulator::RunBattlePhase(
auto availableCommands = engine.GetAvailableCommandProtos(currentPlayer, false);
if (availableCommands.empty()) {
std::cout << "No commands available for player " << (int)currentPlayer << "\n";
std::cout << "No commands available for player " << static_cast<int>(currentPlayer)
<< "\n";
break;
}
@@ -910,6 +917,7 @@ BattleResult AiBattleSimulator::CreateResultFromGameState(
// Collect surviving units (for winner, or all units if draw)
const auto* units = state->units();
if (units) {
result.survivingUnits.reserve(units->size());
for (const auto* unit : *units) {
if (!unit) { continue; }
@@ -2,8 +2,8 @@
// AI Battle Simulator - Runs AI vs AI battles to completion
//
#ifndef EAGLE0_AI_BATTLE_SIMULATOR_HPP
#define EAGLE0_AI_BATTLE_SIMULATOR_HPP
#ifndef EAGLE0_SHARDOK_AI_BATTLE_SIMULATOR_AI_BATTLE_SIMULATOR_HPP
#define EAGLE0_SHARDOK_AI_BATTLE_SIMULATOR_AI_BATTLE_SIMULATOR_HPP
#include <memory>
#include <optional>
@@ -149,4 +149,4 @@ private:
} // namespace ai_battle_simulator
} // namespace shardok
#endif // EAGLE0_AI_BATTLE_SIMULATOR_HPP
#endif // EAGLE0_SHARDOK_AI_BATTLE_SIMULATOR_AI_BATTLE_SIMULATOR_HPP
@@ -88,8 +88,7 @@ void GenerateConfigFile(const std::string& outputPath) {
// Output to file
std::ofstream outFile(outputPath);
if (!outFile.is_open()) {
std::cerr << "Error: Failed to open output file: " << outputPath << "\n";
std::exit(1);
throw std::runtime_error("Failed to open output file: " + outputPath);
}
outFile << jsonString << "\n";
std::cout << "Sample config written to: " << outputPath << "\n";
@@ -295,8 +294,7 @@ BattleResult RunBattle(
if (!config) {
restoreOutput();
std::cerr << "Error: Failed to load config file\n";
std::exit(1);
throw std::runtime_error("Failed to load config file");
}
// Create simulator
@@ -328,6 +326,8 @@ int main(int argc, char* argv[]) {
std::vector<std::string> configPaths;
std::vector<std::string> statePaths;
configPaths.reserve(argc - 1);
statePaths.reserve(argc - 1);
std::string outputPath;
std::string tracePath;
std::string runLabel;
@@ -2,7 +2,6 @@
// Converts saved Eagle ShardokBattle/GameState protobuf blobs into AI battle configs.
//
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <optional>
@@ -146,9 +145,9 @@ int main(int argc, char *argv[]) {
} else if (arg.starts_with("--output=")) {
outputPath = arg.substr(9);
} else if (arg.starts_with("--seed=")) {
seed = std::atoi(arg.substr(7).c_str());
seed = std::stoi(arg.substr(7));
} else if (arg.starts_with("--max-rounds=")) {
maxRounds = std::atoi(arg.substr(13).c_str());
maxRounds = std::stoi(arg.substr(13));
} else if (arg.starts_with("--attacker-scoring=")) {
const auto scoring = ParseScoring(arg.substr(19));
if (!scoring.has_value()) { throw std::runtime_error("Unknown attacker scoring"); }
@@ -115,6 +115,7 @@ int main(int argc, char *argv[]) {
std::string resultPath;
std::string stateFbPath;
std::vector<std::string> actionViewPaths;
actionViewPaths.reserve(argc - 1);
for (int i = 1; i < argc; ++i) {
const std::string arg = argv[i];
@@ -145,6 +146,9 @@ int main(int argc, char *argv[]) {
std::vector<int> attackerFactions;
std::vector<int> defenderFactions;
std::vector<std::string> playerSides;
attackerFactions.reserve(battle.players_size());
defenderFactions.reserve(battle.players_size());
playerSides.reserve(battle.players_size());
bool aiVsAi = true;
for (const auto &player : battle.players()) {
if (player.eagle_fid() == 3 || player.eagle_fid() == 4) { aiVsAi = false; }
@@ -4,9 +4,10 @@
#include "AIPerformanceRunner.hpp"
#include <cstdlib>
#include <iomanip>
#include <iostream>
#include <optional>
#include <stdexcept>
#include <string>
#include "PerformanceTestGameStateBuilder.hpp"
@@ -18,7 +19,15 @@
#include "src/main/cpp/net/eagle0/shardok/library/ShardokEngine.hpp"
#include "src/main/protobuf/net/eagle0/shardok/common/command_type.pb.h"
using namespace shardok;
using shardok::AIAlgorithmType;
using shardok::AIPerformanceMetrics;
using shardok::EvaluationCompletionReason;
using shardok::PerformanceTestConfig;
using shardok::PerformanceTestGameStateBuilder;
using shardok::PlayerId;
using shardok::ShardokAIClient;
using shardok::ShardokEngine;
namespace ai_testing_common = shardok::ai_testing_common;
namespace {
@@ -39,7 +48,7 @@ auto CompletionReasonToString(EvaluationCompletionReason reason) -> std::string
/**
* Parse command line arguments into a configuration struct.
*/
auto ParseCommandLineArgs(int argc, char* argv[]) -> PerformanceTestConfig {
auto ParseCommandLineArgs(int argc, char* argv[]) -> std::optional<PerformanceTestConfig> {
PerformanceTestConfig config;
for (int i = 1; i < argc; ++i) {
@@ -55,7 +64,7 @@ auto ParseCommandLineArgs(int argc, char* argv[]) -> PerformanceTestConfig {
<< " --defender=BOOL AI is defender (default: false)\n"
<< " --verbose Enable verbose output\n"
<< " --help, -h Show this help message\n";
std::exit(0);
return std::nullopt;
} else if (arg.starts_with("--map=")) {
config.mapName = arg.substr(6);
} else if (arg.starts_with("--turns=")) {
@@ -66,9 +75,8 @@ auto ParseCommandLineArgs(int argc, char* argv[]) -> PerformanceTestConfig {
} else if (arg == "--verbose") {
config.verbose = true;
} else {
std::cerr << "Unknown argument: " << arg << "\n";
std::cerr << "Use --help for usage information.\n";
std::exit(1);
throw std::invalid_argument(
"Unknown argument: " + arg + "\nUse --help for usage information.");
}
}
@@ -78,7 +86,7 @@ auto ParseCommandLineArgs(int argc, char* argv[]) -> PerformanceTestConfig {
} // namespace
int main(int argc, char* argv[]) {
std::cout << "Starting AI Performance Runner..." << std::endl;
std::cout << "Starting AI Performance Runner..." << '\n';
// Set exec path so FilesystemUtils can find resource files
FilesystemUtils::SetExecPath(argv[0]);
@@ -88,7 +96,9 @@ int main(int argc, char* argv[]) {
std::cout << "==============================\n";
// Parse command line arguments
auto config = ParseCommandLineArgs(argc, argv);
const auto parsedConfig = ParseCommandLineArgs(argc, argv);
if (!parsedConfig.has_value()) { return 0; }
const auto& config = parsedConfig.value();
if (config.verbose) {
std::cout << "Configuration:\n";
@@ -155,6 +165,7 @@ int main(int argc, char* argv[]) {
std::cout << "Running AI performance test for " << config.numTurns << " turns...\n";
std::vector<AIPerformanceMetrics> metrics;
if (config.numTurns > 0) { metrics.reserve(config.numTurns); }
for (int turn = 0; turn < config.numTurns; ++turn) {
// Check if AI can make a move
@@ -300,4 +311,4 @@ int main(int argc, char* argv[]) {
}
return 0;
}
}
@@ -2,8 +2,8 @@
// Created by Dan Crosby on 2025-01-15.
//
#ifndef EAGLE0_AIPERFORMANCERUNNER_HPP
#define EAGLE0_AIPERFORMANCERUNNER_HPP
#ifndef EAGLE0_SHARDOK_AI_PERFORMANCE_RUNNER_AI_PERFORMANCE_RUNNER_HPP
#define EAGLE0_SHARDOK_AI_PERFORMANCE_RUNNER_AI_PERFORMANCE_RUNNER_HPP
#include <chrono>
#include <map>
@@ -52,4 +52,4 @@ struct PerformanceTestConfig {
} // namespace shardok
#endif // EAGLE0_AIPERFORMANCERUNNER_HPP
#endif // EAGLE0_SHARDOK_AI_PERFORMANCE_RUNNER_AI_PERFORMANCE_RUNNER_HPP
@@ -4,7 +4,9 @@
#include "PerformanceTestGameStateBuilder.hpp"
#include <algorithm>
#include <filesystem>
#include <utility>
#include "src/main/cpp/net/eagle0/common/FilesystemUtils.hpp"
#include "src/main/cpp/net/eagle0/shardok/ai_testing_common/GameSettingsFactory.hpp"
@@ -53,6 +55,7 @@ auto PerformanceTestGameStateBuilder::CreateCustomTestGameState(
// Create player info protos
std::vector<net::eagle0::shardok::common::PlayerInfo> playerInfoProtos;
playerInfoProtos.reserve(2);
// AI player
net::eagle0::shardok::common::PlayerInfo aiPlayerInfo;
@@ -68,7 +71,7 @@ auto PerformanceTestGameStateBuilder::CreateCustomTestGameState(
aiPlayerInfo.add_victory_conditions(
net::eagle0::shardok::common::VICTORY_CONDITION_HOLDS_CRITICAL_TILES);
}
playerInfoProtos.push_back(aiPlayerInfo);
playerInfoProtos.push_back(std::move(aiPlayerInfo));
// Human player
net::eagle0::shardok::common::PlayerInfo humanPlayerInfo;
@@ -84,13 +87,16 @@ auto PerformanceTestGameStateBuilder::CreateCustomTestGameState(
humanPlayerInfo.add_victory_conditions(
net::eagle0::shardok::common::VICTORY_CONDITION_HOLDS_CRITICAL_TILES);
}
playerInfoProtos.push_back(humanPlayerInfo);
playerInfoProtos.push_back(std::move(humanPlayerInfo));
// Create units
const int clampedAiUnitCount = std::clamp(aiUnitCount, 0, 6);
const int clampedHumanUnitCount = std::clamp(humanUnitCount, 0, 6);
std::vector<net::eagle0::shardok::storage::fb::Unit> units;
units.reserve(clampedAiUnitCount + clampedHumanUnitCount);
// Create AI units in reserve (location -1, -1)
for (int i = 0; i < aiUnitCount && i < 6; ++i) {
for (int i = 0; i < clampedAiUnitCount; ++i) {
units.push_back(AddGenericUnit(
AI_PLAYER_ID,
i, // Unit ID
@@ -101,10 +107,10 @@ auto PerformanceTestGameStateBuilder::CreateCustomTestGameState(
}
// Create human units in reserve (location -1, -1)
for (int i = 0; i < humanUnitCount && i < 6; ++i) {
for (int i = 0; i < clampedHumanUnitCount; ++i) {
units.push_back(AddGenericUnit(
HUMAN_PLAYER_ID,
aiUnitCount + i, // Unit ID starting aiUnitCount
clampedAiUnitCount + i, // Unit ID starting after generated AI units
net::eagle0::shardok::storage::fb::Coords(-1, -1), // Reserve location
NO_PROFESSION,
HEAVY_INFANTRY_BATTALION_TYPE,
@@ -235,4 +241,4 @@ auto PerformanceTestGameStateBuilder::AddGenericUnit(
return unit;
}
} // namespace shardok
} // namespace shardok

Some files were not shown because too many files have changed in this diff Show More