| name | New Rule Completeness Validator |
| description | Validates that all necessary code changes are implemented when adding new game rules; use when adding new game rules or variants to ensure no files are missed. |
New Rule Completeness Validator
Purpose
When adding a new game rule or rule variant to Sanmill, you need to modify multiple files (typically 70-80 files, including ~60 localization files). This skill provides a completeness checklist to ensure no necessary code changes are missed.
Reference: docs/guides/ADDING_NEW_GAME_RULES.md
Use Cases
- Adding a new game rule variant
- Adding new game mechanics to existing rules (e.g., new capture rules)
- Modifying rule structure or parameters
- Reviewing rule-related pull requests
Architecture Philosophy
Sanmill is configuration-based, not inheritance-based. Rule variants are expressed as data (
Rulein C++,RuleSettingsin Flutter) and toggled at runtime. Any new mechanics must be gated by booleans/params so existing variants remain untouched and fast.
Quick Overview
- Estimated time: Simple parameter rule 2-4 hours; new mechanics 6-8 hours
- Complexity: ⭐⭐⭐ Medium-High
- Touched files: ~70-80 total (incl. ~60 ARB localization files)
Core Validation Checklist (Required Modifications)
1. C++ Engine Rule Definition
File: src/rule.cpp
- Added new rule definition to the end of
RULES[]array - Set all required fields:
-
name- Rule name (max 32 chars) -
description- Rule description (max 512 chars) -
pieceCount,flyPieceCount,piecesAtLeastCount -
hasDiagonalLines- Whether diagonal lines exist - Mill formation action and phase flags
- Capture rule configs (custodian, intervention, leap)
- Flying, draw rules (
mayFly,nMoveRule, etc.)
-
Example structure:
{
"Your Rule Name",
"Short description",
9, 3, 3, // pieceCount, flyPieceCount, piecesAtLeastCount
false, // hasDiagonalLines
MillFormationActionInPlacingPhase::removeOpponentsPieceFromBoard,
/*mayMoveInPlacingPhase*/ false,
/*isDefenderMoveFirst*/ false,
/*mayRemoveMultiple*/ false,
// ... other fields
kDefaultCaptureRuleConfig, // custodian
kDefaultCaptureRuleConfig, // intervention
kDefaultCaptureRuleConfig, // leap
/*mayFly*/ true,
/*nMoveRule*/ 100,
/*endgameNMoveRule*/ 100,
/*threefoldRepetitionRule*/ true
}
File: src/rule.h
- Incremented
N_RULESconstant (to match new RULES[] length) - If new parameters needed, added new fields to
Rulestruct?
// e.g., if it was 11, now it should be 12
constexpr auto N_RULES = 12;
2. Flutter UI Models
File: lib/rule_settings/models/rule_settings.dart
- Added new rule variant value to
RuleSetenum - Created new
RuleSettingssubclass (e.g.,YourNewVariantRuleSettings) - All fields in new subclass match 1-to-1 with C++
Rulestruct - Updated
ruleSetDescriptionsmap with description - Updated
ruleSetPropertiesmap with default property instance
Example:
enum RuleSet {
// ... existing variants
yourNewVariant, // new
}
class YourNewVariantRuleSettings extends RuleSettings {
const YourNewVariantRuleSettings({
// All params must match C++ Rule fields
}) : super(/* ... */);
}
3. Flutter UI Selection Interface
File: lib/rule_settings/widgets/modals/rule_set_modal.dart
- Added
RadioListTileor similar UI component for selecting new rule - UI item correctly references new
RuleSetenum value - If rule is experimental, marked as "Experimental" in
rule_settings_page.dart?
4. Localization (Internationalization)
Files: lib/l10n/intl_*.arb (~60 files)
-
intl_en.arb- Added English translations -
intl_zh_CN.arb- Added Simplified Chinese translations - All other
intl_*.arbfiles - At least copied English version - Ran
flutter gen-l10nto generate localization code
Key strings:
- Rule name
- Rule description
- Any new UI labels or hints
Conditional Validation Checklist (Based on Mechanic Type)
5. Game Logic Modifications (If Gameplay Changed)
File: src/position.cpp (C++ side)
- Must modify: If new rule changes placement, movement, or capture logic
- Used conditional guards for performance:
if (!rule.yourFeature) return fastPath(); // new logic - Handled all edge cases
File: lib/game_page/services/engine/position.dart (Flutter side)
- Must mirror logic from C++
position.cpp - C++ and Dart game logic remain fully symmetric
- Implemented
_putPiece,_movePiece,_removePiecerelated logic
Important: User-visible game logic must be symmetrically implemented in both C++ and Dart. Move generation (movegen) is C++ only.
6. Move Generation (C++ Only)
File: src/movegen.cpp
- If custom movement patterns exist (e.g., special move rules), updated move generation logic?
- Added conditional guards to avoid affecting other rules?
7. Mill Formation
File: src/mills.cpp
- If rule has non-standard mill formation, updated this file?
8. Engine Options (If Added New Rule Fields)
File: lib/game_page/services/engine/engine.dart
- Must modify: If
Rulestruct gained new fields - Added logic in
setRuleOptions()method to send new parameters - Parameter names match UCI option names
File: src/ucioption.cpp
- Must modify: If
Rulestruct gained new fields - Added corresponding UCI option definitions
- Added
on_changehandlers to apply values to globalruleobject
Example:
// ucioption.cpp
{"YourNewOption", "false", "bool", {}, on_your_new_option}
void on_your_new_option(Option &o) {
rule.yourNewField = o.get<bool>();
}
9. FEN Format Extension (Only If Dynamic State Persistence Needed)
When to extend FEN: Only when you must persist dynamic state that cannot be recomputed from the board.
Need to extend if:
- Need to track capture targets (custodian/intervention)
- Need to persist temporary user choice (e.g.,
preferredRemoveTarget) - Need to store intermediate multi-step state across moves/undo/search
- Need history-dependent constraints (e.g., "same piece can't move twice")
Do NOT extend for:
- ✗ Static rule params (piece counts,
hasDiagonalLines, etc.) - ✗ Anything derivable from board (piece counts, mills)
- ✗ Global flags (
mayFly,mayRemoveMultiple)
Files: src/position.h, src/position.cpp
- If FEN extension needed:
- Added new fields in
position.h - Added export logic in
fen()method - Added parsing logic in
set()method - Followed backward compatibility rules: new fields at end, missing fields have safe defaults
- Added new fields in
File: lib/game_page/services/engine/position.dart
- Mirrored C++ FEN fields
- Implemented
_getFen()export logic - Implemented
setFen()parsing logic
Files: tests/test_position.cpp, Flutter integration tests
- Added FEN round-trip tests (export then reimport, state should match)
10. Evaluation and Search (Rare)
File: src/evaluate.cpp
- If rule variant requires special evaluation logic, updated?
File: src/search.cpp
- If rule variant requires special search logic, updated?
Testing Validation Checklist
11. C++ Tests
Files: tests/test_*.cpp
- Rule loading and bounds tests (
set_rule(i)correctly loads new rule) - Position/logic unit tests (including new mechanics)
- If FEN extended, added FEN round-trip tests (
tests/test_position.cpp) - Ran
make testto ensure all tests pass
Run tests:
cd src
make build all
make test
12. Flutter Tests
Widget tests
- New rule displays correctly in modal
- Selecting new rule correctly saves and restores
Engine mirror tests
- Test scenarios cover
_putPiece,_movePiece,_removePiece - C++ and Dart logic produce same results
Integration tests
- Select new rule → start new game → verify behavior visible
Run tests:
cd src/ui/flutter_app
flutter test
13. Performance Benchmarks
- Ran benchmarks (if project has
make benchmark) - Ensured no performance regression when new feature is off
- Verified conditional guards enable early exits on hot paths
Build, Format, and Generate
14. Code Formatting
# Format all code
./format.sh
- C++ code formatted (clang-format)
- Dart code formatted (dart format)
15. Localization Generation
cd src/ui/flutter_app
flutter gen-l10n
- Generated localization code without errors
16. Full Build
# C++ build
cd src
make build all
# Flutter build (pick a platform to test)
cd ../src/ui/flutter_app
flutter build apk # Android
# or
flutter build ios # iOS
# or
flutter build windows # Windows
- All platforms build successfully
Quality & Safety Assurance Checklist
17. Backward Compatibility
- All existing rule variants remain unchanged
- All old tests still pass
- New features don't affect existing game logic (via conditional guards)
18. Performance Parity
- When new feature is off, no performance regression
- Hot paths remain efficient (early exits)
- Benchmarks show acceptable performance
19. Code Symmetry
- C++ and Dart user-visible logic fully symmetric
- Same inputs produce same outputs
- Tests verify symmetry
20. Documentation and Comments
- Clear docs and comments near each new flag
- Complex logic has explanatory comments
- Updated
docs/guides/ADDING_NEW_GAME_RULES.md(if needed)
21. Localization Completeness
- At minimum, includes English (EN) and Simplified Chinese (ZH_CN)
- Ideally, all ARB files updated
- All translation strings meaningful with no placeholders
Complete QA Checklist (Summary)
Before submitting PR, confirm all of the following:
-
RULES[]has new entry,N_RULESincremented - Flutter
RuleSetenum andRuleSettingssubclass match C++ - UI selection interface added, strings localized
- Game logic uses conditional guards and early exits
- C++ ↔ Dart position logic symmetric
- FEN extended if necessary; round-trip tests added
- If new
Rulefields added, engine options updated - All tests pass (C++ and Flutter)
- Benchmarks show no performance drop when feature off
- Code formatted, builds succeed
- Documentation and comments clear and complete
Validation Workflow
Phase 1: Planning (1 hour)
- Determine specific requirements for rule variant
- Determine if simple parameter adjustment or new mechanics needed
- List files that need modification
Phase 2: C++ Implementation (2-3 hours)
- Modify
src/rule.handsrc/rule.cpp - If new mechanics needed, modify
src/position.cpp,src/movegen.cpp, etc. - If new fields added, update
src/ucioption.cpp - Run C++ tests, ensure they pass
Phase 3: Flutter Implementation (2-3 hours)
- Modify
rule_settings.dart(enum + subclass + mappings) - Modify
rule_set_modal.dart(UI selection) - If game logic modified, mirror to
position.dart - Update
engine.dart'ssetRuleOptions() - Run Flutter tests, ensure they pass
Phase 4: Localization (30 minutes)
- Update
intl_en.arbandintl_zh_CN.arb - Batch update other ARB files (can use English initially)
- Run
flutter gen-l10n
Phase 5: Testing & Validation (1-2 hours)
- Run all C++ tests
- Run all Flutter tests
- Manually test new rule behavior in UI
- Check performance benchmarks
- Verify backward compatibility
Phase 6: Code Review & Documentation (30 minutes)
- Run code formatting
- Review all changes
- Update docs and comments
- Complete QA checklist
Common Pitfalls and Notes
❌ Common Mistakes
Forgot to increment
N_RULES- Symptom: Runtime crash or rule loading failure
- Fix: Ensure
N_RULESinsrc/rule.hmatchesRULES[]length
C++ and Flutter fields don't match
- Symptom: UI-set rule parameters don't take effect
- Fix: Ensure
RuleSettingsfields matchRulestruct 1-to-1
Missing UCI options
- Symptom: New Rule field values always default
- Fix: Add corresponding options and handlers in
ucioption.cpp
Didn't mirror game logic to Dart
- Symptom: UI preview inconsistent with actual game
- Fix: Ensure
position.dartmirrorsposition.cpplogic
Incomplete localization
- Symptom: Some languages show placeholders or English
- Fix: At minimum complete English and Chinese, others can use English placeholder
Performance regression
- Symptom: Game slower even when not using new feature
- Fix: Use conditional guards, ensure early exits
Over-extended FEN
- Symptom: FEN strings too long, hard to debug
- Fix: Only extend FEN when must persist dynamic state
✓ Best Practices
- Incremental development: Implement C++ first, test, then do Flutter
- Frequent testing: Run relevant tests after each file modification
- Use conditional guards: Ensure new code doesn't affect existing rules
- Maintain symmetry: C++ and Dart logic should be readable side-by-side
- Documentation first: Write comments and docs before code
- Reference existing rules: Look at other rules in
RULES[]as templates
Reference Files and Resources
Core Documentation
docs/guides/ADDING_NEW_GAME_RULES.md- Official guide for adding rules (must read)
C++ Files
src/rule.h- Rule struct definitionsrc/rule.cpp- RULES[] arraysrc/position.h/.cpp- Game position and logicsrc/movegen.cpp- Move generationsrc/ucioption.cpp- UCI optionssrc/mills.cpp- Mill formation logicinclude/config.h- Default config (rarely modified)
Flutter Files
lib/rule_settings/models/rule_settings.dart- Rule modelslib/rule_settings/widgets/modals/rule_set_modal.dart- UI selectionlib/game_page/services/engine/position.dart- Position mirrorlib/game_page/services/engine/engine.dart- Engine communicationlib/l10n/intl_*.arb- Localization files
Test Files
tests/test_*.cpp- C++ unit teststest/- Flutter unit and widget testsintegration_test/- Flutter integration tests
Output Format
Validation results should be reported clearly:
✓ [Complete] Core File Modifications
✓ src/rule.cpp - RULES[] added
✓ src/rule.h - N_RULES incremented
✓ rule_settings.dart - RuleSet enum added
...
⚠ [Warning] Conditional File Modifications
✓ src/position.cpp - Logic updated
✗ position.dart - Logic not mirrored (needs fix)
...
✓ [Complete] Test Validation
✓ C++ tests all passed (25/25)
✓ Flutter tests all passed (42/42)
...
✗ [Failed] Localization
✓ intl_en.arb - Updated
✓ intl_zh_CN.arb - Updated
✗ Other ARB files - Not updated (needs completion)
...
📊 Completion: 75% (15/20 checks passed)
💡 Recommendation: Priority fix position.dart mirror and localization
Summary
Adding a new game rule is a systematic effort requiring careful coordination between the C++ engine and Flutter UI. Using this checklist ensures:
- ✓ No necessary file modifications are missed
- ✓ C++ and Flutter stay synchronized
- ✓ Backward compatibility is not broken
- ✓ Performance is not affected
- ✓ Test coverage is adequate
- ✓ Localization is complete
Remember: When in doubt, refer to docs/guides/ADDING_NEW_GAME_RULES.md and existing rule implementations.