mirror of
https://github.com/nolen777/eagle0.git
synced 2026-07-28 20:55:44 +00:00
356 lines
14 KiB
Markdown
356 lines
14 KiB
Markdown
# CLAUDE.md
|
|
|
|
## CRITICAL BASH RULES (NEVER VIOLATE)
|
|
|
|
**NEVER chain bash commands.** Do not use `&&`, `||`, or `;` to combine commands. Each command must be a separate Bash tool call. Use parallel tool calls when commands are independent.
|
|
|
|
**NEVER prefix a command with `cd`.** Run `git`, `gh`, `bazel`, etc. directly from whatever the cwd already is. The cwd is a subdirectory of the worktree; `git`/`gh` find the repo via `.git` discovery and `bazel` finds the workspace via MODULE.bazel discovery — walking up from a subdir works fine. The shell resets cwd after every command anyway, so a `cd` never persists. (And `cd <dir> && <cmd>` violates the no-chaining rule above and forces an approval prompt every time.) Only `cd` in the rare case a tool genuinely cannot locate its root, and even then as its own Bash call, never a chain.
|
|
|
|
## CRITICAL GIT RULES (NEVER VIOLATE)
|
|
|
|
**NEVER use `git -C`.** Just run `git` directly from the cwd — it finds the repo via `.git` discovery. Do not `cd` to the repo root either (see the no-`cd` bash rule above).
|
|
|
|
**NEVER push directly to main/master.** No exceptions. Not for "small changes." Not for docs. Not ever.
|
|
|
|
**NEVER merge PRs.** You create PRs. The user merges them. No exceptions.
|
|
|
|
**ALWAYS use this workflow:**
|
|
1. Create a feature branch from origin/main
|
|
2. Commit to that branch
|
|
3. Create a PR with `gh pr create`
|
|
4. Wait for user to merge (DO NOT run `gh pr merge`)
|
|
|
|
If you catch yourself about to run `git push origin main` or `git push origin <branch>:main`, STOP. You are about to violate a critical rule. Create a PR instead.
|
|
|
|
If you catch yourself about to run `gh pr merge`, STOP. Only the user merges PRs.
|
|
|
|
---
|
|
|
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
|
|
## Project Overview
|
|
|
|
Eagle0 is a multi-language gaming system combining strategic turn-based gameplay (Eagle) with tactical hex-based
|
|
combat (Shardok). The system integrates LLM-based narrative generation and supports both human and AI players.
|
|
|
|
## Architecture
|
|
|
|
**Three-Tier Game System:**
|
|
|
|
- **Unity Client (C#)**: Real-time strategy game client with integrated tactical combat UI
|
|
- **Eagle (Scala)**: Strategic layer managing turn-based gameplay, diplomacy, hero progression, and province control
|
|
- **Shardok (C++)**: Tactical layer handling real-time hex-based combat simulation with performance-critical battle
|
|
resolution
|
|
|
|
**Communication Flow:**
|
|
|
|
```
|
|
Unity Client ↔ Eagle (gRPC streaming) ↔ Shardok (internal gRPC)
|
|
```
|
|
|
|
**Key Entry Points:**
|
|
|
|
- `/src/main/csharp/net/eagle0/clients/unity/eagle0/` - Unity C# game client
|
|
- `/src/main/scala/net/eagle0/eagle/Main.scala` - Eagle strategic game server
|
|
- `/src/main/cpp/net/eagle0/shardok/shardok_server_main.cpp` - Shardok tactical server
|
|
|
|
**Protocol Buffer Architecture:**
|
|
|
|
- Extensive use of protobuf for type-safe communication
|
|
- Separate packages: `api/` (client-facing), `internal/` (server state), `views/` (client projections)
|
|
- Event sourcing pattern with immutable action history
|
|
|
|
## Essential Commands
|
|
|
|
### Building
|
|
|
|
```bash
|
|
# Build Eagle server (Scala strategic layer)
|
|
bazel build //src/main/scala/net/eagle0/eagle:eagle_server_deploy.jar
|
|
|
|
# Build Shardok server (C++ tactical layer)
|
|
bazel build -c opt //src/main/cpp/net/eagle0/shardok:shardok-server
|
|
|
|
# Shardok server includes both AI algorithms
|
|
bazel build //src/main/cpp/net/eagle0/shardok:shardok-server
|
|
|
|
# Build Unity/C# client
|
|
./scripts/build_protos.sh # Protocol buffer generation for Unity
|
|
./scripts/build_plugins.sh # Native plugins for all platforms
|
|
./scripts/build_windows_plugin.sh # Windows-specific plugin build
|
|
# Unity builds via CI: ci/github_actions/build_unity.sh
|
|
```
|
|
|
|
### Running Services
|
|
|
|
```bash
|
|
# Eagle server (port 40032)
|
|
bazel run //src/main/scala/net/eagle0/eagle:eagle_server -- --eagle-grpc-port 40032
|
|
# Or: ./scripts/eagle_run.sh
|
|
|
|
# Shardok server
|
|
bazel run //src/main/cpp/net/eagle0/shardok:shardok-server --compilation_mode=opt
|
|
# Or: ./scripts/shardok_run.sh
|
|
```
|
|
|
|
### Testing
|
|
|
|
```bash
|
|
# Run all tests
|
|
bazel test //src/test/... //src/main/go/...
|
|
|
|
# Component-specific tests
|
|
bazel test //src/test/scala/... # Scala Eagle tests
|
|
bazel test //src/test/cpp/... # C++ Shardok tests
|
|
```
|
|
|
|
### Code Generation
|
|
|
|
```bash
|
|
bazel run gazelle # Update Go build files
|
|
./scripts/updateActionResultTypes.sh # Update protocol buffer mappings
|
|
```
|
|
|
|
### Pre-Commit Checklist
|
|
|
|
**MANDATORY: Before running `git commit`, verify:**
|
|
|
|
1. **If you modified any BUILD.bazel file:** Run `bazel run gazelle` and stage any changes it makes
|
|
2. **If you modified C++ or C# files:** Run `clang-format -i` on the modified files
|
|
3. **If you modified Scala files:** scalafmt will run automatically via pre-commit hook
|
|
4. **ALWAYS run the full test suite for your language changes before pushing:**
|
|
- For Scala changes: `bazel test //src/test/scala/...`
|
|
- For C++ changes: `bazel test //src/test/cpp/...`
|
|
- This catches exhaustive pattern match errors and other compile-time failures that single-target builds miss
|
|
|
|
The pre-commit hook runs gazelle but only checks if it succeeds - it does NOT verify the BUILD files are in canonical format. The `gazelle_test` will fail if deps are not alphabetically sorted. **Always run gazelle manually after BUILD file changes.**
|
|
|
|
### Code Formatting
|
|
|
|
```bash
|
|
# ALWAYS run clang-format after making any C++ or C# code changes
|
|
clang-format -i <modified_files>
|
|
|
|
# Format all C++ files in a directory:
|
|
find . -name "*.cpp" -o -name "*.hpp" | xargs clang-format -i
|
|
|
|
# Format all C# files in a directory:
|
|
find . -name "*.cs" | xargs clang-format -i
|
|
```
|
|
|
|
### Static Analysis
|
|
|
|
```bash
|
|
# Run clang-tidy static analysis on C++ files
|
|
# Note: This may show some header include errors but will still analyze the main file
|
|
bazel run @llvm_toolchain//:clang-tidy -- --checks='readability-*,bugprone-*,clang-analyzer-*' <file_path> -- -I/Users/dancrosby/CodingProjects/github/eagle0 -std=c++23
|
|
|
|
# Example for AI files:
|
|
bazel run @llvm_toolchain//:clang-tidy -- --checks='readability-*,bugprone-*,clang-analyzer-*' /Users/dancrosby/CodingProjects/github/eagle0/src/main/cpp/net/eagle0/shardok/ai/AIUnitScoreCalculator.cpp -- -I/Users/dancrosby/CodingProjects/github/eagle0 -std=c++23
|
|
```
|
|
|
|
## AI Algorithm Selection
|
|
|
|
Eagle0 supports two AI algorithms for tactical combat decision-making:
|
|
|
|
### Iterative Deepening AI (Default)
|
|
|
|
The original minimax-based AI with sophisticated randomness handling:
|
|
|
|
- **Advantages**: Proven, sophisticated randomness evaluation, comprehensive lookahead
|
|
- **Use cases**: Production builds, scenarios requiring precise evaluation
|
|
- **Performance**: Single-threaded, thorough evaluation
|
|
|
|
### Monte Carlo Tree Search AI (MCTS)
|
|
|
|
Modern MCTS-based AI with multithreading support:
|
|
|
|
- **Advantages**: Multithreaded, better performance on modern CPUs, anytime algorithm
|
|
- **Use cases**: Performance testing, scenarios requiring fast decisions
|
|
- **Performance**: Multithreaded, adaptive depth based on time budget
|
|
|
|
### Switching Between Algorithms
|
|
|
|
The algorithm is selected at **runtime** via the ShardokAIClient constructor:
|
|
|
|
```cpp
|
|
// Using Iterative Deepening AI (default)
|
|
ShardokAIClient client(playerId, isDefender, hexMap, settings);
|
|
// OR explicitly:
|
|
ShardokAIClient client(playerId, isDefender, hexMap, settings, AIAlgorithmType::ITERATIVE_DEEPENING);
|
|
|
|
// Using MCTS AI
|
|
ShardokAIClient client(playerId, isDefender, hexMap, settings, AIAlgorithmType::MCTS);
|
|
```
|
|
|
|
```bash
|
|
# Build the server (includes both AI algorithms)
|
|
bazel build //src/main/cpp/net/eagle0/shardok:shardok-server
|
|
|
|
# Test both algorithms
|
|
bazel test //src/test/cpp/net/eagle0/shardok/ai:ai_iterative_deepening_test
|
|
bazel test //src/test/cpp/net/eagle0/shardok/ai:ai_mcts_test # If available
|
|
|
|
# Performance tests
|
|
./scripts/ai_perf_test.sh # Uses whatever algorithm the server is configured to use
|
|
```
|
|
|
|
Both implementations are compatible with all existing interfaces and produce the same `SearchResult` structure.
|
|
|
|
**Note**: Both implementations are documented in `src/main/cpp/net/eagle0/shardok/ai/AI_SCORING_SYSTEM.md`, including
|
|
recommendations for improving MCTS randomness handling.
|
|
|
|
The AI algorithm selection is made at runtime when creating ShardokAIClient instances, allowing different AI strategies
|
|
to be used for different players or game situations within the same server process.
|
|
|
|
## Language-Specific Patterns
|
|
|
|
**Scala (Strategic Layer):**
|
|
|
|
- Use `EngineImpl.scala` for core game logic modifications
|
|
- Follow event sourcing pattern - all changes through immutable actions
|
|
- gRPC streaming for real-time client updates via `EagleServiceImpl.scala`
|
|
- LLM integration in `/common/llm_integration/` for narrative generation
|
|
|
|
**C++ (Tactical Layer):**
|
|
|
|
- Performance-critical combat in `ShardokEngine.hpp/.cpp`
|
|
- FlatBuffers for efficient serialization in `/flatbuffer/` directory
|
|
- AI systems in `/ai/` subdirectory with pluggable strategy selectors
|
|
- Extensive unit testing with Google Test framework
|
|
|
|
**Protocol Buffers:**
|
|
|
|
- Three-layer structure: `api/` (client), `internal/` (server), `views/` (projections)
|
|
- Use `shardok_internal_interface.proto` for Eagle-Shardok communication
|
|
- Maintain backward compatibility when modifying existing messages
|
|
|
|
**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)
|
|
- 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/`
|
|
- Seamless transition between strategic gameplay and hex-based tactical combat
|
|
- **NEVER add defensive null checks on Unity Inspector fields** - these hide configuration bugs. If a field isn't
|
|
linked in the editor, it should throw a NullReferenceException so the problem is immediately obvious. Silently
|
|
skipping code when a required field is null makes bugs harder to find.
|
|
|
|
**Go (Build Tools):**
|
|
|
|
- Build automation and code generation utilities
|
|
- AWS S3 integration for deployment artifacts
|
|
|
|
## Testing Strategy
|
|
|
|
- Comprehensive unit tests for both Scala and C++ components
|
|
- Integration tests for Eagle-Shardok communication
|
|
- Map validation tests ensure game content integrity
|
|
- Use `GameSettings_test_utils.cpp` and `ShardokEngineBasedTestData.cpp` for C++ test helpers
|
|
|
|
### Scala Testing Patterns
|
|
|
|
**Use `inside()` instead of `asInstanceOf` for type matching in tests:**
|
|
|
|
Never use `asInstanceOf` in tests. Instead, use ScalaTest's `inside()` pattern for safe type matching:
|
|
|
|
```scala
|
|
// BAD - don't do this
|
|
val changedHero = result.changedHeroes.head.asInstanceOf[ChangedHeroC]
|
|
changedHero.heroId shouldBe 19
|
|
|
|
// GOOD - use inside() pattern
|
|
import org.scalatest.Inside.inside
|
|
|
|
inside(result.changedHeroes.head) { case changedHero: ChangedHeroC =>
|
|
changedHero.heroId shouldBe 19
|
|
changedHero.vigorChange shouldBe StatDelta(17.2)
|
|
}
|
|
```
|
|
|
|
The `inside()` pattern:
|
|
- Provides better error messages when the type doesn't match
|
|
- Is idiomatic ScalaTest
|
|
- Works with pattern matching for more complex assertions
|
|
|
|
## Performance Testing
|
|
|
|
When making performance-related changes to the AI or engine:
|
|
|
|
```bash
|
|
# 1. Commit your changes to a feature branch
|
|
git checkout -b performance-improvement-feature
|
|
git add . && git commit -m "Implement performance improvement"
|
|
|
|
# 2. Run performance tests multiple times on your branch to reduce noise
|
|
for i in 1 2 3; do
|
|
echo "=== Run $i ==="
|
|
./scripts/ai_perf_test.sh 2>&1 | grep -A 20 "AI Search Performance Summary"
|
|
done
|
|
# Save or note the results
|
|
|
|
# 3. Switch to main branch and run the same tests
|
|
git checkout main
|
|
for i in 1 2 3; do
|
|
echo "=== Run $i ==="
|
|
./scripts/ai_perf_test.sh 2>&1 | grep -A 20 "AI Search Performance Summary"
|
|
done
|
|
|
|
# 4. Compare the results between your branch and main
|
|
# Key metrics to compare:
|
|
# - Commands evaluated at each depth (e.g., "Depth 3: 169/523 commands")
|
|
# - Average search depth achieved
|
|
# - Completion rates at each depth
|
|
```
|
|
|
|
**Important notes:**
|
|
|
|
- Run tests multiple times (3-5) to account for performance variance
|
|
- Focus on commands evaluated at each depth rather than total commands
|
|
- Commands at different depths aren't directly comparable (depth 3 is more valuable than depth 2)
|
|
- **Always test performance changes** - what seems like an optimization may sometimes have unexpected overhead or
|
|
behavior changes.
|
|
|
|
## Troubleshooting Scala Build Errors
|
|
|
|
### MissingType Errors
|
|
|
|
When you see errors like:
|
|
```
|
|
dotty.tools.dotc.core.MissingType: Cannot resolve reference to type net.eagle0.eagle.internal.game_state.type.GameState
|
|
```
|
|
|
|
**This is NOT a Scala compiler crash.** This is a missing dependency in BUILD.bazel.
|
|
|
|
**How to fix:**
|
|
1. Identify the missing type from the error message (e.g., `game_state.GameState`)
|
|
2. Find the Bazel target that provides this type (e.g., `//src/main/protobuf/net/eagle0/eagle/internal:game_state_scala_proto`)
|
|
3. Add it to the `deps` of the failing target
|
|
4. If the type appears in a public method signature, also add it to `exports` so downstream targets can see it
|
|
|
|
**Common pattern:** When adding a method to a class that takes or returns a proto type, the proto dependency often needs to be added to both `deps` AND `exports`.
|
|
|
|
### Bazel Clean
|
|
|
|
**NEVER run `bazel clean` without asking first.** It rarely fixes actual issues and wastes significant rebuild time. The issues that seem like they need `bazel clean` are usually:
|
|
- Missing imports in Scala code
|
|
- Missing dependencies in BUILD.bazel
|
|
- Missing exports for types used in public signatures
|
|
|
|
## Game Content
|
|
|
|
**Maps:** `.e0mj` files in `/src/main/resources/net/eagle0/shardok/maps/`
|
|
**Configuration:** Game parameters in `/src/main/resources/net/eagle0/eagle/game_parameters.json`
|
|
**Data Files:** TSV format for battalions, heroes, and other game data
|
|
|
|
**NEVER modify hero names.** Do not change names in `heroes.tsv`, `generated_heroes`, or any other hero data files. The names are carefully chosen and are not to be altered.
|
|
|
|
## Deployment
|
|
|
|
- Bazel handles multi-language builds and dependencies
|
|
- CI/CD via GitHub Actions with platform-specific build scripts in `/ci/github_actions/`
|
|
- Docker containerization available via `ci/eagle_run.Dockerfile`
|
|
- Always run "bazel run //:gazelle" after editing any BUILD.bazel files
|
|
- *ALWAYS ALWAYS* run "bazel run gazelle" after any change that modifies a BUILD.bazel file
|