From 79e724d91dc67777f645ab3149f9ed4fab73c0fa Mon Sep 17 00:00:00 2001 From: Niyaz <9331133+niyazmft@users.noreply.github.com> Date: Wed, 3 Jun 2026 23:01:38 +0300 Subject: [PATCH 1/5] docs: add project board tracking all development phases --- PROJECT_BOARD.md | 229 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 229 insertions(+) create mode 100644 PROJECT_BOARD.md diff --git a/PROJECT_BOARD.md b/PROJECT_BOARD.md new file mode 100644 index 0000000..a781d64 --- /dev/null +++ b/PROJECT_BOARD.md @@ -0,0 +1,229 @@ +# Emberfall Development Roadmap - Project Board + +**Project:** Emberfall Tactical Combat Game +**Repository:** https://github.com/niyazmft/emberfall +**Last Updated:** 2026-06-03 +**Current Phase:** Phase 4 (Minimum Viable Gameplay) + +--- + +## Board Overview + +This document serves as the project board tracking all development tasks across phases. +**Status Legend:** +- ✅ **Done** - Completed and merged +- 🔄 **In Progress** - Currently being worked on +- ⏳ **Ready** - Can start immediately (no blockers) +- ⚠️ **Blocked** - Waiting on other tasks +- 📋 **Backlog** - Future work + +--- + +## Phase 1: Foundation Hardening ✅ COMPLETE + +| Task | Issue | Status | Assignee | Notes | +|------|-------|--------|----------|-------| +| 1.1 Split BurdenManager | #72 | ✅ Done | Jules | Split into 3 classes | +| 1.2 Fix ElementalTypes Enum | #73 | ✅ Done | Jules | Removed magic numbers | +| 1.3 Timing Instrumentation | #74 | ✅ Done | Jules | Performance monitoring | +| 1.4 BaseEnemy Class | #76 | ✅ Done | Jules | Enemy framework ready | + +**Phase 1 Completion:** 100% ✅ +**Date Completed:** 2026-05-31 + +--- + +## Phase 2: 2.5D Rendering System ✅ COMPLETE + +| Task | Issue | Status | Assignee | Notes | +|------|-------|--------|----------|-------| +| 2.1 GridRenderer | #75 | ✅ Done | Jules | Isometric grid working | +| 2.2 EntityVisualProxy | #77 | ✅ Done | Jules | Smooth interpolation | +| 2.3 ApparitionRenderer | #78 | ✅ Done | Jules | Effects integrated | +| 2.4 CombatRoom Scene | #79 | ✅ Done | Jules | Main scene functional | + +**Phase 2 Completion:** 100% ✅ +**Date Completed:** 2026-05-31 + +--- + +## Phase 3: CI/CD & Documentation ✅ COMPLETE + +| Task | Issue | Status | Assignee | Notes | +|------|-------|--------|----------|-------| +| 3.1 Update CI Workflow | #83 | ✅ Done | Jules | Test suite in CI | +| 3.2 Pre-Push Script | #81 | ✅ Done | Jules | Full validation | +| 3.3 ARCHITECTURE.md | #80 | ✅ Done | Jules | Documentation | + +**Phase 3 Completion:** 100% ✅ +**Date Completed:** 2026-05-31 + +--- + +## Phase 4: Minimum Viable Gameplay 🎯 CURRENT + +### Sprint 1: Combat Core (Weeks 1-2) + +| Task | Issue | Status | Priority | Blocked By | Can Parallel With | +|------|-------|--------|----------|--------------|-------------------| +| 4.1 Combat Input System | #130 | ⏳ Ready | CRITICAL | None | 4.2, 4.7 | +| 4.2 Basic Enemy AI | #132 | ⏳ Ready | CRITICAL | None | 4.1, 4.7 | + +**Sprint 1 Goal:** Player can target and attack enemies; enemies can move and attack +**Definition of Done:** Combat actions work in isolation (no turn system yet) + +--- + +### Sprint 2: Turn System (Weeks 3-4) + +| Task | Issue | Status | Priority | Blocked By | Can Parallel With | +|------|-------|--------|----------|--------------|-------------------| +| 4.3 Turn Manager | #131 | ⚠️ Blocked | CRITICAL | 4.1, 4.2 | Nothing | + +**Sprint 2 Goal:** Full turn-based combat loop +**Definition of Done:** 1 player vs 3 enemies, turn order, AP economy, victory/defeat + +--- + +### Sprint 3: Content Foundation (Weeks 5-6) + +| Task | Issue | Status | Priority | Blocked By | Can Parallel With | +|------|-------|--------|----------|--------------|-------------------| +| 4.4 Room Loading System | #133 | ⚠️ Blocked | HIGH | 4.3 | 4.5 | +| 4.5 Enemy Variety | #135 | ⚠️ Blocked | HIGH | 4.2 | 4.4 | + +**Sprint 3 Goal:** Different rooms with different enemies +**Definition of Done:** 3-5 room layouts, 3 enemy types, encounter spawning + +--- + +### Sprint 4: Polish (Weeks 7-8) + +| Task | Issue | Status | Priority | Blocked By | Can Parallel With | +|------|-------|--------|----------|--------------|-------------------| +| 4.6 Combat HUD | #134 | ⚠️ Blocked | MEDIUM | 4.1, 4.3 | 4.7, 4.8 | +| 4.7 Settings Menu | #136 | ⏳ Ready | MEDIUM | None | 4.6, 4.8 | +| 4.8 Moral Choice UI | #137 | ⚠️ Blocked | MEDIUM | 4.1, 4.3 | 4.6, 4.7 | + +**Sprint 4 Goal:** Game feels polished and complete +**Definition of Done:** HUD shows stats, settings work, moral choices visible + +--- + +## Phase 4 Definition of Done + +After completing all Phase 4 tasks, a player can: + +1. ✅ Start a new game +2. ✅ Enter a combat room with different layouts +3. ✅ See 3 types of enemies (Grunt, Archer, Tank) +4. ✅ Take turns with enemies (initiative based on SPD) +5. ✅ Move and attack using AP +6. ✅ Defeat enemies or be defeated +7. ✅ See HP/AP on HUD +8. ✅ Make moral choices (spare/execute) +9. ✅ Configure settings + +**This is a Minimum Viable Gameplay loop!** + +--- + +## Current Sprint Status: Sprint 1 + +**Week:** 1-2 +**Focus:** Combat Input + Enemy AI +**Parallel Work:** +- Agent 1: Task 4.1 (Combat Input) +- Agent 2: Task 4.2 (Enemy AI) +- Agent 3: Task 4.7 (Settings Menu) - *Independent task* + +**Next Milestone:** Turn Manager (Task 4.3) - Requires both 4.1 and 4.2 complete + +--- + +## Resource Allocation + +### Recommended Agent Distribution + +| Agent Role | Current Task | Next Task | Notes | +|------------|--------------|-----------|-------| +| **Gameplay Programmer** | 4.1 Combat Input | 4.3 Turn Manager | Core combat loop | +| **AI Programmer** | 4.2 Enemy AI | 4.5 Enemy Variety | AI + Content | +| **UI Programmer** | 4.7 Settings Menu | 4.6 Combat HUD | UI work | +| **Level Designer** | - | 4.4 Room Loading | Wait for 4.3 | + +--- + +## Dependency Graph + +``` +Phase 4A (Sprint 1-2): +├─ 4.1 Combat Input ──┐ +│ ├─→ 4.3 Turn Manager ──┐ +└─ 4.2 Enemy AI ──────┘ ├─→ 4.6 Combat HUD + ├─→ 4.8 Moral Choice + └─→ 4.4 Room Loading + └─→ 4.5 Enemy Variety + +Independent: +└─ 4.7 Settings Menu (can start anytime) +``` + +--- + +## Risk Tracking + +| Risk | Impact | Likelihood | Mitigation | Status | +|------|--------|-----------|------------|--------| +| Turn Manager complexity | High | Medium | Simple state machine, test early | 🟡 Watching | +| Agent availability | Medium | Medium | Monitor heartbeats, have backup | 🟡 Watching | +| Scope creep | High | High | Strict sprint boundaries | 🟡 Watching | +| Integration bugs | Medium | Medium | Test after each merge | 🟡 Watching | + +--- + +## Phase 5 Backlog (Future) + +*Waiting for Phase 4 completion before planning* + +**Potential Areas:** +- More enemy types (Mage, Assassin, Support) +- Skills/Abilities system +- Status effects (burn, stun, poison) +- Equipment/Loot +- Advanced AI (behavior trees) +- Narrative integration +- Audio/SFX +- Particle effects + +--- + +## How to Use This Board + +**For Jules (Developer):** +1. Pick a task with ⏳ "Ready" status +2. Check "Blocked By" - ensure dependencies are done +3. Check "Can Parallel With" - coordinate with other agents +4. Update status to 🔄 "In Progress" when starting +5. Move to ✅ "Done" after PR merged + +**For You (Director):** +1. Review board weekly +2. Check critical path (4.1 → 4.2 → 4.3) +3. Reprioritize if blockers arise +4. Plan next sprint when current one completes + +--- + +## Updates + +| Date | Changes | +|------|---------| +| 2026-06-03 | Created board with Phase 1-4 tasks | +| | Phase 1-3 marked complete | +| | Phase 4 tasks with dependencies mapped | + +--- + +*Last Updated: 2026-06-03* +*Next Review: After Sprint 1 completion (Week 2)* From c7be79ab7089a31c4b4ba3f7e733d24237392321 Mon Sep 17 00:00:00 2001 From: Niyaz <9331133+niyazmft@users.noreply.github.com> Date: Wed, 3 Jun 2026 23:01:58 +0300 Subject: [PATCH 2/5] style(docs): apply markdownlint fixes --- .github/issue_4_2_update.md | 99 +++++++ .github/issue_4_3_update.md | 95 +++++++ .github/issue_4_4_update.md | 124 +++++++++ .github/issue_4_5_update.md | 127 +++++++++ .github/issue_4_6_update.md | 115 ++++++++ .github/issue_4_7_update.md | 102 +++++++ .github/issue_4_8_update.md | 126 +++++++++ PHASE4_PROJECT_PLAN.md | 523 ++++++++++++++++++++++++++++++++++++ PROJECT_BOARD.md | 7 +- 9 files changed, 1317 insertions(+), 1 deletion(-) create mode 100644 .github/issue_4_2_update.md create mode 100644 .github/issue_4_3_update.md create mode 100644 .github/issue_4_4_update.md create mode 100644 .github/issue_4_5_update.md create mode 100644 .github/issue_4_6_update.md create mode 100644 .github/issue_4_7_update.md create mode 100644 .github/issue_4_8_update.md create mode 100644 PHASE4_PROJECT_PLAN.md diff --git a/.github/issue_4_2_update.md b/.github/issue_4_2_update.md new file mode 100644 index 0000000..022d4a9 --- /dev/null +++ b/.github/issue_4_2_update.md @@ -0,0 +1,99 @@ +## Task 4.2: Basic Enemy AI + +**Phase:** 4A - Combat Core +**Priority:** CRITICAL +**Duration:** 4-5 days +**Sprint:** Sprint 1 (Weeks 1-2) + +--- + +## Objective +Implement basic enemy AI that can move toward the player and attack when in range. + +### Current State +- `simple_ai.gd` exists but returns `{"type": "wait"}` for everything +- BaseEnemy has `take_turn()` method but doesn't do anything useful +- Enemies just stand still + +--- + +## Execution Order + +**Can Start Immediately:** ✅ +**Blocked by:** Nothing +**Blocks:** Task 4.3 (Turn Manager - needs AI for enemy turns), Task 4.5 (Enemy Variety - extends AI) + +--- + +## Implementation Details + +**Update File:** `scripts/ai/simple_ai.gd` + +```gdscript +class_name EnemyAIController +extends Node + +@export var enemy_entity: Entity +@export var grid_system: _GridSystem +@export var player_entity: Entity + +enum BehaviorType { GRUNT, ARCHER, TANK } +@export var behavior: BehaviorType = BehaviorType.GRUNT + +func decide_action() -> void: + var player_dist := _grid_distance(enemy_entity, player_entity) + + match behavior: + BehaviorType.GRUNT: + return _grunt_behavior(player_dist) + BehaviorType.ARCHER: + return _archer_behavior(player_dist) + BehaviorType.TANK: + return _tank_behavior(player_dist) + + return {"type": "wait"} +``` + +### AI Behaviors +1. **Grunt (Melee)** - Rush player, attack when adjacent +2. **Archer (Ranged)** - Maintain 2-3 tile distance +3. **Tank (Slow Heavy)** - Slow advance, heavy damage + +### Pathfinding +Use GridSystem.can_move() for simple pathfinding. + +--- + +## Acceptance Criteria +- [ ] Grunts move toward player when out of range +- [ ] Grunts attack when adjacent to player +- [ ] Archers maintain 2-3 tile distance +- [ ] Archers move away when player gets too close +- [ ] Tanks move slowly (1 tile per turn) +- [ ] Tanks have high damage +- [ ] Enemies respect grid boundaries +- [ ] Enemies avoid blocked tiles +- [ ] Enemies don't move into other enemies +- [ ] AI runs during enemy turn only +- [ ] Test file: `tests/test_enemy_ai.gd` + +--- + +## Dependencies +- **None** - Can be developed in parallel with Task 4.1 +- **Uses existing:** GridSystem, CombatFormula, EntityLifecycle + +## Parallel Work +- **Can be done in parallel with:** Task 4.1 (Combat Input) +- **Best completed before:** Task 4.3 (Turn Manager needs enemy actions) +- **Shares no files with:** Task 4.1 + +--- + +## References +- `scripts/autoload/grid_system.gd` - Grid movement +- `scripts/core/combat_formula.gd` - Damage calculation +- `scripts/entities/entity_lifecycle.gd` - Damage application + +## Notes for Jules +Keep AI simple - no A* pathfinding needed. Use direct movement toward player with GridSystem.can_move() checks. diff --git a/.github/issue_4_3_update.md b/.github/issue_4_3_update.md new file mode 100644 index 0000000..1a498af --- /dev/null +++ b/.github/issue_4_3_update.md @@ -0,0 +1,95 @@ +## Task 4.3: Turn Manager + +**Phase:** 4A - Combat Core +**Priority:** CRITICAL +**Duration:** 5-7 days +**Sprint:** Sprint 2 (Weeks 3-4) + +--- + +## Objective +Implement the turn-based combat system with initiative order, AP economy, and round management. + +### Current State +- Real-time movement only (no turns) +- AP exists in Entity but isn't consumed +- No concept of rounds or initiative +- CombatRoom is just a sandbox + +--- + +## Execution Order + +**Can Start:** After Tasks 4.1 and 4.2 are functional +**Blocked by:** Task 4.1 (Combat Input), Task 4.2 (Enemy AI) +**Blocks:** Task 4.6 (Combat HUD), Task 4.8 (Moral Choice) + +--- + +## Implementation Details + +**New File:** `scripts/combat/turn_manager.gd` + +```gdscript +class_name TurnManager +extends Node + +signal turn_started(entity: Entity, is_player: bool) +signal turn_ended(entity: Entity) +signal round_started(round_number: int) +signal combat_ended(victory: bool) +``` + +### Core Mechanics +1. **Initiative Order** - Sort by SPD (descending) +2. **Turn Flow** - Player manual, Enemy AI auto +3. **AP Economy** - Regen at turn start, consume for actions +4. **Combat End** - Victory/defeat conditions + +### State Machine +``` +COMBAT_START + ├── INITIATIVE_PHASE + └── TURN_LOOP + ├── PLAYER_TURN (manual input) + ├── ENEMY_TURN (AI auto-execute) + └── CHECK_END_CONDITIONS +``` + +--- + +## Acceptance Criteria +- [ ] Combat starts when entering room +- [ ] Initiative calculated by SPD (highest first) +- [ ] Player gets manual control on their turn +- [ ] Enemies act automatically on their turn +- [ ] AP consumed for actions (move/attack) +- [ ] AP regenerated at start of each turn +- [ ] Unused AP carries over (capped) +- [ ] Turn order indicator visible +- [ ] Combat ends when all enemies defeated +- [ ] Combat ends when player defeated +- [ ] Victory/defeat signals emitted +- [ ] Test file: `tests/test_turn_manager.gd` + +--- + +## Dependencies +- **Task 4.1** (Combat Input) - For player actions +- **Task 4.2** (Enemy AI) - For enemy actions +- **Uses existing:** Entity, CombatFormula, EntityLifecycle + +## Parallel Work +- **Must be done AFTER:** Tasks 4.1 and 4.2 +- **Can be done in parallel with:** Nothing (needs both 4.1 and 4.2) +- **Blocks:** Task 4.6 (Combat HUD), Task 4.8 (Moral Choice) + +--- + +## References +- `scripts/entities/entity.gd` - Entity stats (SPD, AP) +- `scripts/core/combat_formula.gd` - Damage calculation +- `scripts/entities/entity_lifecycle.gd` - State changes + +## Notes for Jules +This is the core loop. Test thoroughly with 1 player + 3 enemies. Use signals for loose coupling with UI. diff --git a/.github/issue_4_4_update.md b/.github/issue_4_4_update.md new file mode 100644 index 0000000..526fba9 --- /dev/null +++ b/.github/issue_4_4_update.md @@ -0,0 +1,124 @@ +## Task 4.4: Room Loading System + +**Phase:** 4B - Content Foundation +**Priority:** HIGH +**Duration:** 4-5 days +**Sprint:** Sprint 3 (Weeks 5-6) + +--- + +## Objective +Create a room loading system that reads room definitions from JSON and configures the grid, elevation, cover, and enemy encounters. + +### Current State +- Room generation is stubbed in RunManager +- Seeds are generated but no actual room layouts exist +- GridSystem can load rooms from JSON but no room JSON files exist +- CombatRoom has hardcoded enemy spawns + +--- + +## Execution Order + +**Can Start:** After Task 4.3 (Turn Manager) is functional +**Blocked by:** Task 4.3 (needs combat loop working first) +**Blocks:** None (content task, doesn't block others) + +--- + +## Implementation Details + +**1. Create Room JSON Files** + +```json +// config/rooms/room_standard_01.json +{ + "id": "room_standard_01", + "biome": "sanctum", + "size": {"width": 12, "height": 12}, + "layout": { + "elevation": [...], + "cover": [...], + "blocked": [...] + }, + "encounters": [ + { + "enemy_type": "grunt", + "count": 3, + "positions": [...] + } + ], + "player_start": {"x": 1, "y": 1} +} +``` + +**2. Create Room Loader** + +```gdscript +# scripts/combat/room_loader.gd +class_name RoomLoader +extends Node + +const ROOMS_PATH := "res://config/rooms/" + +func load_room(room_id: String) -> Dictionary: + var path := ROOMS_PATH + room_id + ".json" + # Read and parse JSON + return json.data + +func configure_grid(room_data: Dictionary) -> void: + GridSystem.load_room(room_data) + +func spawn_encounters(room_data: Dictionary, entity_container: Node) -> void: + # Spawn enemies at defined positions +``` + +**3. Hook into RunManager** + +```gdscript +# In RunManager._enter_room(): +func _enter_room(_ctx: Dictionary) -> void: + var room_data := _get_current_room_data() + var room_id: String = room_data.get("room_id", "room_standard_01") + var room_def := RoomLoader.load_room(room_id) + + if not room_def.is_empty(): + RoomLoader.configure_grid(room_def) + RoomLoader.spawn_encounters(room_def, _entity_container) + _spawn_player(room_def.get("player_start", {"x": 1, "y": 1})) +``` + +--- + +## Acceptance Criteria +- [ ] 3-5 room JSON files created +- [ ] Room loader reads and parses JSON +- [ ] Grid configured from room definition (elevation, cover, blocked) +- [ ] Enemies spawned at defined positions +- [ ] Player spawned at defined start position +- [ ] Rooms have different layouts +- [ ] Rooms have different enemy encounters +- [ ] Hooked into RunManager room flow +- [ ] Test file: `tests/test_room_loader.gd` + +--- + +## Dependencies +- **Task 4.3** (Turn Manager) - Good to have combat first +- **Task 4.5** (Enemy Variety) - For different enemy types in rooms +- **Uses existing:** GridSystem.load_room(), BaseEnemy scenes + +## Parallel Work +- **Can be done in parallel with:** Task 4.5 (Enemy Variety) +- **Best completed after:** Task 4.3 +- **Shares files with:** Task 4.5 (enemy scenes) + +--- + +## References +- `scripts/autoload/grid_system.gd` - Grid configuration +- `scripts/state_machine/run_manager.gd` - Room flow +- `scripts/entities/base_enemy.gd` - Enemy spawning + +## Notes for Jules +Create 3-5 simple room layouts first. Focus on different elevation and cover configurations. diff --git a/.github/issue_4_5_update.md b/.github/issue_4_5_update.md new file mode 100644 index 0000000..30f4053 --- /dev/null +++ b/.github/issue_4_5_update.md @@ -0,0 +1,127 @@ +## Task 4.5: Enemy Variety + +**Phase:** 4B - Content Foundation +**Priority:** HIGH +**Duration:** 3-4 days +**Sprint:** Sprint 3 (Weeks 5-6) + +--- + +## Objective +Create 3 distinct enemy types with different stats, behaviors, and visuals. + +### Current State +- Only Grunt enemy exists +- All enemies use the same base class with no variation +- AI is a single file with enum-based behavior switching + +--- + +## Execution Order + +**Can Start:** After Task 4.2 (Basic Enemy AI) is functional +**Blocked by:** Task 4.2 (needs AI behaviors defined) +**Blocks:** Task 4.4 (Room Loading - needs enemy scenes to spawn) + +--- + +## Implementation Details + +**1. Create Enemy Subclasses** + +```gdscript +# scripts/entities/enemies/enemy_grunt.gd +class_name EnemyGrunt +extends BaseEnemy + +func _setup_entity() -> void: + entity = Entity.new("Grunt", x, y, 30, 8, 4) + entity.is_player = false + entity.spd = 4 + +func _setup_ai() -> void: + ai_controller.behavior = EnemyAIController.BehaviorType.GRUNT +``` + +```gdscript +# scripts/entities/enemies/enemy_archer.gd +class_name EnemyArcher +extends BaseEnemy + +func _setup_entity() -> void: + entity = Entity.new("Archer", x, y, 25, 6, 2) + entity.is_player = false + entity.spd = 5 + +func _setup_ai() -> void: + ai_controller.behavior = EnemyAIController.BehaviorType.ARCHER +``` + +```gdscript +# scripts/entities/enemies/enemy_tank.gd +class_name EnemyTank +extends BaseEnemy + +func _setup_entity() -> void: + entity = Entity.new("Tank", x, y, 60, 15, 8) + entity.is_player = false + entity.spd = 2 + +func _setup_ai() -> void: + ai_controller.behavior = EnemyAIController.BehaviorType.TANK +``` + +**2. Update AI Controller** + +```gdscript +# In EnemyAIController: +func _archer_behavior(dist: int) -> Dictionary: + if dist >= 2 and dist <= 3: + return {"type": "attack", "target": player_entity} + elif dist < 2: + var away_dir := _get_move_away_from_player() + return {"type": "move", "dx": away_dir.x, "dy": away_dir.y} + else: + var toward_dir := _get_move_toward_player() + return {"type": "move", "dx": toward_dir.x, "dy": toward_dir.y} +``` + +### Enemy Stats + +| Type | HP | OFF | DEF | SPD | Range | Behavior | +|------|----|-----|-----|-----|-------|----------| +| Grunt | 30 | 8 | 4 | 4 | Melee (1) | Rush player | +| Archer | 25 | 6 | 2 | 5 | Ranged (2-3) | Maintain distance | +| Tank | 60 | 15 | 8 | 2 | Melee (1) | Slow advance | + +--- + +## Acceptance Criteria +- [ ] 3 enemy classes created (Grunt, Archer, Tank) +- [ ] Different stats for each type +- [ ] Different AI behaviors +- [ ] Scene files for each type +- [ ] Visual differentiation (color/size) +- [ ] Spawnable via RoomLoader +- [ ] Each type has unique tactical role +- [ ] Test file: `tests/test_enemy_variety.gd` + +--- + +## Dependencies +- **Task 4.2** (Enemy AI) - For behavior variations +- **Task 4.4** (Room Loading) - For spawning + +## Parallel Work +- **Can be done in parallel with:** Task 4.4 (Room Loading) +- **Best completed after:** Task 4.2 +- **Shares files with:** Task 4.4 (enemy scenes) + +--- + +## References +- `scripts/entities/base_enemy.gd` - Base class +- `scripts/ai/enemy_ai_controller.gd` - AI behaviors + +## Notes for Jules +Create scenes with different colored rectangles for now: Grunt=white, Archer=green, Tank=blue. diff --git a/.github/issue_4_6_update.md b/.github/issue_4_6_update.md new file mode 100644 index 0000000..71e9e4c --- /dev/null +++ b/.github/issue_4_6_update.md @@ -0,0 +1,115 @@ +## Task 4.6: Combat HUD + +**Phase:** 4C - Polish +**Priority:** MEDIUM +**Duration:** 3-4 days +**Sprint:** Sprint 5 (Weeks 7-8) + +--- + +## Objective +Create a functional combat HUD that displays HP, AP, turn information, and action buttons. + +### Current State +- CombatHUD scene exists but is not functional +- No HP/AP display during combat +- No action buttons visible +- No turn indicator + +--- + +## Execution Order + +**Can Start:** After Task 4.3 (Turn Manager) is functional +**Blocked by:** Task 4.3 (needs turn state), Task 4.1 (needs attack action) +**Blocks:** None (UI only) + +--- + +## Implementation Details + +**1. HP/AP Display** + +```gdscript +# scripts/ui/combat_hud.gd (update existing) +@onready var hp_bar: ProgressBar = %HPBar +@onready var hp_label: Label = %HPLabel +@onready var ap_bar: ProgressBar = %APBar +@onready var ap_label: Label = %APLabel + +func update_player_stats(entity: Entity) -> void: + hp_bar.max_value = entity.hp_max + hp_bar.value = entity.hp + hp_label.text = "%d / %d" % [entity.hp, entity.hp_max] + + ap_bar.max_value = GameConstants.AP_MAX + ap_bar.value = entity.ap + ap_label.text = "%d / %d" % [entity.ap, GameConstants.AP_MAX] +``` + +**2. Action Buttons** + +```gdscript +@onready var move_button: Button = %MoveButton +@onready var attack_button: Button = %AttackButton +@onready var end_turn_button: Button = %EndTurnButton + +func _on_attack_pressed() -> void: + combat_input._enter_targeting_mode() + +func _on_end_turn_pressed() -> void: + turn_manager.end_turn() +``` + +**3. Turn Indicator** + +```gdscript +@onready var turn_label: Label = %TurnLabel +@onready var round_label: Label = %RoundLabel + +func show_player_turn() -> void: + turn_label.text = "Your Turn" + turn_label.modulate = Color.GREEN + enable_action_buttons() + +func show_enemy_turn() -> void: + turn_label.text = "Enemy Turn" + turn_label.modulate = Color.RED + disable_action_buttons() +``` + +--- + +## Acceptance Criteria +- [ ] HP bar shows current/max HP +- [ ] AP bar shows current/max AP +- [ ] Action buttons visible and functional +- [ ] Turn indicator shows whose turn +- [ ] Round counter visible +- [ ] Target info appears on hover +- [ ] Combat log shows actions +- [ ] Disabled state when not player's turn +- [ ] Updates dynamically during combat +- [ ] Test file: `tests/test_combat_hud.gd` + +--- + +## Dependencies +- **Task 4.1** (Combat Input) - For attack button +- **Task 4.3** (Turn Manager) - For turn display +- **Uses existing:** CombatHUD scene + +## Parallel Work +- **Can be done in parallel with:** Tasks 4.7, 4.8 +- **Best completed after:** Task 4.3 +- **Shares no critical files with:** Tasks 4.7, 4.8 + +--- + +## References +- `scripts/ui/combat_hud.gd` - Current implementation +- `scripts/combat/turn_manager.gd` - Turn signals +- `scripts/combat/combat_input.gd` - Input actions + +## Notes for Jules +This is UI-only work. Focus on layout and signals. No game logic changes needed. diff --git a/.github/issue_4_7_update.md b/.github/issue_4_7_update.md new file mode 100644 index 0000000..4d18341 --- /dev/null +++ b/.github/issue_4_7_update.md @@ -0,0 +1,102 @@ +## Task 4.7: Settings Menu + +**Phase:** 4C - Polish +**Priority:** MEDIUM +**Duration:** 2-3 days +**Sprint:** Sprint 5 (Weeks 7-8) + +--- + +## Objective +Make the Settings menu functional with resolution, audio, and key binding options. + +### Current State +- Settings menu scene exists (`scenes/ui/settings_menu.tscn`) +- Settings button on title screen does nothing +- SettingsManager autoload exists but is basic +- No actual settings are persisted or applied + +--- + +## Execution Order + +**Can Start:** Immediately +**Blocked by:** Nothing (UI only, independent of combat) +**Blocks:** None + +--- + +## Implementation Details + +**1. Settings Categories** + +```gdscript +# scripts/autoload/settings_manager.gd (update) +enum SettingCategory { + DISPLAY, + AUDIO, + INPUT, + ACCESSIBILITY +} +``` + +**2. Display Settings** +- Resolution dropdown (1920x1080, 1600x900, 1280x720) +- Fullscreen toggle +- VSync toggle +- Apply button (requires restart) + +**3. Audio Settings** +- Master volume slider (0-100%) +- Music volume slider +- SFX volume slider +- Mute toggle + +**4. Input Settings** +- Show current key bindings +- Click to rebind (capture next keypress) +- Reset to defaults button + +**5. Settings Persistence** +```gdscript +func save_settings() -> void: + var file := FileAccess.open("user://settings.json", FileAccess.WRITE) + if file: + file.store_string(JSON.stringify(_settings)) + file.close() +``` + +--- + +## Acceptance Criteria +- [ ] Settings accessible from title screen +- [ ] Settings accessible from pause menu +- [ ] Resolution options work +- [ ] Fullscreen toggle works +- [ ] Volume sliders work +- [ ] Key rebinding works +- [ ] Settings persist between sessions +- [ ] Reset to defaults works +- [ ] Apply button confirms changes +- [ ] Back button returns to previous screen +- [ ] Test file: `tests/test_settings_menu.gd` + +--- + +## Dependencies +- **None** - UI only, independent of combat + +## Parallel Work +- **Can be done in parallel with:** Tasks 4.6, 4.8 +- **Best completed:** Anytime (no dependencies) +- **Shares no files with:** Combat tasks + +--- + +## References +- `scripts/autoload/settings_manager.gd` - Current implementation +- `scripts/ui/settings_menu.gd` - Menu script +- `scenes/ui/settings_menu.tscn` - Menu scene + +## Notes for Jules +This is UI-only work. Can be done at any time. Focus on user:// persistence for settings. diff --git a/.github/issue_4_8_update.md b/.github/issue_4_8_update.md new file mode 100644 index 0000000..e7ed96c --- /dev/null +++ b/.github/issue_4_8_update.md @@ -0,0 +1,126 @@ +## Task 4.8: Moral Choice UI + +**Phase:** 4C - Polish +**Priority:** MEDIUM +**Duration:** 2-3 days +**Sprint:** Sprint 5 (Weeks 7-8) + +--- + +## Objective +Create the UI for moral choices (spare vs execute) when an enemy reaches the DYING state. + +### Current State +- EntityLifecycle has `spare_entity()` and `execute_entity()` methods +- Moral delta system works (queued and processed) +- Burden system tracks kills +- **NO UI exists** - Choices are invisible to player + +--- + +## Execution Order + +**Can Start:** After Task 4.3 (Turn Manager) is functional +**Blocked by:** Task 4.3 (needs turn flow), Task 4.1 (needs combat to reach DYING state) +**Blocks:** None (UI only) + +--- + +## Implementation Details + +**1. Choice Popup** + +```gdscript +# scripts/ui/moral_choice_ui.gd +class_name MoralChoiceUI +extends CanvasLayer + +signal choice_made(spared: bool) + +@onready var panel: PanelContainer = %ChoicePanel +@onready var enemy_name_label: Label = %EnemyNameLabel +@onready var spare_button: Button = %SpareButton +@onready var execute_button: Button = %ExecuteButton +@onready var timeout_bar: ProgressBar = %TimeoutBar +@onready var moral_preview: Label = %MoralPreview + +var _target_entity: Entity = null +var _timeout: float = 5.0 +var _remaining: float = 0.0 +``` + +**2. Show Choice** + +```gdscript +func show_choice(enemy: Entity) -> void: + _target_entity = enemy + panel.visible = true + + enemy_name_label.text = "%s is dying..." % enemy.entity_name + moral_preview.text = "Spare: -1 | Execute: +1" + + spare_button.text = "Spare (Cost: 1 AP)" + execute_button.text = "Execute" + + _remaining = _timeout + timeout_bar.max_value = _timeout + timeout_bar.value = _timeout +``` + +**3. Handle Choices** + +```gdscript +func _on_spare_pressed() -> void: + var player := _get_player_entity() + if player and player.ap >= 1: + EntityLifecycle.spare_entity(player, _target_entity) + choice_made.emit(true) + _hide_choice() + +func _on_execute_pressed() -> void: + EntityLifecycle.execute_entity(_target_entity) + choice_made.emit(false) + _hide_choice() + +func _auto_execute() -> void: + if _target_entity and _target_entity.state == Entity.State.DYING: + EntityLifecycle.execute_entity(_target_entity) + choice_made.emit(false) + _hide_choice() +``` + +--- + +## Acceptance Criteria +- [ ] Choice appears when enemy reaches 0 HP +- [ ] Shows enemy name +- [ ] Shows moral consequences +- [ ] Spare button visible (disabled if no AP) +- [ ] Execute button visible +- [ ] 5-second timeout with progress bar +- [ ] Auto-executes if no choice made +- [ ] Spare: Consumes 1 AP, -1 moral, enemy becomes GHOST +- [ ] Execute: Free, +1 moral, enemy becomes DEAD +- [ ] Visual feedback on choice +- [ ] Test file: `tests/test_moral_choice_ui.gd` + +--- + +## Dependencies +- **Task 4.1** (Combat Input) - For triggering DYING state +- **Task 4.3** (Turn Manager) - For turn flow +- **Uses existing:** EntityLifecycle.spare_entity/execute_entity + +## Parallel Work +- **Can be done in parallel with:** Tasks 4.6, 4.7 +- **Best completed after:** Task 4.3 +- **Shares no critical files with:** Tasks 4.6, 4.7 + +--- + +## References +- `scripts/entities/entity_lifecycle.gd` - Spare/execute logic +- `scripts/entities/entity.gd` - Entity states + +## Notes for Jules +This is UI-only work. Hook into entity state changes. Timeout prevents soft-locking. diff --git a/PHASE4_PROJECT_PLAN.md b/PHASE4_PROJECT_PLAN.md new file mode 100644 index 0000000..3ea3f75 --- /dev/null +++ b/PHASE4_PROJECT_PLAN.md @@ -0,0 +1,523 @@ +# Phase 4 Project Plan: Minimum Viable Gameplay + +## Overview + +**Timeline:** 8 weeks (sprint-based) +**Goal:** Transform Emberfall from tech demo into playable tactical combat game +**Sprint Structure:** 2-week sprints + +--- + +## Phase 4A: Combat Core (Sprints 1-2) + +**Weeks 1-3 | Priority: CRITICAL** + +### Task 4.1: Combat Input System + +**Duration:** 3-4 days +**Dependencies:** None (uses existing Entity + CombatFormula) + +**Implementation:** + +```gdscript +# scripts/combat/combat_input.gd +class_name CombatInput +extends Node + +## Handles player combat input: targeting, attacking, abilities + +@export var player_entity: Entity +@export var grid_system: _GridSystem +@export var combat_system: CombatSystem + +var _selected_target: BaseEnemy = null +var _in_targeting_mode: bool = false +var _valid_targets: Array[BaseEnemy] = [] + +func _input(event: InputEvent) -> void: + if event.is_action_pressed("attack"): + _enter_targeting_mode() + elif event.is_action_pressed("confirm") and _in_targeting_mode: + _execute_attack() + elif event.is_action_pressed("cancel") and _in_targeting_mode: + _exit_targeting_mode() + +func _enter_targeting_mode() -> void: + _in_targeting_mode = true + _valid_targets = _find_valid_targets() + _highlight_targets(_valid_targets) + _selected_target = _get_nearest_target() + _update_target_highlight() + +func _find_valid_targets() -> Array[BaseEnemy]: + ## Find enemies within attack range (adjacent tiles) + var targets: Array[BaseEnemy] = [] + var enemies := get_tree().get_nodes_in_group("enemies") + for enemy in enemies: + if enemy is BaseEnemy and enemy.alive(): + var dist := _grid_distance(player_entity, enemy.entity) + if dist <= 1: # Melee range + targets.append(enemy) + return targets + +func _execute_attack() -> void: + if _selected_target == null: + return + + combat_system.execute_attack(player_entity, _selected_target.entity) + _exit_targeting_mode() +``` + +**Acceptance Criteria:** + +- [ ] Press Space or click to enter targeting mode +- [ ] Valid targets highlighted (adjacent enemies) +- [ ] Tab or arrows to cycle targets +- [ ] Enter or click to confirm attack +- [ ] Damage dealt using CombatFormula +- [ ] Visual feedback (flash, shake) +- [ ] Escape to cancel targeting + +--- + +### Task 4.2: Basic Enemy AI + +**Duration:** 4-5 days +**Dependencies:** Task 4.1 (combat input exists) + +**Implementation:** + +```gdscript +# scripts/ai/enemy_ai_controller.gd +class_name EnemyAIController +extends Node + +## Basic AI for enemies: move toward player, attack in range + +@export var enemy_entity: Entity +@export var grid_system: _GridSystem +@export var player_entity: Entity + +enum BehaviorType { GRUNT, ARCHER, TANK } +@export var behavior: BehaviorType = BehaviorType.GRUNT + +func decide_action() -> Dictionary: + var player_dist := _grid_distance(enemy_entity, player_entity) + + match behavior: + BehaviorType.GRUNT: + return _grunt_behavior(player_dist) + BehaviorType.ARCHER: + return _archer_behavior(player_dist) + BehaviorType.TANK: + return _tank_behavior(player_dist) + + return {"type": "wait"} + +func _grunt_behavior(dist: int) -> Dictionary: + if dist <= 1: + return {"type": "attack", "target": player_entity} + else: + var move_dir := _get_move_toward_player() + return {"type": "move", "dx": move_dir.x, "dy": move_dir.y} + +func _get_move_toward_player() -> Vector2i: + var dx := sign(player_entity.x - enemy_entity.x) + var dy := sign(player_entity.y - enemy_entity.y) + + # Try direct path first + if grid_system.can_move(enemy_entity.x, enemy_entity.y, + enemy_entity.x + dx, enemy_entity.y + dy): + return Vector2i(dx, dy) + + # Try alternate paths + if dx != 0 and grid_system.can_move(enemy_entity.x, enemy_entity.y, + enemy_entity.x + dx, enemy_entity.y): + return Vector2i(dx, 0) + + if dy != 0 and grid_system.can_move(enemy_entity.x, enemy_entity.y, + enemy_entity.x, enemy_entity.y + dy): + return Vector2i(0, dy) + + return Vector2i.ZERO +``` + +**Acceptance Criteria:** + +- [ ] Grunts move toward player when out of range +- [ ] Grunts attack when adjacent +- [ ] Archers maintain distance (2-3 tiles) +- [ ] Tanks move slowly but have high HP +- [ ] Enemies respect grid boundaries +- [ ] Enemies avoid blocked tiles +- [ ] AI runs during enemy turn + +--- + +### Task 4.3: Turn Manager + +**Duration:** 5-7 days +**Dependencies:** Tasks 4.1, 4.2 + +**Implementation:** + +```gdscript +# scripts/combat/turn_manager.gd +class_name TurnManager +extends Node + +## Manages turn order, initiative, AP economy + +signal turn_started(entity: Entity, is_player: bool) +signal turn_ended(entity: Entity) +signal round_started(round_number: int) +signal combat_ended(victory: bool) + +var _participants: Array[Entity] = [] +var _current_index: int = 0 +var _round_number: int = 0 +var _combat_active: bool = false + +func start_combat(player: Entity, enemies: Array[BaseEnemy]) -> void: + _participants.clear() + _participants.append(player) + for enemy in enemies: + _participants.append(enemy.entity) + + # Sort by SPD (descending) for initiative + _participants.sort_custom( + func(a: Entity, b: Entity) -> bool: + return a.spd > b.spd + ) + + _current_index = 0 + _round_number = 1 + _combat_active = true + + round_started.emit(_round_number) + _start_turn() + +func _start_turn() -> void: + if not _combat_active: + return + + var entity := _participants[_current_index] + + # Reset AP at start of turn + if entity.is_player: + entity.ap = DeterministicMath.ap_start(entity.ap, GameConstants.AP_REGEN, GameConstants.AP_MAX) + else: + entity.ap = GameConstants.AP_MAX # Enemies get full AP + + turn_started.emit(entity, entity.is_player) + + if not entity.is_player: + # Auto-run enemy AI after delay + await get_tree().create_timer(0.5).timeout + _execute_enemy_turn(entity) + +func _execute_enemy_turn(entity: Entity) -> void: + var enemy := _get_enemy_node(entity) + if enemy: + var action := enemy.ai_controller.decide_action() + _execute_action(entity, action) + end_turn() + +func _execute_action(entity: Entity, action: Dictionary) -> void: + match action.get("type", ""): + "move": + var dx: int = action.get("dx", 0) + var dy: int = action.get("dy", 0) + if entity.ap >= 1: + entity.set_grid_position(entity.x + dx, entity.y + dy) + entity.ap -= 1 + "attack": + var target: Entity = action.get("target") + if entity.ap >= 2 and target != null: + var damage := CombatFormula.compute_damage_from_entities(entity, target, []) + EntityLifecycle.apply_damage(entity, target, damage) + entity.ap -= 2 + +func end_turn() -> void: + var entity := _participants[_current_index] + turn_ended.emit(entity) + + _current_index += 1 + if _current_index >= _participants.size(): + _current_index = 0 + _round_number += 1 + round_started.emit(_round_number) + + _check_combat_end() + if _combat_active: + _start_turn() + +func _check_combat_end() -> void: + var player_alive := false + var enemies_alive := false + + for entity in _participants: + if entity.is_player and entity.alive(): + player_alive = true + elif not entity.is_player and entity.alive(): + enemies_alive = true + + if not player_alive: + _combat_active = false + combat_ended.emit(false) # Defeat + elif not enemies_alive: + _combat_active = false + combat_ended.emit(true) # Victory + +func _get_enemy_node(entity: Entity) -> BaseEnemy: + var enemies := get_tree().get_nodes_in_group("enemies") + for enemy in enemies: + if enemy is BaseEnemy and enemy.entity == entity: + return enemy + return null +``` + +**Acceptance Criteria:** + +- [ ] Combat starts when entering room +- [ ] Turns ordered by SPD (highest first) +- [ ] Player turn: manual control (move/attack/end) +- [ ] Enemy turn: AI executes automatically +- [ ] AP consumed for actions +- [ ] AP regenerated at turn start +- [ ] Combat ends when all enemies or player defeated +- [ ] Victory/defeat signals emitted + +--- + +## Phase 4B: Content Foundation (Sprints 3-4) + +**Weeks 4-6 | Priority: HIGH** + +### Task 4.4: Room Loading System + +**Duration:** 4-5 days +**Dependencies:** Task 4.3 (turn manager) + +**Implementation:** + +```json +// config/rooms/room_standard.json +{ + "id": "room_standard_01", + "biome": "sanctum", + "layout": { + "width": 12, + "height": 12, + "elevation": [ + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,1,1,0,0,1,1,0,0,0], + [0,0,1,1,1,1,1,1,1,1,0,0], + [0,1,1,2,2,1,1,2,2,1,1,0], + [0,1,1,2,2,1,1,2,2,1,1,0], + [0,0,1,1,1,1,1,1,1,1,0,0], + [0,0,0,1,1,0,0,1,1,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0,0,0] + ], + "cover": [ + {"x": 3, "y": 3, "type": "heavy"}, + {"x": 8, "y": 3, "type": "heavy"}, + {"x": 5, "y": 5, "type": "light"} + ] + }, + "encounters": [ + { + "enemy_type": "grunt", + "count": 3, + "positions": [{"x": 9, "y": 2}, {"x": 10, "y": 3}, {"x": 9, "y": 4}] + } + ] +} +``` + +**Acceptance Criteria:** + +- [ ] 3-5 room JSON files created +- [ ] Room loader reads JSON and configures grid +- [ ] Elevation data applied to grid +- [ ] Cover tiles configured +- [ ] Encounter spawner places enemies +- [ ] Hooked into RunManager room flow + +--- + +### Task 4.5: Enemy Variety + +**Duration:** 3-4 days +**Dependencies:** Task 4.4 (room loading) + +**Implementation:** + +```gdscript +# scripts/entities/enemies/enemy_archer.gd +class_name EnemyArcher +extends BaseEnemy + +func _setup_entity() -> void: + entity = Entity.new("Archer", x, y, 25, 6, 2) + entity.is_player = false + entity.spd = 5 + +# scripts/entities/enemies/enemy_tank.gd +class_name EnemyTank +extends BaseEnemy + +func _setup_entity() -> void: + entity = Entity.new("Tank", x, y, 60, 15, 8) + entity.is_player = false + entity.spd = 2 +``` + +**Acceptance Criteria:** + +- [ ] 3 enemy types: Grunt, Archer, Tank +- [ ] Different stats for each type +- [ ] Different AI behaviors +- [ ] Scene files for each type +- [ ] Spawnable via room loader + +--- + +## Phase 4C: Polish (Sprints 5-6) + +**Weeks 7-8 | Priority: MEDIUM** + +### Task 4.6: Combat HUD + +**Duration:** 3-4 days +**Dependencies:** Task 4.3 (turn manager + AP system) + +**Features:** + +- HP/MP bars for player +- AP display (current/max) +- Action buttons: Move, Attack, End Turn +- Turn indicator ("Player Turn" / "Enemy Turn") +- Target info (hover enemy to see HP) + +**Acceptance Criteria:** + +- [ ] Player HP bar visible +- [ ] AP counter visible +- [ ] Action buttons work +- [ ] Turn indicator shows whose turn +- [ ] Enemy info on hover + +--- + +### Task 4.7: Settings Menu + +**Duration:** 2-3 days +**Dependencies:** None (UI only) + +**Features:** + +- Resolution settings +- Audio volume +- Key rebinding (already partially done) +- Accessibility options + +**Acceptance Criteria:** + +- [ ] Settings accessible from pause menu +- [ ] Settings persist between sessions +- [ ] Volume controls work +- [ ] Resolution options work + +--- + +### Task 4.8: Moral Choice UI + +**Duration:** 2-3 days +**Dependencies:** Task 4.1 (combat) + existing EntityLifecycle + +**Features:** + +- When enemy reaches DYING state, show choice popup +- Options: Spare (costs 1 AP, -1 moral) or Execute (free, +1 moral) +- Visual indicator of moral consequences +- Timer (enemy dies automatically after X turns if no choice) + +**Acceptance Criteria:** + +- [ ] Choice appears when enemy at 0 HP +- [ ] Spare button visible and functional +- [ ] Execute button visible and functional +- [ ] AP cost shown +- [ ] Moral delta preview shown +- [ ] Auto-execute after timeout + +--- + +## Sprint Schedule + +| Sprint | Weeks | Focus | Tasks | +|--------|-------|-------|-------| +| **Sprint 1** | 1-2 | Combat Core | 4.1 Combat Input, 4.2 Enemy AI | +| **Sprint 2** | 3-4 | Combat Core + Turn System | 4.3 Turn Manager | +| **Sprint 3** | 5-6 | Content Foundation | 4.4 Room Loading, 4.5 Enemy Variety | +| **Sprint 4** | 7-8 | Polish | 4.6 Combat HUD, 4.7 Settings, 4.8 Moral Choice | + +--- + +## Definition of Done (Phase 4) + +**A player can:** + +1. Start a new game +2. Enter a combat room +3. See enemies and environment +4. Take turns with enemies +5. Move and attack +6. Defeat enemies or be defeated +7. Make moral choices (spare/execute) +8. See HP/AP on HUD + +**This is a Minimum Viable Gameplay loop.** + +--- + +## Risk Mitigation + +| Risk | Likelihood | Impact | Mitigation | +|------|-----------|--------|------------| +| AI pathfinding complex | Medium | High | Use GridSystem.can_move(), not A* | +| Turn system bugs | Medium | High | Extensive testing, simple state machine | +| Scope creep | High | Medium | Strict sprint boundaries | +| Performance with many enemies | Low | Medium | Grid is small (12×12) | + +--- + +## Dependencies Summary + +``` +4.1 Combat Input + | + v +4.2 Enemy AI + | + v +4.3 Turn Manager + | + +---> 4.4 Room Loading + | | + | v + | 4.5 Enemy Variety + | + +---> 4.6 Combat HUD + | + +---> 4.8 Moral Choice UI +``` + +--- + +*Prepared for Donchitos CEO Agent | 2026-06-03* diff --git a/PROJECT_BOARD.md b/PROJECT_BOARD.md index a781d64..d0ea8c2 100644 --- a/PROJECT_BOARD.md +++ b/PROJECT_BOARD.md @@ -1,7 +1,7 @@ # Emberfall Development Roadmap - Project Board **Project:** Emberfall Tactical Combat Game -**Repository:** https://github.com/niyazmft/emberfall +**Repository:** **Last Updated:** 2026-06-03 **Current Phase:** Phase 4 (Minimum Viable Gameplay) @@ -11,6 +11,7 @@ This document serves as the project board tracking all development tasks across phases. **Status Legend:** + - ✅ **Done** - Completed and merged - 🔄 **In Progress** - Currently being worked on - ⏳ **Ready** - Can start immediately (no blockers) @@ -133,6 +134,7 @@ After completing all Phase 4 tasks, a player can: **Week:** 1-2 **Focus:** Combat Input + Enemy AI **Parallel Work:** + - Agent 1: Task 4.1 (Combat Input) - Agent 2: Task 4.2 (Enemy AI) - Agent 3: Task 4.7 (Settings Menu) - *Independent task* @@ -187,6 +189,7 @@ Independent: *Waiting for Phase 4 completion before planning* **Potential Areas:** + - More enemy types (Mage, Assassin, Support) - Skills/Abilities system - Status effects (burn, stun, poison) @@ -201,6 +204,7 @@ Independent: ## How to Use This Board **For Jules (Developer):** + 1. Pick a task with ⏳ "Ready" status 2. Check "Blocked By" - ensure dependencies are done 3. Check "Can Parallel With" - coordinate with other agents @@ -208,6 +212,7 @@ Independent: 5. Move to ✅ "Done" after PR merged **For You (Director):** + 1. Review board weekly 2. Check critical path (4.1 → 4.2 → 4.3) 3. Reprioritize if blockers arise From 2681a16e490687f74fcab1d7351d43c1428a1f57 Mon Sep 17 00:00:00 2001 From: Niyaz <9331133+niyazmft@users.noreply.github.com> Date: Wed, 3 Jun 2026 23:02:39 +0300 Subject: [PATCH 3/5] docs: fix markdownlint issues in project board --- .github/issue_4_2_update.md | 99 ---------------------------- .github/issue_4_3_update.md | 95 --------------------------- .github/issue_4_4_update.md | 124 ----------------------------------- .github/issue_4_5_update.md | 127 ------------------------------------ .github/issue_4_6_update.md | 115 -------------------------------- .github/issue_4_7_update.md | 102 ----------------------------- .github/issue_4_8_update.md | 126 ----------------------------------- PROJECT_BOARD.md | 6 +- 8 files changed, 4 insertions(+), 790 deletions(-) delete mode 100644 .github/issue_4_2_update.md delete mode 100644 .github/issue_4_3_update.md delete mode 100644 .github/issue_4_4_update.md delete mode 100644 .github/issue_4_5_update.md delete mode 100644 .github/issue_4_6_update.md delete mode 100644 .github/issue_4_7_update.md delete mode 100644 .github/issue_4_8_update.md diff --git a/.github/issue_4_2_update.md b/.github/issue_4_2_update.md deleted file mode 100644 index 022d4a9..0000000 --- a/.github/issue_4_2_update.md +++ /dev/null @@ -1,99 +0,0 @@ -## Task 4.2: Basic Enemy AI - -**Phase:** 4A - Combat Core -**Priority:** CRITICAL -**Duration:** 4-5 days -**Sprint:** Sprint 1 (Weeks 1-2) - ---- - -## Objective -Implement basic enemy AI that can move toward the player and attack when in range. - -### Current State -- `simple_ai.gd` exists but returns `{"type": "wait"}` for everything -- BaseEnemy has `take_turn()` method but doesn't do anything useful -- Enemies just stand still - ---- - -## Execution Order - -**Can Start Immediately:** ✅ -**Blocked by:** Nothing -**Blocks:** Task 4.3 (Turn Manager - needs AI for enemy turns), Task 4.5 (Enemy Variety - extends AI) - ---- - -## Implementation Details - -**Update File:** `scripts/ai/simple_ai.gd` - -```gdscript -class_name EnemyAIController -extends Node - -@export var enemy_entity: Entity -@export var grid_system: _GridSystem -@export var player_entity: Entity - -enum BehaviorType { GRUNT, ARCHER, TANK } -@export var behavior: BehaviorType = BehaviorType.GRUNT - -func decide_action() -> void: - var player_dist := _grid_distance(enemy_entity, player_entity) - - match behavior: - BehaviorType.GRUNT: - return _grunt_behavior(player_dist) - BehaviorType.ARCHER: - return _archer_behavior(player_dist) - BehaviorType.TANK: - return _tank_behavior(player_dist) - - return {"type": "wait"} -``` - -### AI Behaviors -1. **Grunt (Melee)** - Rush player, attack when adjacent -2. **Archer (Ranged)** - Maintain 2-3 tile distance -3. **Tank (Slow Heavy)** - Slow advance, heavy damage - -### Pathfinding -Use GridSystem.can_move() for simple pathfinding. - ---- - -## Acceptance Criteria -- [ ] Grunts move toward player when out of range -- [ ] Grunts attack when adjacent to player -- [ ] Archers maintain 2-3 tile distance -- [ ] Archers move away when player gets too close -- [ ] Tanks move slowly (1 tile per turn) -- [ ] Tanks have high damage -- [ ] Enemies respect grid boundaries -- [ ] Enemies avoid blocked tiles -- [ ] Enemies don't move into other enemies -- [ ] AI runs during enemy turn only -- [ ] Test file: `tests/test_enemy_ai.gd` - ---- - -## Dependencies -- **None** - Can be developed in parallel with Task 4.1 -- **Uses existing:** GridSystem, CombatFormula, EntityLifecycle - -## Parallel Work -- **Can be done in parallel with:** Task 4.1 (Combat Input) -- **Best completed before:** Task 4.3 (Turn Manager needs enemy actions) -- **Shares no files with:** Task 4.1 - ---- - -## References -- `scripts/autoload/grid_system.gd` - Grid movement -- `scripts/core/combat_formula.gd` - Damage calculation -- `scripts/entities/entity_lifecycle.gd` - Damage application - -## Notes for Jules -Keep AI simple - no A* pathfinding needed. Use direct movement toward player with GridSystem.can_move() checks. diff --git a/.github/issue_4_3_update.md b/.github/issue_4_3_update.md deleted file mode 100644 index 1a498af..0000000 --- a/.github/issue_4_3_update.md +++ /dev/null @@ -1,95 +0,0 @@ -## Task 4.3: Turn Manager - -**Phase:** 4A - Combat Core -**Priority:** CRITICAL -**Duration:** 5-7 days -**Sprint:** Sprint 2 (Weeks 3-4) - ---- - -## Objective -Implement the turn-based combat system with initiative order, AP economy, and round management. - -### Current State -- Real-time movement only (no turns) -- AP exists in Entity but isn't consumed -- No concept of rounds or initiative -- CombatRoom is just a sandbox - ---- - -## Execution Order - -**Can Start:** After Tasks 4.1 and 4.2 are functional -**Blocked by:** Task 4.1 (Combat Input), Task 4.2 (Enemy AI) -**Blocks:** Task 4.6 (Combat HUD), Task 4.8 (Moral Choice) - ---- - -## Implementation Details - -**New File:** `scripts/combat/turn_manager.gd` - -```gdscript -class_name TurnManager -extends Node - -signal turn_started(entity: Entity, is_player: bool) -signal turn_ended(entity: Entity) -signal round_started(round_number: int) -signal combat_ended(victory: bool) -``` - -### Core Mechanics -1. **Initiative Order** - Sort by SPD (descending) -2. **Turn Flow** - Player manual, Enemy AI auto -3. **AP Economy** - Regen at turn start, consume for actions -4. **Combat End** - Victory/defeat conditions - -### State Machine -``` -COMBAT_START - ├── INITIATIVE_PHASE - └── TURN_LOOP - ├── PLAYER_TURN (manual input) - ├── ENEMY_TURN (AI auto-execute) - └── CHECK_END_CONDITIONS -``` - ---- - -## Acceptance Criteria -- [ ] Combat starts when entering room -- [ ] Initiative calculated by SPD (highest first) -- [ ] Player gets manual control on their turn -- [ ] Enemies act automatically on their turn -- [ ] AP consumed for actions (move/attack) -- [ ] AP regenerated at start of each turn -- [ ] Unused AP carries over (capped) -- [ ] Turn order indicator visible -- [ ] Combat ends when all enemies defeated -- [ ] Combat ends when player defeated -- [ ] Victory/defeat signals emitted -- [ ] Test file: `tests/test_turn_manager.gd` - ---- - -## Dependencies -- **Task 4.1** (Combat Input) - For player actions -- **Task 4.2** (Enemy AI) - For enemy actions -- **Uses existing:** Entity, CombatFormula, EntityLifecycle - -## Parallel Work -- **Must be done AFTER:** Tasks 4.1 and 4.2 -- **Can be done in parallel with:** Nothing (needs both 4.1 and 4.2) -- **Blocks:** Task 4.6 (Combat HUD), Task 4.8 (Moral Choice) - ---- - -## References -- `scripts/entities/entity.gd` - Entity stats (SPD, AP) -- `scripts/core/combat_formula.gd` - Damage calculation -- `scripts/entities/entity_lifecycle.gd` - State changes - -## Notes for Jules -This is the core loop. Test thoroughly with 1 player + 3 enemies. Use signals for loose coupling with UI. diff --git a/.github/issue_4_4_update.md b/.github/issue_4_4_update.md deleted file mode 100644 index 526fba9..0000000 --- a/.github/issue_4_4_update.md +++ /dev/null @@ -1,124 +0,0 @@ -## Task 4.4: Room Loading System - -**Phase:** 4B - Content Foundation -**Priority:** HIGH -**Duration:** 4-5 days -**Sprint:** Sprint 3 (Weeks 5-6) - ---- - -## Objective -Create a room loading system that reads room definitions from JSON and configures the grid, elevation, cover, and enemy encounters. - -### Current State -- Room generation is stubbed in RunManager -- Seeds are generated but no actual room layouts exist -- GridSystem can load rooms from JSON but no room JSON files exist -- CombatRoom has hardcoded enemy spawns - ---- - -## Execution Order - -**Can Start:** After Task 4.3 (Turn Manager) is functional -**Blocked by:** Task 4.3 (needs combat loop working first) -**Blocks:** None (content task, doesn't block others) - ---- - -## Implementation Details - -**1. Create Room JSON Files** - -```json -// config/rooms/room_standard_01.json -{ - "id": "room_standard_01", - "biome": "sanctum", - "size": {"width": 12, "height": 12}, - "layout": { - "elevation": [...], - "cover": [...], - "blocked": [...] - }, - "encounters": [ - { - "enemy_type": "grunt", - "count": 3, - "positions": [...] - } - ], - "player_start": {"x": 1, "y": 1} -} -``` - -**2. Create Room Loader** - -```gdscript -# scripts/combat/room_loader.gd -class_name RoomLoader -extends Node - -const ROOMS_PATH := "res://config/rooms/" - -func load_room(room_id: String) -> Dictionary: - var path := ROOMS_PATH + room_id + ".json" - # Read and parse JSON - return json.data - -func configure_grid(room_data: Dictionary) -> void: - GridSystem.load_room(room_data) - -func spawn_encounters(room_data: Dictionary, entity_container: Node) -> void: - # Spawn enemies at defined positions -``` - -**3. Hook into RunManager** - -```gdscript -# In RunManager._enter_room(): -func _enter_room(_ctx: Dictionary) -> void: - var room_data := _get_current_room_data() - var room_id: String = room_data.get("room_id", "room_standard_01") - var room_def := RoomLoader.load_room(room_id) - - if not room_def.is_empty(): - RoomLoader.configure_grid(room_def) - RoomLoader.spawn_encounters(room_def, _entity_container) - _spawn_player(room_def.get("player_start", {"x": 1, "y": 1})) -``` - ---- - -## Acceptance Criteria -- [ ] 3-5 room JSON files created -- [ ] Room loader reads and parses JSON -- [ ] Grid configured from room definition (elevation, cover, blocked) -- [ ] Enemies spawned at defined positions -- [ ] Player spawned at defined start position -- [ ] Rooms have different layouts -- [ ] Rooms have different enemy encounters -- [ ] Hooked into RunManager room flow -- [ ] Test file: `tests/test_room_loader.gd` - ---- - -## Dependencies -- **Task 4.3** (Turn Manager) - Good to have combat first -- **Task 4.5** (Enemy Variety) - For different enemy types in rooms -- **Uses existing:** GridSystem.load_room(), BaseEnemy scenes - -## Parallel Work -- **Can be done in parallel with:** Task 4.5 (Enemy Variety) -- **Best completed after:** Task 4.3 -- **Shares files with:** Task 4.5 (enemy scenes) - ---- - -## References -- `scripts/autoload/grid_system.gd` - Grid configuration -- `scripts/state_machine/run_manager.gd` - Room flow -- `scripts/entities/base_enemy.gd` - Enemy spawning - -## Notes for Jules -Create 3-5 simple room layouts first. Focus on different elevation and cover configurations. diff --git a/.github/issue_4_5_update.md b/.github/issue_4_5_update.md deleted file mode 100644 index 30f4053..0000000 --- a/.github/issue_4_5_update.md +++ /dev/null @@ -1,127 +0,0 @@ -## Task 4.5: Enemy Variety - -**Phase:** 4B - Content Foundation -**Priority:** HIGH -**Duration:** 3-4 days -**Sprint:** Sprint 3 (Weeks 5-6) - ---- - -## Objective -Create 3 distinct enemy types with different stats, behaviors, and visuals. - -### Current State -- Only Grunt enemy exists -- All enemies use the same base class with no variation -- AI is a single file with enum-based behavior switching - ---- - -## Execution Order - -**Can Start:** After Task 4.2 (Basic Enemy AI) is functional -**Blocked by:** Task 4.2 (needs AI behaviors defined) -**Blocks:** Task 4.4 (Room Loading - needs enemy scenes to spawn) - ---- - -## Implementation Details - -**1. Create Enemy Subclasses** - -```gdscript -# scripts/entities/enemies/enemy_grunt.gd -class_name EnemyGrunt -extends BaseEnemy - -func _setup_entity() -> void: - entity = Entity.new("Grunt", x, y, 30, 8, 4) - entity.is_player = false - entity.spd = 4 - -func _setup_ai() -> void: - ai_controller.behavior = EnemyAIController.BehaviorType.GRUNT -``` - -```gdscript -# scripts/entities/enemies/enemy_archer.gd -class_name EnemyArcher -extends BaseEnemy - -func _setup_entity() -> void: - entity = Entity.new("Archer", x, y, 25, 6, 2) - entity.is_player = false - entity.spd = 5 - -func _setup_ai() -> void: - ai_controller.behavior = EnemyAIController.BehaviorType.ARCHER -``` - -```gdscript -# scripts/entities/enemies/enemy_tank.gd -class_name EnemyTank -extends BaseEnemy - -func _setup_entity() -> void: - entity = Entity.new("Tank", x, y, 60, 15, 8) - entity.is_player = false - entity.spd = 2 - -func _setup_ai() -> void: - ai_controller.behavior = EnemyAIController.BehaviorType.TANK -``` - -**2. Update AI Controller** - -```gdscript -# In EnemyAIController: -func _archer_behavior(dist: int) -> Dictionary: - if dist >= 2 and dist <= 3: - return {"type": "attack", "target": player_entity} - elif dist < 2: - var away_dir := _get_move_away_from_player() - return {"type": "move", "dx": away_dir.x, "dy": away_dir.y} - else: - var toward_dir := _get_move_toward_player() - return {"type": "move", "dx": toward_dir.x, "dy": toward_dir.y} -``` - -### Enemy Stats - -| Type | HP | OFF | DEF | SPD | Range | Behavior | -|------|----|-----|-----|-----|-------|----------| -| Grunt | 30 | 8 | 4 | 4 | Melee (1) | Rush player | -| Archer | 25 | 6 | 2 | 5 | Ranged (2-3) | Maintain distance | -| Tank | 60 | 15 | 8 | 2 | Melee (1) | Slow advance | - ---- - -## Acceptance Criteria -- [ ] 3 enemy classes created (Grunt, Archer, Tank) -- [ ] Different stats for each type -- [ ] Different AI behaviors -- [ ] Scene files for each type -- [ ] Visual differentiation (color/size) -- [ ] Spawnable via RoomLoader -- [ ] Each type has unique tactical role -- [ ] Test file: `tests/test_enemy_variety.gd` - ---- - -## Dependencies -- **Task 4.2** (Enemy AI) - For behavior variations -- **Task 4.4** (Room Loading) - For spawning - -## Parallel Work -- **Can be done in parallel with:** Task 4.4 (Room Loading) -- **Best completed after:** Task 4.2 -- **Shares files with:** Task 4.4 (enemy scenes) - ---- - -## References -- `scripts/entities/base_enemy.gd` - Base class -- `scripts/ai/enemy_ai_controller.gd` - AI behaviors - -## Notes for Jules -Create scenes with different colored rectangles for now: Grunt=white, Archer=green, Tank=blue. diff --git a/.github/issue_4_6_update.md b/.github/issue_4_6_update.md deleted file mode 100644 index 71e9e4c..0000000 --- a/.github/issue_4_6_update.md +++ /dev/null @@ -1,115 +0,0 @@ -## Task 4.6: Combat HUD - -**Phase:** 4C - Polish -**Priority:** MEDIUM -**Duration:** 3-4 days -**Sprint:** Sprint 5 (Weeks 7-8) - ---- - -## Objective -Create a functional combat HUD that displays HP, AP, turn information, and action buttons. - -### Current State -- CombatHUD scene exists but is not functional -- No HP/AP display during combat -- No action buttons visible -- No turn indicator - ---- - -## Execution Order - -**Can Start:** After Task 4.3 (Turn Manager) is functional -**Blocked by:** Task 4.3 (needs turn state), Task 4.1 (needs attack action) -**Blocks:** None (UI only) - ---- - -## Implementation Details - -**1. HP/AP Display** - -```gdscript -# scripts/ui/combat_hud.gd (update existing) -@onready var hp_bar: ProgressBar = %HPBar -@onready var hp_label: Label = %HPLabel -@onready var ap_bar: ProgressBar = %APBar -@onready var ap_label: Label = %APLabel - -func update_player_stats(entity: Entity) -> void: - hp_bar.max_value = entity.hp_max - hp_bar.value = entity.hp - hp_label.text = "%d / %d" % [entity.hp, entity.hp_max] - - ap_bar.max_value = GameConstants.AP_MAX - ap_bar.value = entity.ap - ap_label.text = "%d / %d" % [entity.ap, GameConstants.AP_MAX] -``` - -**2. Action Buttons** - -```gdscript -@onready var move_button: Button = %MoveButton -@onready var attack_button: Button = %AttackButton -@onready var end_turn_button: Button = %EndTurnButton - -func _on_attack_pressed() -> void: - combat_input._enter_targeting_mode() - -func _on_end_turn_pressed() -> void: - turn_manager.end_turn() -``` - -**3. Turn Indicator** - -```gdscript -@onready var turn_label: Label = %TurnLabel -@onready var round_label: Label = %RoundLabel - -func show_player_turn() -> void: - turn_label.text = "Your Turn" - turn_label.modulate = Color.GREEN - enable_action_buttons() - -func show_enemy_turn() -> void: - turn_label.text = "Enemy Turn" - turn_label.modulate = Color.RED - disable_action_buttons() -``` - ---- - -## Acceptance Criteria -- [ ] HP bar shows current/max HP -- [ ] AP bar shows current/max AP -- [ ] Action buttons visible and functional -- [ ] Turn indicator shows whose turn -- [ ] Round counter visible -- [ ] Target info appears on hover -- [ ] Combat log shows actions -- [ ] Disabled state when not player's turn -- [ ] Updates dynamically during combat -- [ ] Test file: `tests/test_combat_hud.gd` - ---- - -## Dependencies -- **Task 4.1** (Combat Input) - For attack button -- **Task 4.3** (Turn Manager) - For turn display -- **Uses existing:** CombatHUD scene - -## Parallel Work -- **Can be done in parallel with:** Tasks 4.7, 4.8 -- **Best completed after:** Task 4.3 -- **Shares no critical files with:** Tasks 4.7, 4.8 - ---- - -## References -- `scripts/ui/combat_hud.gd` - Current implementation -- `scripts/combat/turn_manager.gd` - Turn signals -- `scripts/combat/combat_input.gd` - Input actions - -## Notes for Jules -This is UI-only work. Focus on layout and signals. No game logic changes needed. diff --git a/.github/issue_4_7_update.md b/.github/issue_4_7_update.md deleted file mode 100644 index 4d18341..0000000 --- a/.github/issue_4_7_update.md +++ /dev/null @@ -1,102 +0,0 @@ -## Task 4.7: Settings Menu - -**Phase:** 4C - Polish -**Priority:** MEDIUM -**Duration:** 2-3 days -**Sprint:** Sprint 5 (Weeks 7-8) - ---- - -## Objective -Make the Settings menu functional with resolution, audio, and key binding options. - -### Current State -- Settings menu scene exists (`scenes/ui/settings_menu.tscn`) -- Settings button on title screen does nothing -- SettingsManager autoload exists but is basic -- No actual settings are persisted or applied - ---- - -## Execution Order - -**Can Start:** Immediately -**Blocked by:** Nothing (UI only, independent of combat) -**Blocks:** None - ---- - -## Implementation Details - -**1. Settings Categories** - -```gdscript -# scripts/autoload/settings_manager.gd (update) -enum SettingCategory { - DISPLAY, - AUDIO, - INPUT, - ACCESSIBILITY -} -``` - -**2. Display Settings** -- Resolution dropdown (1920x1080, 1600x900, 1280x720) -- Fullscreen toggle -- VSync toggle -- Apply button (requires restart) - -**3. Audio Settings** -- Master volume slider (0-100%) -- Music volume slider -- SFX volume slider -- Mute toggle - -**4. Input Settings** -- Show current key bindings -- Click to rebind (capture next keypress) -- Reset to defaults button - -**5. Settings Persistence** -```gdscript -func save_settings() -> void: - var file := FileAccess.open("user://settings.json", FileAccess.WRITE) - if file: - file.store_string(JSON.stringify(_settings)) - file.close() -``` - ---- - -## Acceptance Criteria -- [ ] Settings accessible from title screen -- [ ] Settings accessible from pause menu -- [ ] Resolution options work -- [ ] Fullscreen toggle works -- [ ] Volume sliders work -- [ ] Key rebinding works -- [ ] Settings persist between sessions -- [ ] Reset to defaults works -- [ ] Apply button confirms changes -- [ ] Back button returns to previous screen -- [ ] Test file: `tests/test_settings_menu.gd` - ---- - -## Dependencies -- **None** - UI only, independent of combat - -## Parallel Work -- **Can be done in parallel with:** Tasks 4.6, 4.8 -- **Best completed:** Anytime (no dependencies) -- **Shares no files with:** Combat tasks - ---- - -## References -- `scripts/autoload/settings_manager.gd` - Current implementation -- `scripts/ui/settings_menu.gd` - Menu script -- `scenes/ui/settings_menu.tscn` - Menu scene - -## Notes for Jules -This is UI-only work. Can be done at any time. Focus on user:// persistence for settings. diff --git a/.github/issue_4_8_update.md b/.github/issue_4_8_update.md deleted file mode 100644 index e7ed96c..0000000 --- a/.github/issue_4_8_update.md +++ /dev/null @@ -1,126 +0,0 @@ -## Task 4.8: Moral Choice UI - -**Phase:** 4C - Polish -**Priority:** MEDIUM -**Duration:** 2-3 days -**Sprint:** Sprint 5 (Weeks 7-8) - ---- - -## Objective -Create the UI for moral choices (spare vs execute) when an enemy reaches the DYING state. - -### Current State -- EntityLifecycle has `spare_entity()` and `execute_entity()` methods -- Moral delta system works (queued and processed) -- Burden system tracks kills -- **NO UI exists** - Choices are invisible to player - ---- - -## Execution Order - -**Can Start:** After Task 4.3 (Turn Manager) is functional -**Blocked by:** Task 4.3 (needs turn flow), Task 4.1 (needs combat to reach DYING state) -**Blocks:** None (UI only) - ---- - -## Implementation Details - -**1. Choice Popup** - -```gdscript -# scripts/ui/moral_choice_ui.gd -class_name MoralChoiceUI -extends CanvasLayer - -signal choice_made(spared: bool) - -@onready var panel: PanelContainer = %ChoicePanel -@onready var enemy_name_label: Label = %EnemyNameLabel -@onready var spare_button: Button = %SpareButton -@onready var execute_button: Button = %ExecuteButton -@onready var timeout_bar: ProgressBar = %TimeoutBar -@onready var moral_preview: Label = %MoralPreview - -var _target_entity: Entity = null -var _timeout: float = 5.0 -var _remaining: float = 0.0 -``` - -**2. Show Choice** - -```gdscript -func show_choice(enemy: Entity) -> void: - _target_entity = enemy - panel.visible = true - - enemy_name_label.text = "%s is dying..." % enemy.entity_name - moral_preview.text = "Spare: -1 | Execute: +1" - - spare_button.text = "Spare (Cost: 1 AP)" - execute_button.text = "Execute" - - _remaining = _timeout - timeout_bar.max_value = _timeout - timeout_bar.value = _timeout -``` - -**3. Handle Choices** - -```gdscript -func _on_spare_pressed() -> void: - var player := _get_player_entity() - if player and player.ap >= 1: - EntityLifecycle.spare_entity(player, _target_entity) - choice_made.emit(true) - _hide_choice() - -func _on_execute_pressed() -> void: - EntityLifecycle.execute_entity(_target_entity) - choice_made.emit(false) - _hide_choice() - -func _auto_execute() -> void: - if _target_entity and _target_entity.state == Entity.State.DYING: - EntityLifecycle.execute_entity(_target_entity) - choice_made.emit(false) - _hide_choice() -``` - ---- - -## Acceptance Criteria -- [ ] Choice appears when enemy reaches 0 HP -- [ ] Shows enemy name -- [ ] Shows moral consequences -- [ ] Spare button visible (disabled if no AP) -- [ ] Execute button visible -- [ ] 5-second timeout with progress bar -- [ ] Auto-executes if no choice made -- [ ] Spare: Consumes 1 AP, -1 moral, enemy becomes GHOST -- [ ] Execute: Free, +1 moral, enemy becomes DEAD -- [ ] Visual feedback on choice -- [ ] Test file: `tests/test_moral_choice_ui.gd` - ---- - -## Dependencies -- **Task 4.1** (Combat Input) - For triggering DYING state -- **Task 4.3** (Turn Manager) - For turn flow -- **Uses existing:** EntityLifecycle.spare_entity/execute_entity - -## Parallel Work -- **Can be done in parallel with:** Tasks 4.6, 4.7 -- **Best completed after:** Task 4.3 -- **Shares no critical files with:** Tasks 4.6, 4.7 - ---- - -## References -- `scripts/entities/entity_lifecycle.gd` - Spare/execute logic -- `scripts/entities/entity.gd` - Entity states - -## Notes for Jules -This is UI-only work. Hook into entity state changes. Timeout prevents soft-locking. diff --git a/PROJECT_BOARD.md b/PROJECT_BOARD.md index d0ea8c2..02873a2 100644 --- a/PROJECT_BOARD.md +++ b/PROJECT_BOARD.md @@ -158,7 +158,7 @@ After completing all Phase 4 tasks, a player can: ## Dependency Graph -``` +```text Phase 4A (Sprint 1-2): ├─ 4.1 Combat Input ──┐ │ ├─→ 4.3 Turn Manager ──┐ @@ -186,7 +186,9 @@ Independent: ## Phase 5 Backlog (Future) -*Waiting for Phase 4 completion before planning* +### Phase 5 Status + +Waiting for Phase 4 completion before planning **Potential Areas:** From 13babe9d26b711cf69043b11830b0894de5379e0 Mon Sep 17 00:00:00 2001 From: Niyaz <9331133+niyazmft@users.noreply.github.com> Date: Wed, 3 Jun 2026 23:03:14 +0300 Subject: [PATCH 4/5] docs: remove temp planning file, keep project board --- PHASE4_PROJECT_PLAN.md | 523 ----------------------------------------- 1 file changed, 523 deletions(-) delete mode 100644 PHASE4_PROJECT_PLAN.md diff --git a/PHASE4_PROJECT_PLAN.md b/PHASE4_PROJECT_PLAN.md deleted file mode 100644 index 3ea3f75..0000000 --- a/PHASE4_PROJECT_PLAN.md +++ /dev/null @@ -1,523 +0,0 @@ -# Phase 4 Project Plan: Minimum Viable Gameplay - -## Overview - -**Timeline:** 8 weeks (sprint-based) -**Goal:** Transform Emberfall from tech demo into playable tactical combat game -**Sprint Structure:** 2-week sprints - ---- - -## Phase 4A: Combat Core (Sprints 1-2) - -**Weeks 1-3 | Priority: CRITICAL** - -### Task 4.1: Combat Input System - -**Duration:** 3-4 days -**Dependencies:** None (uses existing Entity + CombatFormula) - -**Implementation:** - -```gdscript -# scripts/combat/combat_input.gd -class_name CombatInput -extends Node - -## Handles player combat input: targeting, attacking, abilities - -@export var player_entity: Entity -@export var grid_system: _GridSystem -@export var combat_system: CombatSystem - -var _selected_target: BaseEnemy = null -var _in_targeting_mode: bool = false -var _valid_targets: Array[BaseEnemy] = [] - -func _input(event: InputEvent) -> void: - if event.is_action_pressed("attack"): - _enter_targeting_mode() - elif event.is_action_pressed("confirm") and _in_targeting_mode: - _execute_attack() - elif event.is_action_pressed("cancel") and _in_targeting_mode: - _exit_targeting_mode() - -func _enter_targeting_mode() -> void: - _in_targeting_mode = true - _valid_targets = _find_valid_targets() - _highlight_targets(_valid_targets) - _selected_target = _get_nearest_target() - _update_target_highlight() - -func _find_valid_targets() -> Array[BaseEnemy]: - ## Find enemies within attack range (adjacent tiles) - var targets: Array[BaseEnemy] = [] - var enemies := get_tree().get_nodes_in_group("enemies") - for enemy in enemies: - if enemy is BaseEnemy and enemy.alive(): - var dist := _grid_distance(player_entity, enemy.entity) - if dist <= 1: # Melee range - targets.append(enemy) - return targets - -func _execute_attack() -> void: - if _selected_target == null: - return - - combat_system.execute_attack(player_entity, _selected_target.entity) - _exit_targeting_mode() -``` - -**Acceptance Criteria:** - -- [ ] Press Space or click to enter targeting mode -- [ ] Valid targets highlighted (adjacent enemies) -- [ ] Tab or arrows to cycle targets -- [ ] Enter or click to confirm attack -- [ ] Damage dealt using CombatFormula -- [ ] Visual feedback (flash, shake) -- [ ] Escape to cancel targeting - ---- - -### Task 4.2: Basic Enemy AI - -**Duration:** 4-5 days -**Dependencies:** Task 4.1 (combat input exists) - -**Implementation:** - -```gdscript -# scripts/ai/enemy_ai_controller.gd -class_name EnemyAIController -extends Node - -## Basic AI for enemies: move toward player, attack in range - -@export var enemy_entity: Entity -@export var grid_system: _GridSystem -@export var player_entity: Entity - -enum BehaviorType { GRUNT, ARCHER, TANK } -@export var behavior: BehaviorType = BehaviorType.GRUNT - -func decide_action() -> Dictionary: - var player_dist := _grid_distance(enemy_entity, player_entity) - - match behavior: - BehaviorType.GRUNT: - return _grunt_behavior(player_dist) - BehaviorType.ARCHER: - return _archer_behavior(player_dist) - BehaviorType.TANK: - return _tank_behavior(player_dist) - - return {"type": "wait"} - -func _grunt_behavior(dist: int) -> Dictionary: - if dist <= 1: - return {"type": "attack", "target": player_entity} - else: - var move_dir := _get_move_toward_player() - return {"type": "move", "dx": move_dir.x, "dy": move_dir.y} - -func _get_move_toward_player() -> Vector2i: - var dx := sign(player_entity.x - enemy_entity.x) - var dy := sign(player_entity.y - enemy_entity.y) - - # Try direct path first - if grid_system.can_move(enemy_entity.x, enemy_entity.y, - enemy_entity.x + dx, enemy_entity.y + dy): - return Vector2i(dx, dy) - - # Try alternate paths - if dx != 0 and grid_system.can_move(enemy_entity.x, enemy_entity.y, - enemy_entity.x + dx, enemy_entity.y): - return Vector2i(dx, 0) - - if dy != 0 and grid_system.can_move(enemy_entity.x, enemy_entity.y, - enemy_entity.x, enemy_entity.y + dy): - return Vector2i(0, dy) - - return Vector2i.ZERO -``` - -**Acceptance Criteria:** - -- [ ] Grunts move toward player when out of range -- [ ] Grunts attack when adjacent -- [ ] Archers maintain distance (2-3 tiles) -- [ ] Tanks move slowly but have high HP -- [ ] Enemies respect grid boundaries -- [ ] Enemies avoid blocked tiles -- [ ] AI runs during enemy turn - ---- - -### Task 4.3: Turn Manager - -**Duration:** 5-7 days -**Dependencies:** Tasks 4.1, 4.2 - -**Implementation:** - -```gdscript -# scripts/combat/turn_manager.gd -class_name TurnManager -extends Node - -## Manages turn order, initiative, AP economy - -signal turn_started(entity: Entity, is_player: bool) -signal turn_ended(entity: Entity) -signal round_started(round_number: int) -signal combat_ended(victory: bool) - -var _participants: Array[Entity] = [] -var _current_index: int = 0 -var _round_number: int = 0 -var _combat_active: bool = false - -func start_combat(player: Entity, enemies: Array[BaseEnemy]) -> void: - _participants.clear() - _participants.append(player) - for enemy in enemies: - _participants.append(enemy.entity) - - # Sort by SPD (descending) for initiative - _participants.sort_custom( - func(a: Entity, b: Entity) -> bool: - return a.spd > b.spd - ) - - _current_index = 0 - _round_number = 1 - _combat_active = true - - round_started.emit(_round_number) - _start_turn() - -func _start_turn() -> void: - if not _combat_active: - return - - var entity := _participants[_current_index] - - # Reset AP at start of turn - if entity.is_player: - entity.ap = DeterministicMath.ap_start(entity.ap, GameConstants.AP_REGEN, GameConstants.AP_MAX) - else: - entity.ap = GameConstants.AP_MAX # Enemies get full AP - - turn_started.emit(entity, entity.is_player) - - if not entity.is_player: - # Auto-run enemy AI after delay - await get_tree().create_timer(0.5).timeout - _execute_enemy_turn(entity) - -func _execute_enemy_turn(entity: Entity) -> void: - var enemy := _get_enemy_node(entity) - if enemy: - var action := enemy.ai_controller.decide_action() - _execute_action(entity, action) - end_turn() - -func _execute_action(entity: Entity, action: Dictionary) -> void: - match action.get("type", ""): - "move": - var dx: int = action.get("dx", 0) - var dy: int = action.get("dy", 0) - if entity.ap >= 1: - entity.set_grid_position(entity.x + dx, entity.y + dy) - entity.ap -= 1 - "attack": - var target: Entity = action.get("target") - if entity.ap >= 2 and target != null: - var damage := CombatFormula.compute_damage_from_entities(entity, target, []) - EntityLifecycle.apply_damage(entity, target, damage) - entity.ap -= 2 - -func end_turn() -> void: - var entity := _participants[_current_index] - turn_ended.emit(entity) - - _current_index += 1 - if _current_index >= _participants.size(): - _current_index = 0 - _round_number += 1 - round_started.emit(_round_number) - - _check_combat_end() - if _combat_active: - _start_turn() - -func _check_combat_end() -> void: - var player_alive := false - var enemies_alive := false - - for entity in _participants: - if entity.is_player and entity.alive(): - player_alive = true - elif not entity.is_player and entity.alive(): - enemies_alive = true - - if not player_alive: - _combat_active = false - combat_ended.emit(false) # Defeat - elif not enemies_alive: - _combat_active = false - combat_ended.emit(true) # Victory - -func _get_enemy_node(entity: Entity) -> BaseEnemy: - var enemies := get_tree().get_nodes_in_group("enemies") - for enemy in enemies: - if enemy is BaseEnemy and enemy.entity == entity: - return enemy - return null -``` - -**Acceptance Criteria:** - -- [ ] Combat starts when entering room -- [ ] Turns ordered by SPD (highest first) -- [ ] Player turn: manual control (move/attack/end) -- [ ] Enemy turn: AI executes automatically -- [ ] AP consumed for actions -- [ ] AP regenerated at turn start -- [ ] Combat ends when all enemies or player defeated -- [ ] Victory/defeat signals emitted - ---- - -## Phase 4B: Content Foundation (Sprints 3-4) - -**Weeks 4-6 | Priority: HIGH** - -### Task 4.4: Room Loading System - -**Duration:** 4-5 days -**Dependencies:** Task 4.3 (turn manager) - -**Implementation:** - -```json -// config/rooms/room_standard.json -{ - "id": "room_standard_01", - "biome": "sanctum", - "layout": { - "width": 12, - "height": 12, - "elevation": [ - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,1,1,0,0,1,1,0,0,0], - [0,0,1,1,1,1,1,1,1,1,0,0], - [0,1,1,2,2,1,1,2,2,1,1,0], - [0,1,1,2,2,1,1,2,2,1,1,0], - [0,0,1,1,1,1,1,1,1,1,0,0], - [0,0,0,1,1,0,0,1,1,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0], - [0,0,0,0,0,0,0,0,0,0,0,0] - ], - "cover": [ - {"x": 3, "y": 3, "type": "heavy"}, - {"x": 8, "y": 3, "type": "heavy"}, - {"x": 5, "y": 5, "type": "light"} - ] - }, - "encounters": [ - { - "enemy_type": "grunt", - "count": 3, - "positions": [{"x": 9, "y": 2}, {"x": 10, "y": 3}, {"x": 9, "y": 4}] - } - ] -} -``` - -**Acceptance Criteria:** - -- [ ] 3-5 room JSON files created -- [ ] Room loader reads JSON and configures grid -- [ ] Elevation data applied to grid -- [ ] Cover tiles configured -- [ ] Encounter spawner places enemies -- [ ] Hooked into RunManager room flow - ---- - -### Task 4.5: Enemy Variety - -**Duration:** 3-4 days -**Dependencies:** Task 4.4 (room loading) - -**Implementation:** - -```gdscript -# scripts/entities/enemies/enemy_archer.gd -class_name EnemyArcher -extends BaseEnemy - -func _setup_entity() -> void: - entity = Entity.new("Archer", x, y, 25, 6, 2) - entity.is_player = false - entity.spd = 5 - -# scripts/entities/enemies/enemy_tank.gd -class_name EnemyTank -extends BaseEnemy - -func _setup_entity() -> void: - entity = Entity.new("Tank", x, y, 60, 15, 8) - entity.is_player = false - entity.spd = 2 -``` - -**Acceptance Criteria:** - -- [ ] 3 enemy types: Grunt, Archer, Tank -- [ ] Different stats for each type -- [ ] Different AI behaviors -- [ ] Scene files for each type -- [ ] Spawnable via room loader - ---- - -## Phase 4C: Polish (Sprints 5-6) - -**Weeks 7-8 | Priority: MEDIUM** - -### Task 4.6: Combat HUD - -**Duration:** 3-4 days -**Dependencies:** Task 4.3 (turn manager + AP system) - -**Features:** - -- HP/MP bars for player -- AP display (current/max) -- Action buttons: Move, Attack, End Turn -- Turn indicator ("Player Turn" / "Enemy Turn") -- Target info (hover enemy to see HP) - -**Acceptance Criteria:** - -- [ ] Player HP bar visible -- [ ] AP counter visible -- [ ] Action buttons work -- [ ] Turn indicator shows whose turn -- [ ] Enemy info on hover - ---- - -### Task 4.7: Settings Menu - -**Duration:** 2-3 days -**Dependencies:** None (UI only) - -**Features:** - -- Resolution settings -- Audio volume -- Key rebinding (already partially done) -- Accessibility options - -**Acceptance Criteria:** - -- [ ] Settings accessible from pause menu -- [ ] Settings persist between sessions -- [ ] Volume controls work -- [ ] Resolution options work - ---- - -### Task 4.8: Moral Choice UI - -**Duration:** 2-3 days -**Dependencies:** Task 4.1 (combat) + existing EntityLifecycle - -**Features:** - -- When enemy reaches DYING state, show choice popup -- Options: Spare (costs 1 AP, -1 moral) or Execute (free, +1 moral) -- Visual indicator of moral consequences -- Timer (enemy dies automatically after X turns if no choice) - -**Acceptance Criteria:** - -- [ ] Choice appears when enemy at 0 HP -- [ ] Spare button visible and functional -- [ ] Execute button visible and functional -- [ ] AP cost shown -- [ ] Moral delta preview shown -- [ ] Auto-execute after timeout - ---- - -## Sprint Schedule - -| Sprint | Weeks | Focus | Tasks | -|--------|-------|-------|-------| -| **Sprint 1** | 1-2 | Combat Core | 4.1 Combat Input, 4.2 Enemy AI | -| **Sprint 2** | 3-4 | Combat Core + Turn System | 4.3 Turn Manager | -| **Sprint 3** | 5-6 | Content Foundation | 4.4 Room Loading, 4.5 Enemy Variety | -| **Sprint 4** | 7-8 | Polish | 4.6 Combat HUD, 4.7 Settings, 4.8 Moral Choice | - ---- - -## Definition of Done (Phase 4) - -**A player can:** - -1. Start a new game -2. Enter a combat room -3. See enemies and environment -4. Take turns with enemies -5. Move and attack -6. Defeat enemies or be defeated -7. Make moral choices (spare/execute) -8. See HP/AP on HUD - -**This is a Minimum Viable Gameplay loop.** - ---- - -## Risk Mitigation - -| Risk | Likelihood | Impact | Mitigation | -|------|-----------|--------|------------| -| AI pathfinding complex | Medium | High | Use GridSystem.can_move(), not A* | -| Turn system bugs | Medium | High | Extensive testing, simple state machine | -| Scope creep | High | Medium | Strict sprint boundaries | -| Performance with many enemies | Low | Medium | Grid is small (12×12) | - ---- - -## Dependencies Summary - -``` -4.1 Combat Input - | - v -4.2 Enemy AI - | - v -4.3 Turn Manager - | - +---> 4.4 Room Loading - | | - | v - | 4.5 Enemy Variety - | - +---> 4.6 Combat HUD - | - +---> 4.8 Moral Choice UI -``` - ---- - -*Prepared for Donchitos CEO Agent | 2026-06-03* From ff46246bb605af3ed96718615ce25567c15d0724 Mon Sep 17 00:00:00 2001 From: Niyaz Almufti <9331133+niyazmft@users.noreply.github.com> Date: Wed, 3 Jun 2026 23:06:27 +0300 Subject: [PATCH 5/5] Update PROJECT_BOARD.md Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- PROJECT_BOARD.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/PROJECT_BOARD.md b/PROJECT_BOARD.md index 02873a2..de9d8df 100644 --- a/PROJECT_BOARD.md +++ b/PROJECT_BOARD.md @@ -163,9 +163,9 @@ Phase 4A (Sprint 1-2): ├─ 4.1 Combat Input ──┐ │ ├─→ 4.3 Turn Manager ──┐ └─ 4.2 Enemy AI ──────┘ ├─→ 4.6 Combat HUD - ├─→ 4.8 Moral Choice - └─→ 4.4 Room Loading - └─→ 4.5 Enemy Variety + │ ├─→ 4.8 Moral Choice + │ └─→ 4.4 Room Loading + └───────────────────────────────────────────→ 4.5 Enemy Variety Independent: └─ 4.7 Settings Menu (can start anytime)