MineRPG uses a simplified Git Flow model:
master ← Stable releases, always buildable, tagged versions
└── develop ← Integration branch, always compilable
├── feature/* ← New features
├── fix/* ← Bug fixes
├── refactor/* ← Refactoring (no behavior change)
├── perf/* ← Performance optimizations
├── docs/* ← Documentation only
├── chore/* ← Maintenance (CI, deps, configs)
└── test/* ← Adding/modifying tests
Rules:
master: only merges fromdevelopvia PR. Never push directly. Each merge = a tagged stable versiondevelop: only merges from working branches via PR. Never push directly- Working branches: created from
develop, merged back intodevelop - Branches are deleted automatically after merge
Format: type/description-in-kebab-case
feature/greedy-meshing
fix/chunk-loading-crash
refactor/stat-modifier-api
perf/mesh-pooling
docs/update-architecture
chore/update-ci-pipeline
test/damage-calculator-edge-cases
Names in kebab-case, in English, descriptive but short (3-5 words max).
# Always start from develop
git checkout develop
git pull origin develop
git checkout -b feature/my-featureEvery commit must follow Conventional Commits:
type(scope): description
[optional body]
[optional footer]
| Type | Usage |
|---|---|
feat |
New feature |
fix |
Bug fix |
refactor |
Refactoring (no behavior change) |
perf |
Performance optimization |
test |
Adding or modifying tests |
docs |
Documentation |
chore |
Maintenance (CI, .gitignore, deps, configs) |
style |
Formatting, conventions (no logic change) |
build |
Build system changes (.csproj, .sln, Directory.Build.props) |
ci |
CI/CD changes (GitHub Actions) |
Scopes match project names:
core, rpg, world, entities, network,
godot-world, godot-entities, godot-ui, godot-network,
game, tests
- Description in English, lowercase, no period at the end
- Imperative present tense: "add", "fix", "remove" (not "added", "fixes")
- Description line: max 72 characters
- Body: wrap at 80 characters, explains why not what
- Footer:
Closes #XX,Fixes #XX,Breaking change: description
feat(world): implement greedy mesh builder
Implements GreedyMeshBuilder that reduces vertex count by 80-90%
compared to naive per-face meshing.
- Iterates over 3 axes x 2 directions (6 passes)
- Merges coplanar adjacent faces of same block type
- Calculates per-vertex ambient occlusion
Closes #42
fix(godot-world): fix chunk mesh not applied on main thread
Mesh application was called from the generation thread,
causing random crashes. Now uses CallDeferred().
Fixes #58
perf(world): add frustum culling for chunks
test(rpg): add damage calculator tests for critical hits
Before every commit, verify:
# Build with zero warnings
dotnet build MineRPG.sln -c Release
# All tests pass
dotnet test src/MineRPG.Tests/MineRPG.Tests.csproj -c Release
# Code format is correct
dotnet format MineRPG.sln --verify-no-changesAlso check:
- File names match type names exactly
- Namespaces mirror folder structure
- No hardcoded data (everything in
Data/files) - No
GD.Print()(use centralized logging) - No
GetNode()with string paths (use[Export]) - No business logic in Godot bridge nodes
- No cross-project dependency violations
- New public types have corresponding tests
# Push your branch
git push -u origin feature/my-feature
# Create PR via GitHub CLI or web UI
gh pr create --base develop --title "feat(world): implement greedy mesh builder"| Check | Required | Tool |
|---|---|---|
| Build with 0 warnings | Yes | dotnet build -c Release |
| All tests pass | Yes | dotnet test |
| Code format correct | Yes | dotnet format --verify-no-changes |
| Conventional Commits | Yes | CI commit-lint |
No GD.Print |
Yes | CI grep check |
| JSON data valid | Yes | CI validation |
| Files < 300 lines | Warning | CI quality check |
| Methods < 40 lines | Warning | CI quality check |
All checks above plus:
| Check | Required |
|---|---|
| 1 approval on the PR | Yes |
Branch up to date with master |
Yes |
Fill in the PR template when creating a pull request. Ensure all checkboxes are checked before requesting review.
When reviewing PRs, check for:
- Follows the Style Guide (no
var, Allman braces, explicit types) - Respects the Architecture dependency graph
- No allocations in hot paths
- Data-driven where applicable
- Tests cover the new/changed logic
- Commit messages follow Conventional Commits
- PRs to
develop: squash merge (clean linear history) - PRs to
master: squash merge with version tag after merge - Delete the source branch after merge
Releases follow semantic versioning: vMAJOR.MINOR.PATCH
# After merging develop into master
git checkout master
git pull origin master
git tag v0.1.0
git push origin v0.1.0The release workflow automatically:
- Builds and tests the tagged commit
- Generates a changelog from Conventional Commits
- Creates a GitHub Release
- Determine which project it belongs to (pure logic vs Godot bridge)
- Create the file in the correct subfolder of that project
- Use the matching namespace:
namespace MineRPG.{Project}.{SubFolder}; - If it's a Godot Node class:
partial class, must be in aGodot.*project - Verify the project has the correct dependency references
- Add tests in
MineRPG.Tests/{Project}/if it's pure logic
See ARCHITECTURE.md for the step-by-step guide:
- Determine where it lives (pure vs bridge)
- Define the interface
- Implement pure logic
- Create the Godot bridge (if needed)
- Wire it up in
CompositionRoot - Add events
- Write tests
- New block: JSON in
Data/Blocks/, followBlockDefinitionschema - New item: JSON in
Data/Items/, followItemDefinitionschema - New mob: JSON in
Data/Mobs/, followMobDefinitionschema - New biome: JSON in
Data/Biomes/, followBiomeDefinitionschema
No code changes needed for new data entries unless custom behavior is required.
- Every public method in pure projects (Core, RPG, World, Entities, Network)
- Every data transformation (damage calculation, stat modifiers, loot generation)
- Every state transition (quest states, AI states, chunk states)
- Edge cases (empty inventory, zero health, max level, full stack)
MethodName_Condition_ExpectedResult
public void Calculate_WithCriticalHit_ReturnsDoubledDamage() { }
public void AddItem_WhenInventoryFull_ReturnsFalse() { }# All tests
dotnet test src/MineRPG.Tests/MineRPG.Tests.csproj -c Release
# Specific test class
dotnet test src/MineRPG.Tests/MineRPG.Tests.csproj --filter "FullyQualifiedName~DamageCalculatorTests"
# With verbose output
dotnet test src/MineRPG.Tests/MineRPG.Tests.csproj -c Release -v normalUse FluentAssertions exclusively:
result.Should().Be(42);
items.Should().HaveCount(3);
action.Should().Throw<InvalidOperationException>();Use NSubstitute:
IEventBus eventBus = Substitute.For<IEventBus>();
eventBus.Received(1).Publish(Arg.Any<BlockMinedEvent>());