Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
196 commits
Select commit Hold shift + click to select a range
d6a40f1
update scons usage
kylelutze Mar 26, 2026
728adc1
fix zlib usage bugs
kylelutze Mar 26, 2026
eeb6698
switch from boost threads to std threads
kylelutze Mar 26, 2026
61cf097
Replace boost::shared_ptr, lexical_cast, and tuple with C++11 equival…
kylelutze Mar 26, 2026
b68bac9
WIP: checksum sidecar for cross-replay debugging
kylelutze Apr 26, 2026
7a3c029
Add GLOB2_REPLAY_PATH env var to override replay output path
kylelutze Apr 26, 2026
8e12f02
Emit GLOB2_GAME_END summary line on automatic game end
kylelutze Apr 26, 2026
7490f81
Add --ai-types to constrain the AI pool in random test games
kylelutze Apr 26, 2026
b710cd6
ReplayWriter: bypass FileManager for absolute output paths
kylelutze Apr 26, 2026
24510b3
GLOB2_GAME_END: include seed, map name, and order count
kylelutze Apr 26, 2026
4a4a031
Add --map and --matchup for explicit headless game spec
kylelutze Apr 26, 2026
4b6bc20
.gitignore: ignore .DS_Store
kylelutze Apr 26, 2026
106e5c4
Game::save: don't permanently mutate the live mapHeader
kylelutze Apr 26, 2026
8bfc8d2
Add DatasetWriter for AI-trainer (state, action) records
kylelutze Apr 26, 2026
24b466e
DatasetWriter: drop format_version and flags from header
kylelutze Apr 26, 2026
c499881
DatasetWriter: populate state_blob with bot-team scalars + 32x32x7 grid
kylelutze Apr 26, 2026
07e8090
Map: split Map.cpp (5271 lines) into 17 focused TUs
kylelutze Apr 26, 2026
4e98560
GameGUI: split GameGUI.cpp (5366 lines) into 7 focused TUs
kylelutze Apr 26, 2026
adf6903
Game: split Game.cpp (3216 lines) into 6 focused TUs
kylelutze Apr 27, 2026
32bfd24
YOG: move src/YOG*.{cpp,h} into src/yog/ subdirectory
kylelutze Apr 27, 2026
4a694bc
AI: move src/AI*.{cpp,h} into src/AI/ subdirectory
kylelutze Apr 27, 2026
cc3efd6
AIEcho: split AIEcho.cpp (5931 lines) into 8 focused TUs
kylelutze Apr 27, 2026
a6758b0
AICastor: split AICastor.cpp (3250 lines) into 7 focused TUs
kylelutze Apr 27, 2026
3273ab5
Fix unguarded `get_height < dist` checks in farming AI
kylelutze Apr 27, 2026
3380b7b
AINicowar: split AINicowar.cpp (2756 lines) into 8 focused TUs
kylelutze Apr 27, 2026
46c94c5
Building: split Building.cpp (2896 lines) into 6 focused TUs
kylelutze Apr 27, 2026
8e4b9eb
AIWarrush: collapse repeated query loops, fix wrap typo
kylelutze Apr 27, 2026
8f69e07
AIEcho: extract Gradient::expand_bfs, add cppunit tests
kylelutze Apr 27, 2026
f06c8fa
Building: fix 4 pre-existing bugs and document clearingFlagStep timer
kylelutze Apr 27, 2026
3959ee0
.gitignore: ignore test runner binary and result XML
kylelutze Apr 27, 2026
46ce79a
Building: collapse wishedRessources / computeWishedRessources into one
kylelutze Apr 27, 2026
89f342e
Building: fix latent setBuilding(decLeft, decLeft) typo in tryToBuild…
kylelutze Apr 27, 2026
8d3d445
Building: drop tautological size>=0 asserts in integrity()
kylelutze Apr 28, 2026
7632721
Building: collapse add/removeForbiddenZoneForUpgradeArea duplicates
kylelutze Apr 28, 2026
451b8e0
Building: comment why subscribeForFlagingStep zeroes failure counts t…
kylelutze Apr 28, 2026
403e6e3
Unit: split Unit.cpp into per-concern files under src/Unit/
kylelutze Apr 28, 2026
194cb98
MapEdit: split MapEdit.cpp into per-concern files under src/map/edit/
kylelutze Apr 28, 2026
800075b
MapGenerator: split MapGenerator.cpp, drop dead code, fix 4 bugs
kylelutze Apr 28, 2026
90173b6
Map: organize map sources under src/map/ subdirectories
kylelutze Apr 28, 2026
deafc94
libwee: remove unused VM/GC sketch
kylelutze Apr 28, 2026
26b80aa
src: lowercase directory names; drop unmaintained VS project files
kylelutze Apr 28, 2026
e25d601
Building: move building sources under src/building/
kylelutze Apr 28, 2026
fa793a7
IRC: move IRC sources under src/net/irc/
kylelutze Apr 28, 2026
9790775
headers: replace include guards with #pragma once
kylelutze Apr 28, 2026
fdac516
Integrity: include <iostream> so the header is self-contained
kylelutze Apr 28, 2026
ee4d046
GameGUIInput: split 1954-line file into per-input-source files
kylelutze Apr 28, 2026
191e7eb
GameGUIInput: collapse repetitive switch cases, split building branch
kylelutze Apr 28, 2026
4bb17c2
NetMessage: split 4588-line file into per-purpose files under src/net…
kylelutze Apr 28, 2026
fd4eda0
Game_render: split 1398-line file into 5 TUs under src/render/
kylelutze Apr 29, 2026
3bb9abf
GraphicContext: split 2356-line file into per-purpose TUs
kylelutze Apr 29, 2026
4c6aaeb
Net: update SConstruct CPPPATH and SConscript paths for src/net/ move
kylelutze Apr 29, 2026
855a579
GUI/Unit: move sources under src/gui/ and src/unit/, update build paths
kylelutze Apr 29, 2026
8703d5c
build/render: switch to C++20 and dedupe render helpers
kylelutze Apr 29, 2026
8ac4eda
GameGUIDrawPanels: split 1207-line file into 5 TUs under src/gui/
kylelutze Apr 30, 2026
3870dd4
Team: split 1360-line Team.cpp into 5 TUs under src/team/
kylelutze May 1, 2026
a90543e
Engine: split 1118-line Engine.cpp into 4 TUs
kylelutze May 1, 2026
c10c582
UnitsSkins: drop dead lazy-load comment in getSkin()
kylelutze May 1, 2026
5ee9a56
Replace UnitsSkins file loader with a static per-type table
kylelutze May 1, 2026
5a04654
gitignore: ignore testResults.xml at any path
kylelutze May 4, 2026
9ba5f66
test: add deterministic replay-diff regression harness
kylelutze May 4, 2026
9211f37
Resources: replace file loader with a static per-type table
kylelutze May 4, 2026
94a974e
Buildings: replace file loader with a static per-type table
kylelutze May 5, 2026
79e7389
Units & Race: replace file loader with a static per-type table
kylelutze May 5, 2026
53e5256
EntityType: delete now-orphaned text-loader infrastructure
kylelutze May 5, 2026
8672838
Replace GPL boilerplate headers with SPDX identifiers
kylelutze May 5, 2026
aa72af7
Add --save-game-as flag and seed-consistency fix for saved games
kylelutze May 5, 2026
ebc2170
Order: split Order.cpp into per-category files
kylelutze May 5, 2026
d34abe4
libgag: let FileManager handle absolute paths directly
kylelutze May 5, 2026
35bd6ae
map: remove unused pathfinding stat counters and MapLog logger
kylelutze May 6, 2026
cc4aa4d
AIEcho: split monolithic header into per-namespace files under echo/
kylelutze May 6, 2026
dd17110
test: regenerate cpp-refactor.replay as full-game baseline
kylelutze May 6, 2026
2d42c34
test: add MapQueryTest characterization suite
kylelutze May 6, 2026
8c96ff6
MapQuery: collapse isFreeFor/isHardSpaceFor into one tile predicate
kylelutze May 6, 2026
01e6278
MapQuery: replace doesUnitTouch/doesPosTouch out-params with optional…
kylelutze May 6, 2026
d1d7ac2
MapQuery: extract pure scoring helpers from doesUnitTouchEnemy
kylelutze May 6, 2026
ccee62b
Revert "MapQuery: extract pure scoring helpers from doesUnitTouchEnemy"
kylelutze May 6, 2026
01dbea2
MapQuery: document doesUnitTouchEnemy tie-break rules
kylelutze May 6, 2026
612125b
team: remove dead logFile member and the fprintfs that fed it
kylelutze May 6, 2026
18a8377
unit: remove dead logFile member and the fprintfs that fed it
kylelutze May 6, 2026
7565378
building: remove dead logFile member and the fprintfs that fed it
kylelutze May 6, 2026
d77ce64
game: remove dead logFile member and the fprintfs that fed it
kylelutze May 6, 2026
cca806b
ai/castor: remove dead logFile member and the fprintfs that fed it
kylelutze May 6, 2026
6b374e5
unit/movement: drop dead verbose printfs and stale debug comments
kylelutze May 6, 2026
5ff3050
unit/movement: split fat handleMovement, dedupe attack targeting, opt…
kylelutze May 6, 2026
6f72bcc
net: split NetMessage.h into per-group headers, snake_case message/*.cpp
kylelutze May 6, 2026
0bfcfed
game_event: collapse 5-class hierarchy to single value-type class
kylelutze May 6, 2026
3d16cb0
MapPathfindArea: drop dead verbose printfs
kylelutze May 6, 2026
273d359
unit/action: extract helpers, name magic numbers, split handleAction …
kylelutze May 7, 2026
419eb4b
team: stable gid tiebreak in prioritize_building
kylelutze May 7, 2026
b53df9d
Game_io: extract RAII guards, lambda, and rotr1 helper
kylelutze May 7, 2026
6df1c7c
map/pathfind: name gradient sentinels, extract probe helpers, kill ve…
kylelutze May 7, 2026
9abbaf5
BasePlayer: fix typos in checkSum comment
kylelutze May 7, 2026
73d6166
libgag: replace GPL boilerplate headers with SPDX identifiers
kylelutze May 7, 2026
5a88d91
build: emit compile_commands.json for clangd
kylelutze May 7, 2026
e8e5875
map/gradient: drop unused Kai variant, route GT_BUILDING/GT_FORBIDDEN…
kylelutze May 7, 2026
397d3fc
libusl: collapse five tiny single-purpose files into their callers
kylelutze May 7, 2026
b3a6e9e
Update .gitignore
kylelutze May 7, 2026
456fb6c
Rename snake_case files back to PascalCase
kylelutze May 7, 2026
9e81f06
libusl: collapse code-gen parameter triple into a CodeGen context
kylelutze May 7, 2026
5f98461
libusl: add -p prelude flag to usl_runner
kylelutze May 7, 2026
1e228ce
map/gradient: chamfer pathfinding alternative, dual-run probe, listed…
kylelutze May 7, 2026
d02eddb
tests/gradient: add four-game corpus for chamfer conformance
kylelutze May 7, 2026
d9f17bf
ChecksumSidecar: drop duplicate checkSum, reuse vec across loop
kylelutze May 7, 2026
7ff6e3a
map/gradient: chamfer is production, BFS becomes dual-run reference
kylelutze May 7, 2026
c3a515d
CLAUDE.md: note that headless runs need release=1
kylelutze May 7, 2026
a518d70
map/gradient: delete BFS, dual-run probe, and listedAddr work queue
kylelutze May 7, 2026
fa759bd
Glob2: line-buffer stderr/stdout so abort() doesn't drop the last log…
kylelutze May 7, 2026
2518050
gitignore: ignore macOS release artifacts (Glob2.app, .dmg)
kylelutze May 7, 2026
445e975
GlobalContainer: split parseArgs into its own .cpp; clean up flag han…
kylelutze May 8, 2026
487027a
map/gradient: collapse vestigial chamfer template and fold body into …
kylelutze May 8, 2026
0b29cd3
threads: extract shared queue plumbing into ThreadMessageQueues<T>
kylelutze May 8, 2026
e7cecd6
yog/commands: replace doesMatch virtual with arity data on the base c…
kylelutze May 8, 2026
b544ee4
yog/commands: rename matchesArity back to doesMatch
kylelutze May 8, 2026
fb3dc5e
map/gradient: name local-grid + sentinel constants, unify spiral prop…
kylelutze May 8, 2026
467e6f1
ai/echo/Conditions: drop dead AllConditions/TicksPassed; trim boilerp…
kylelutze May 8, 2026
818fb36
FertilityCalculator: collapse thread/message scaffolding, name constants
kylelutze May 8, 2026
126cf51
rename: replace magic numbers with named constants (Phase 3a, cross-s…
kylelutze May 8, 2026
c1ed441
rename: high-value per-slice magic numbers (Phase 3b)
kylelutze May 8, 2026
882b8bd
rename: AI tuning constants (Phase 3b, per-AI tuning headers)
kylelutze May 8, 2026
870ba13
refactor: AICastor strategy table as array-of-structs (Phase 3b)
kylelutze May 9, 2026
7810518
refactor: extract rotr1/rotl1 helpers for checksum mixers (Phase 3b)
kylelutze May 9, 2026
a32c299
WinningConditions: simplify, constify, add equivalence harness
kylelutze May 9, 2026
853bd08
gitignore: ignore test/WinningConditionsHarness binary
kylelutze May 9, 2026
cf41c1e
AIEcho: split oversized .cpp files under 500 lines (Phase 3b)
kylelutze May 9, 2026
9a030bb
AIEcho: extract Conditions API demo from tick() to doc/aiEchoExamples…
kylelutze May 9, 2026
8cac99f
AIEcho: trim unused includes and using-directives across split files
kylelutze May 9, 2026
44947ad
NetGamePlayerManager: fix wrong-direction shift in slot compaction
kylelutze May 9, 2026
fe12e08
NetMessage: handle unknown opcode in getNetMessage to avoid null deref
kylelutze May 9, 2026
a6c6b34
Unit: clarify clearing-gradient sentinel filter in handleMovementRandom
kylelutze May 9, 2026
427f86c
AIEcho: dedupe Management* boilerplate, fix two latent serialization …
kylelutze May 9, 2026
87c8a99
docs: CLAUDE.md conventions/logging/pathfinding sections + test/README
kylelutze May 9, 2026
9cf84b0
Brush: collapse port-hostile patterns in BrushTool helpers
kylelutze May 9, 2026
44cb139
Campaign: reject malformed files in load(); check return in callers
kylelutze May 9, 2026
4ce567f
CampaignMenuScreen: fix index/name divergence in mission selection
kylelutze May 9, 2026
69c541c
GUIMapPreview: name the 128-pixel constant; un-shadow widget x/y/w/h
kylelutze May 9, 2026
3193872
Campaign: surface save errors instead of crashing/dropping silently
kylelutze May 10, 2026
57720a2
Building: fold gradient-buffer cleanup into named helpers, fix delete…
kylelutze May 10, 2026
40f401c
CustomGameOtherOptions: switch raw pointer arrays to std::array, fix …
kylelutze May 10, 2026
d408afc
NewMapScreen: persist extraIslands edits and fix initial display offset
kylelutze May 10, 2026
2b5eddb
GUIList: add std::optional<size_t> selection() and migrate sentinel-c…
kylelutze May 10, 2026
baab926
NewMapScreen: gate enum casts behind List::selection() check
kylelutze May 10, 2026
f4a8015
Building::updateCallLists: handle priority change while listed
kylelutze May 10, 2026
7f321c9
GameEvent: split sim and i18n; move unit/team display strings to UI
kylelutze May 10, 2026
1ec9354
CS-547: lift render-only animation state off Sector
kylelutze May 11, 2026
414ebd9
CS-546: mirror localTeam onto Map; drop sim->GUI reach in Building up…
kylelutze May 11, 2026
b378e36
CS-466 + BH-220: decompose AINumbi::mayUpgrade; fix level-1→2 exempla…
kylelutze May 11, 2026
e668d81
CS-545: relocate render-flavored files out of unit/ and map/
kylelutze May 11, 2026
c82ac46
Merge branch 'feat/ai-trainer-support-claude' into feat/ai-trainer-su…
kylelutze May 11, 2026
0fd4cd3
CS-539: rename isHardSpaceForBuildingSite parameter to clear shadowing
kylelutze May 11, 2026
0720668
CS-542: consolidate per-member visibility flips in Building.h
kylelutze May 11, 2026
fbefc21
CS-381: tie OrderModifySwarm::data size to NB_UNIT_TYPE
kylelutze May 12, 2026
946edc7
CS-410/BH-196: replace OrderModify* assert-deserialize ctors with fac…
kylelutze May 12, 2026
3a29e15
CS-410/BH-196: replace remaining assert-deserialize ctors with factories
kylelutze May 12, 2026
49e7602
CS-062: use sizeof(v) instead of magic 2/4 in fwrite size args
kylelutze May 13, 2026
d8daa8b
CS-070 + BH-037: collapse regular/alternate duplication in ChooseMapS…
kylelutze May 13, 2026
05c4f07
CS-178: split Game::executeOrder into per-case helpers
kylelutze May 13, 2026
b8ad2db
BH-059: fix InputStream leak in loadMapHeader on bad-format throw
kylelutze May 13, 2026
41ff24a
BH-061 + BH-235: unbreak glob2-server build (compile + link)
kylelutze May 13, 2026
dd7a980
BH-047: cap Team::MAX_COUNT at 12, keep on-disk format at 32
kylelutze May 14, 2026
628f34d
BH-195: validate OrderAlterateArea wire payload before BitArray::dese…
kylelutze May 14, 2026
2d21582
CS-126 + CS-131: decompose Engine::runOneGameSession into 10 phase he…
kylelutze May 14, 2026
98aad61
CS-320 + CS-322 + BH-150 + BH-151: split Mark tick from draw
kylelutze May 14, 2026
659aba7
CS-321: name pulse-radius locals in Mark::draw, use std::numbers::pi
kylelutze May 14, 2026
04e04d6
CS-150: delete unused Game::interpolateValues
kylelutze May 14, 2026
206230d
clean up compile warnings across libgag, src, libusl
kylelutze May 15, 2026
38a7e07
CS-129: replace C-string idioms on toLoadGameFileName std::string
kylelutze May 15, 2026
ecebd8b
BH-048: clear stale BasePlayer slots in CustomGameScreen::updatePlayers
kylelutze May 15, 2026
5a75dc9
BH-198: symmetric Sint16 codec for OrderAlterateArea maxX/maxY
kylelutze May 15, 2026
d2c8204
BH-170: dispatch slot 0 color change; complete BH-047 in MultiplayerG…
kylelutze May 16, 2026
90c2bf6
BH-058: chooseRandomMap returns std::optional<MapHeader> for empty maps/
kylelutze May 16, 2026
64e5ade
CS-419/417/418: split SettingsScreen god-functions; fix duplicate enum
kylelutze May 16, 2026
8fa89ec
SettingsScreen: split per-tab into four files (each <500 lines)
kylelutze May 16, 2026
9cfb1b9
GameGUI: guard setMultiLine against empty input
kylelutze May 18, 2026
6e80ea4
get rid of shadowed r usage
kylelutze May 18, 2026
fccd3f7
GameGUI: stop mutating selection inside drawRessourceInfos
kylelutze May 18, 2026
3d4f736
GameGUI: decompose drawChoice into four single-purpose helpers
kylelutze May 18, 2026
86d2ce0
GameGUI: move per-flag pending-order shadow off Building
kylelutze May 23, 2026
df321bb
GameGUI: decompose drawBuildingInfos into per-section helpers
kylelutze May 23, 2026
3e779aa
GameGUI: split orders, persistence, and step into separate files
kylelutze May 24, 2026
963e8aa
SoundMixer: lock setVolume and stopMusic against the audio thread
kylelutze May 24, 2026
6aefaa0
SoundMixer: fix OggVorbis_File leak when ov_open fails in loadTrack
kylelutze May 24, 2026
150042a
GameGUI: render actual sim ratio on swarm scrollbox
kylelutze May 24, 2026
1f671c9
NetSendOrder: reject oversized envelope size before allocation
kylelutze May 24, 2026
827f73a
GameGUI: fix chat-input slash-command past-end read
kylelutze May 24, 2026
1f1f761
TeamStep: clear selection via GameGUI hook instead of reading GUI state
kylelutze May 24, 2026
40cb914
MapMinigrad: replace 8 hand-unrolled UPDATE_MAX blocks with a directi…
kylelutze May 24, 2026
af49e61
Building::subscribeForFlagingStep: use linear distance for worker/war…
kylelutze May 24, 2026
47a0e42
GameGUIDrawUnitInfos: extract drawAbilityRow helper
kylelutze May 24, 2026
85ef15e
GameGUI::handleMenuClick: drop unit-selection debug hatch
kylelutze May 24, 2026
bf39d06
Engine: delete dead CPUStatisticsManager
kylelutze May 24, 2026
3714228
ChooseMapScreen::onAction: own InputStream via unique_ptr to plug exc…
kylelutze May 25, 2026
00b4196
BasePlayer::load: reject out-of-range teamNumber/number from external…
kylelutze May 25, 2026
4fd4a56
drawBuildingFailureReasons: fix swapped fruit/resource labels via ind…
kylelutze May 25, 2026
fd9ecde
GameMusicController: extract music state from GameGUI::musicStep to f…
kylelutze May 25, 2026
425554a
GameGUIInputMenuClickBuilding: extract interpretScrollBoxClick helper…
kylelutze May 26, 2026
0052ec4
magic-numbers: name SWIM_VARIANT_COUNT and team-slice constants
kylelutze May 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
11 changes: 11 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ options_cache.py
libgag.a
libusl.a
src/glob2
test/TestsRunner
test/WinningConditionsHarness
test/CampaignLoadHarness
testResults.xml
.sconf_temp
.sconsign.dblite
*.o
Expand All @@ -12,3 +16,10 @@ src/glob2
__pycache__/
build/
vcpkg_installed/
.DS_Store
compile_commands.json
.cache/
Glob2.app/
Glob2-*.dmg
test/CampaignSelectionHarness
test/NetSendOrderDecodeTest
16 changes: 16 additions & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
Globulation 2 -- Authors

Original authors of Globulation 2. Their contact information was previously
included in per-file source headers; sources now carry SPDX short-form
license identifiers (see COPYING for the full GPL v3 text).

Stephane Magnenat <stephane@magnenat.net> (2001-2008)
Luc-Olivier de Charrière <NuageBleu@gmail.com> (2001-2008)
Martin S. Nyffenegger <barock@ysagoon.com> (SGSL scripting language)
Bradley Arsenault (2006-2008)
Eli Dupree (2005, AIWarrush)
Leo Wandersleb <Leo.Wandersleb@gmx.de> (2006-2007, terrain generation)
Michiel De Muynck (2010)
Kyle Lutze (2006-2009)

For ongoing contributors, see the project's git history.
152 changes: 152 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
# glob2 (Legacy C++ Codebase)

This is the original Globulation 2 C++ codebase. It is legacy code and is not under active development.

## Build Commands

```bash
cd glob2

# Build (requires SCons, Python 3)
scons -j16

# Install (may need root)
scons install

# Clean
scons -c

# Build options
scons release=1 # Optimized release build
scons server=1 # Build YOG server only (no GUI/sound)
scons --build=/tmp/out # Out-of-source build
scons mingw=true # Windows cross-compile

# Custom paths
scons BINDIR=/path/bin INSTALLDIR=/path/share
```

**Dependencies:** SDL2, SDL2_net, SDL2_ttf, SDL2_image, libvorbis, libogg, speex, OpenGL, GLU, libepoxy, Boost (thread, date_time, system), zlib, fribidi, pcre. Optional: portaudio (voice chat). See `vcpkg.json` for the full list.

**Server build gotcha:** Always build the YOG server with `scons server=1` — never with a bare `scons build/src/glob2-server`. The `server=1` flag both selects the server target and defines `YOG_SERVER_ONLY` (which strips out GUI/audio code via `#ifndef` guards) and switches `libgag` to its stripped `libgag_server.a` variant. Without the flag, the .o files are compiled with the full GUI code path but link against the stripped libgag, producing dozens of misleading "undefined symbol" errors that look like fundamental rot. SCons also caches the option in `options_cache.py`, so once you've run `server=1`, subsequent `scons` invocations stay in server mode — pass `server=0` explicitly to switch back.

**Use `release=1` for headless runs.** The default `scons` build is `-g` with no `-O` flag, so `--nox` / `-test-games-nox` runs a debug binary that's roughly 10× slower than necessary. Always build with `scons release=1 -j16` for replay generation, benchmarking, or any throughput-sensitive headless work. Drop back to the default build only when you need a debugger, sanitizers, or fast incremental rebuilds. The flag is sticky via `options_cache.py`, so once set it persists until you pass `release=0`.

## Running Tests

Tests live in `glob2/test/` and are built as part of the SCons build. After `scons -j16`, run the resulting binaries (`TestsRunner`, `WinningConditionsHarness`).

See [`test/README.md`](test/README.md) for the **Map subclass test pattern** — the trick used in `MapQueryTest.cpp` to unit-test Map predicates without pulling in `globalContainer`, `Bullet`, `Team`, or the full simulation surface.

## Build System Internals

SCons-based. `SConstruct` is the main build script with platform detection (Linux, Darwin, Windows/MinGW). Library checks are done via custom configure functions in `scons/`. Build options are cached in `options_cache.py`.

## CI

Travis CI runs Docker-based builds on Ubuntu 18.04–21.04. See `.travis.yml` in the repo root.

## Architecture

### Engine Loop (40ms tick)

The engine is **synchronous** — no multithreading in the core. Every 40ms, `Engine::run()` calls `.step()` on the class hierarchy. See `doc/sourceCodeUnderstanding.txt` for the full explanation.

```
Engine::run() loop:
gui.step() → handle input
net.pushOrder(gui.getOrder()) → send local player order to network
net.pushOrder(ai.getOrder()) → send AI orders
net.step() → exchange orders over network
gui.executeOrder(...) → execute all received orders
gui.drawAll() → render
sleep() → maintain 40ms frame
```

### Class Hierarchy

```
Engine
├─ NetGame (network abstraction, order transmission via UDP)
└─ GameGUI (rendering, input)
└─ Game (game state)
├─ Map (terrain, resources)
├─ Team[32] (a "colony" — has color, units, buildings)
│ ├─ Unit[1024]
│ └─ Building[1024]
├─ Player[32] (human interface — keyboard+mouse or AI)
└─ Session (serializable state: BasePlayer[32], BaseTeam[32])
```

**Team vs Player:** A Team is a logical colony (color, units, buildings). A Player is a controller (human or AI). Multiple players can control one team. Only teams can be allied.

### Deterministic Networking

All clients compute identical game state. Only Orders (player actions) are transmitted. This requires:
- **Use `Utilities::syncRand()`** instead of `rand()` — all machines must get the same random numbers
- **Avoid `std::set`** — it is non-deterministic across platforms
- Orders are buffered in per-player FIFO queues (256 slots) to handle latency

### Key Source Directories

- **`src/`** — Main game (382 files): engine, game logic, AI, networking, GUI screens, YOG online system
- **`libgag/`** — Graphics/GUI toolkit library (widget system, sprites, file I/O, rendering)
- **`libusl/`** — USL scripting language for maps/campaigns
- **`data/`** — Runtime assets (graphics, fonts, music, GUI resources)
- **`maps/`** — Game maps
- **`campaigns/`** — Campaign definitions
- **`doc/`** — Architecture documentation

### Headless Mode & Replay Generation

See [docs/headless-replays.md](docs/headless-replays.md) for full details on running headless AI games and generating `.replay` files for cross-codebase testing.

Quick reference:
```bash
# Random AI-vs-AI game, headless, runs until game over or 90k ticks (loops forever — kill after first game)
./glob2 -test-games-nox

# Single game from a .game file, headless
./glob2 --nox <game-file> <steps> <runs>
# Example: ./glob2 --nox games/my_ai_game.game 5000 1
```

Replays are always written to `replays/last_game.replay` (overwritten each game).

### AI System

Multiple AI implementations in `src/`: AICastor, AIEcho, AINicowar, AINumbi, AIToubib, AIWarrush — all inherit from `AI` base class.

### Orders System

All player actions are serialized as `Order` objects (see `src/Order.h`). `NullOrder` means "player did nothing this tick" — distinct from "we haven't heard from this player yet." Orders carry complete action data (team, position, type, unit counts).

### YOG (Online Gaming)

Server/client architecture for online play. `YOGServer` handles matchmaking, chat channels, ratings, map database. LAN play is peer-to-peer. Build server-only binary with `scons server=1`.

## Conventions

**Filenames:** PascalCase (`Game.cpp`, `GameRenderUnits.cpp`). Do NOT introduce snake_case C++ filenames in `glob2/`. When splitting an existing PascalCase file, the new pieces stay PascalCase too.

**Header guards:** `#pragma once`. We are moving away from legacy `#ifndef` guards.

**macOS rename caveat:** Case-only filename renames need `git mv -f Foo.cpp foo_tmp.cpp && git mv -f foo_tmp.cpp Foo.cpp` — plain `git mv foo.cpp Foo.cpp` may silently no-op on a case-insensitive filesystem.

## Logging is dead — do not restore

`glob2/src/LogFileManager.h` defines `#define fprintf if(false)fprintf`. Every translation unit that includes that header gets all `fprintf` calls rewritten to a runtime `if(false)` branch the compiler dead-code-eliminates. ~60 files include `LogFileManager.h`. Original devs disabled logging "for bugs" (per the deprecation comment) and never came back; the infrastructure has been load-bearing in constructors but doing nothing for 15+ years.

**Cleanup pattern when refactoring a class with a `FILE* logFile` member and a `globalContainer->logFileManager->getFile(...)` call in its constructor:** drop the field, drop the `getFile()` call, drop every `fprintf(logFile, …)` site (already no-ops), drop the include of `LogFileManager.h`. No replacement needed — there's no live consumer.

`LogFileManager.h` itself stays — it carries the silencing macro.

**Don't propose adding `spdlog` or another logging library to "preserve diagnostic intent."** There's no consumer asking for the data and 15 years of silence means there's no demand. Re-introduce only when something concrete needs it.

## Pathfinding gotcha — chamfer pass cap

For the chamfer distance transform in `glob2/src/map/gradient/MapGradientGlobal.cpp`, the convergence-pass cap is bounded by the Uint8 value range (256), **not** by the Borgefors 1986 1-pass result.

Borgefors 1-pass holds only on an obstacle-free grid. With obstacles forcing the propagation path to bend (mountains, water channels, building footprints), each direction change costs ~K/2 passes. Real glob2 maps (128×128, e.g. `G2.game`) need significantly more than 4 passes — empirically 32 was sufficient and 8 was not.

Use the value-range bound (256). The cap is a tripwire for monotonicity violations, not a real-workload throttle. Do not try to derive a tighter bound from grid geometry — obstacle topology dominates.
87 changes: 42 additions & 45 deletions SConstruct
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
EnsureSConsVersion(0, 96, 92)
EnsureSConsVersion(3, 0, 0)
import sys
import os
import glob
Expand All @@ -8,7 +8,7 @@ import dmg
import nsis

isWindowsPlatform = sys.platform=='win32'
isLinuxPlatform = sys.platform=='linux2'
isLinuxPlatform = sys.platform.startswith('linux')
isDarwinPlatform = sys.platform=='darwin'


Expand All @@ -17,7 +17,7 @@ def establish_options(env):
opts.Add("CXXFLAGS", "Manually add to the CXXFLAGS", "-g")
opts.Add("LINKFLAGS", "Manually add to the LINKFLAGS", "-g")
if isDarwinPlatform:
opts.Add(PathOption("INSTALLDIR", "Installation Directory", "./"))
opts.Add(PathVariable("INSTALLDIR", "Installation Directory", "./"))
else:
opts.Add("INSTALLDIR", "Installation Directory", "/usr/local/share")
opts.Add("BINDIR", "Binary Installation Directory", "/usr/local/bin")
Expand Down Expand Up @@ -110,9 +110,9 @@ def configure(env, server_only):
missing.append("zlib")
else:
if conf.CheckLib("z"):
env.Append(LIBS="z")
env.Append(LIBS=["z"])
elif conf.CheckLib("zlib1"):
env.Append(LIBS="zlib1")
env.Append(LIBS=["zlib1"])
else:
print("Could not find libz or zlib1.dll")
missing.append("zlib")
Expand All @@ -121,51 +121,31 @@ def configure(env, server_only):
print("Could not find regex.h")
missing.append("regex")

boost_thread = ''
if conf.CheckLib("boost_thread") and conf.CheckCXXHeader("boost/thread/thread.hpp"):
boost_thread="boost_thread"
elif conf.CheckLib("boost_thread-mt") and conf.CheckCXXHeader("boost/thread/thread.hpp"):
boost_thread="boost_thread-mt"
else:
print("Could not find libboost_thread or libboost_thread-mt or boost/thread/thread.hpp")
missing.append("libboost_thread")
env.Append(LIBS=[boost_thread])

boost_date_time = ''
if conf.CheckLib("boost_date_time") and conf.CheckCXXHeader("boost/date_time/posix_time/posix_time.hpp"):
boost_thread="boost_thread"
boost_date_time="boost_date_time"
elif conf.CheckLib("boost_date_time-mt") and conf.CheckCXXHeader("boost/date_time/posix_time/posix_time.hpp"):
boost_thread="boost_thread-mt"
boost_date_time="boost_date_time-mt"
else:
print("Could not find libboost_date_time or libboost_date_time-mt or boost/thread/thread.hpp")
print("Could not find libboost_date_time or libboost_date_time-mt or boost/date_time/posix_time/posix_time.hpp")
missing.append("libboost_date_time")
env.Append(LIBS=[boost_date_time])
env.Append(LIBS=["boost_system", "pthread"])
if conf.CheckLib("boost_system"):
env.Append(LIBS=["boost_system"])
env.Append(LIBS=["pthread"])


if not conf.CheckCXXHeader("boost/shared_ptr.hpp"):
print("Could not find boost/shared_ptr.hpp")
missing.append("boost/shared_ptr.hpp")
if not conf.CheckCXXHeader("boost/tuple/tuple.hpp"):
print("Could not find boost/tuple/tuple.hpp")
missing.append("boost/tuple/tuple.hpp")
if not conf.CheckCXXHeader("boost/tuple/tuple_comparison.hpp"):
print("Could not find boost/tuple/tuple_comparison.hpp")
missing.append("boost/tuple/tuple_comparison.hpp")
if not conf.CheckCXXHeader("boost/logic/tribool.hpp"):
print("Could not find boost/logic/tribool.hpp")
missing.append("boost/logic/tribool.hpp")
if not conf.CheckCXXHeader("boost/lexical_cast.hpp"):
print("Could not find boost/lexical_cast.hpp")
missing.append("boost/lexical_cast.hpp")

#Do checks for OpenGL, which is different on every system
gl_libraries = []
if not server_only:
has_gl = True
if isDarwinPlatform:
print("Using Apple's OpenGL framework")
env.Append(FRAMEWORKS="OpenGL")
env.Append(FRAMEWORKS=["OpenGL", "CoreFoundation"])
elif conf.CheckLib("GL") and conf.CheckCXXHeader("GL/gl.h"):
gl_libraries.append("GL")
elif conf.CheckLib("GL") and conf.CheckCXXHeader("OpenGL/gl.h"):
Expand Down Expand Up @@ -255,6 +235,10 @@ def main():
env = Environment()
env["VERSION"] = "0.9.5.0"
establish_options(env)

# Emit compile_commands.json for clangd / IDE LSPs.
env.Tool('compilation_db')
env.CompilationDatabase()

if env['mingwcross']:
env.Platform('cygwin')
Expand All @@ -263,10 +247,6 @@ def main():
env['AR'] = 'i586-mingw32msvc-ar'
env['RANLIB'] = 'i586-mingw32msvc-ranlib'

try:
env.Clone()
except AttributeError:
env.Clone = env.Copy


# Add specific paths.
Expand All @@ -276,8 +256,18 @@ def main():
env.Append(CPPPATH=["C:/msys/1.0/local/include/SDL", "C:/msys/1.0/local/include", "C:/msys/1.0/include/SDL", "C:/msys/1.0/include"])
env.Append(CPPPATH=['/usr/local/include/SDL'])
if isDarwinPlatform:
env.Append(LIBPATH=["/opt/local/lib"])
env.Append(CPPPATH=["/opt/local/include"])
import subprocess
try:
brew_prefix = subprocess.check_output(["brew", "--prefix"], text=True).strip()
except (FileNotFoundError, subprocess.CalledProcessError):
brew_prefix = None
if brew_prefix:
env.Append(LIBPATH=[brew_prefix + "/lib"])
env.Append(CPPPATH=[brew_prefix + "/include"])
env['ENV']['PATH'] = brew_prefix + "/bin:" + env['ENV'].get('PATH', '')
else:
env.Append(LIBPATH=["/opt/local/lib"])
env.Append(CPPPATH=["/opt/local/include"])
if env['mingwcross']:
if os.path.isabs(env['crossroot']):
crossroot_abs = env['crossroot']
Expand All @@ -295,15 +285,23 @@ def main():

env.Append(CPPPATH=['#libgag/include', '#'])
env.Append(CPPPATH=['#libusl/src', '#'])
env.Append(CXXFLAGS=' -Wall -fPIC')
env.Append(CPPPATH=['#src', '#src/yog', '#src/ai', '#src/building',
'#src/game/entities',
'#src/gui',
'#src/map', '#src/map/edit', '#src/map/generator',
'#src/map/gradient', '#src/map/io', '#src/map/pathfind',
'#src/net', '#src/net/irc', '#src/net/message',
'#src/team',
'#src/unit'])
env.Append(CXXFLAGS=' -std=c++20 -Wall -fPIC')
env.Append(LINKFLAGS=' -Wall')
env.Append(LIBS=['SDL2_net'])
if not server_only:
env.Append(LIBS=['vorbisfile', 'SDL2_ttf', 'SDL2_image', 'speex'])

if env['release']:
env.Append(CXXFLAGS=' -O3 -s')
env.Append(LINKFLAGS=' -O3 -s --fwhole-program')
env.Append(LINKFLAGS=' -O3 -s -fwhole-program')
if env['profile']:
env.Append(CXXFLAGS=' -pg')
env.Append(LINKFLAGS='-pg')
Expand All @@ -315,11 +313,9 @@ def main():
env.Append(LINKFLAGS=['-mwindows'])
env.Append(CPPDEFINES=['-D_GNU_SOURCE=1', '-Dmain=SDL_main'])
elif isDarwinPlatform:
env.ParseConfig("/opt/local/bin/sdl-config --cflags")
env.ParseConfig("/opt/local/bin/sdl-config --libs")
env.ParseConfig("pkg-config sdl2 --cflags --libs")
else:
env.ParseConfig("sdl2-config --cflags")
env.ParseConfig("sdl2-config --libs")
env.ParseConfig("pkg-config sdl2 --cflags --libs")


env["TARFILE"] = env.Dir("#").abspath + "/glob2-" + env["VERSION"] + ".tar.gz"
Expand Down Expand Up @@ -356,7 +352,8 @@ def main():
dmg.create_dmg("Glob2-%s"%env["VERSION"],"%s.app"%env["BUNDLE_NAME"],env)

#TODO mac_bundle should be dependency of Dmg:
arch = os.popen("uname -p").read().strip()
import subprocess
arch = subprocess.check_output(["uname", "-p"], text=True).strip()
# mac_packages = env.Dmg('Glob2-%s-%s.dmg'% (fullVersion, arch), env.Dir('Glob2.app/') )
# env.Alias("package", mac_packages)

Expand Down
2 changes: 1 addition & 1 deletion campaigns/SConscript
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Import("env")
Import("PackTar")


if 'dist' or 'install' in COMMAND_LINE_TARGETS:
if 'dist' in COMMAND_LINE_TARGETS or 'install' in COMMAND_LINE_TARGETS:
any=False
for file in os.listdir("."):
if file.find(".txt") != -1 or file.find(".map") != -1:
Expand Down
Loading