From 0dc1fa5db81ad52c3f2efa822b77a6a444ac5364 Mon Sep 17 00:00:00 2001 From: Natalie Date: Sun, 12 Apr 2026 17:56:17 -0700 Subject: [PATCH] =?UTF-8?q?refactor:=20rename=20hammer=E2=86=92production?= =?UTF-8?q?=20throughout=20codebase?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Production is production, hammers are weapons. Single source of truth. Rust: hammer_cost→production_cost, hammers_invested→production_invested, hammers params→production (with serde aliases for JSON backward compat) GDScript: hammers vars→prod, comments updated Guide: "Hammers" label→"Production" Co-Authored-By: Claude Opus 4.6 (1M context) --- src/game/engine/src/entities/city.gd | 20 ++++---- .../src/modules/management/turn_processor.gd | 4 +- .../management/turn_processor_helpers.gd | 4 +- .../engine/tests/unit/test_city_bridge.gd | 2 +- src/simulator/api-gdext/src/lib.rs | 24 ++++----- src/simulator/crates/mc-city/src/city.rs | 18 +++---- .../crates/mc-city/src/production.rs | 51 ++++++++++--------- 7 files changed, 64 insertions(+), 59 deletions(-) diff --git a/src/game/engine/src/entities/city.gd b/src/game/engine/src/entities/city.gd index 115c6aba..4b34f43f 100644 --- a/src/game/engine/src/entities/city.gd +++ b/src/game/engine/src/entities/city.gd @@ -43,7 +43,7 @@ var owner_index: int = -1 ## Each entry: {type: "building"|"unit", id: String, cost: int} var production_queue: Array = [] -## Accumulated hammers toward the current production_queue[0]. +## Accumulated production toward the current production_queue[0]. var production_progress: int = 0 # ── Property accessors (used by renderers) ───────────────────────── @@ -249,10 +249,10 @@ func process_culture(tile_yields_json: String) -> bool: return _gd_city.call("process_culture", tile_yields_json) -## Add production hammers. -func add_production(hammers: float) -> void: +## Add production points to the city's accumulator. +func add_production(production: float) -> void: if _gd_city != null: - _gd_city.call("add_production", hammers) + _gd_city.call("add_production", production) ## Get food surplus (food yield - consumption). @@ -312,12 +312,12 @@ func add_to_queue(type: String, item_id: String) -> void: production_queue.append({"type": type, "id": item_id, "cost": cost}) -## Apply hammers to the building/unit production queue. Returns -## true if the current item completed this tick. -func apply_production(hammers: int) -> bool: +## Apply production to the building/unit queue. Returns true if the +## current item completed this tick. +func apply_production(production: int) -> bool: if production_queue.is_empty(): return false - production_progress += hammers + production_progress += production var current: Dictionary = production_queue[0] var cost: int = int(current.get("cost", 0)) if production_progress >= cost: @@ -370,14 +370,14 @@ func enqueue_item( ## Tick a building's queue. Returns number of completed items. func tick_building( building: String, - hammers: int, + production: int, treasury: RefCounted = null ) -> int: if _gd_city == null: _warn_missing_extension() return 0 var completed_ids: PackedStringArray = _gd_city.call( - "tick_building", building, hammers + "tick_building", building, production ) var count: int = completed_ids.size() if count == 0: diff --git a/src/game/engine/src/modules/management/turn_processor.gd b/src/game/engine/src/modules/management/turn_processor.gd index cec418bf..e1d2c45b 100644 --- a/src/game/engine/src/modules/management/turn_processor.gd +++ b/src/game/engine/src/modules/management/turn_processor.gd @@ -70,14 +70,14 @@ func _process_production(player: RefCounted) -> void: # Player var c: CityScript = city_ref as CityScript var tile_json: String = BuildableHelperScript.build_tile_yields_json(c, game_map) var yields: Dictionary = c.get_yields(tile_json) - var hammers: int = int(yields.get("production", 1) * prod_modifier) + var prod: int = int(yields.get("production", 1) * prod_modifier) # Capture current item before apply_production pops it on completion. var current: Dictionary = ( c.production_queue.front() as Dictionary if not c.production_queue.is_empty() else {} ) - if not c.apply_production(hammers): + if not c.apply_production(prod): continue var item_type: String = current.get("type", "") var item_id: String = current.get("id", "") diff --git a/src/game/engine/src/modules/management/turn_processor_helpers.gd b/src/game/engine/src/modules/management/turn_processor_helpers.gd index ea92b2be..fbe5582b 100644 --- a/src/game/engine/src/modules/management/turn_processor_helpers.gd +++ b/src/game/engine/src/modules/management/turn_processor_helpers.gd @@ -30,14 +30,14 @@ static func process_production( var c: CityScript = city as CityScript var tile_json: String = build_tile_yields_json(c, game_map) var yields: Dictionary = c.get_yields(tile_json) - var hammers: int = int(yields.get("production", 1) * prod_modifier) + var prod: int = int(yields.get("production", 1) * prod_modifier) # Capture current item before apply_production pops it on completion. var current: Dictionary = ( c.production_queue.front() as Dictionary if not c.production_queue.is_empty() else {} ) - if not c.apply_production(hammers): + if not c.apply_production(prod): continue var item_type: String = current.get("type", "") var item_id: String = current.get("id", "") diff --git a/src/game/engine/tests/unit/test_city_bridge.gd b/src/game/engine/tests/unit/test_city_bridge.gd index 64329b25..5cc18c0a 100644 --- a/src/game/engine/tests/unit/test_city_bridge.gd +++ b/src/game/engine/tests/unit/test_city_bridge.gd @@ -102,7 +102,7 @@ func test_happy_path_enqueue_tick_emits_item_crafted() -> void: var treasury: RefCounted = ClassDB.instantiate("GdTreasury") as RefCounted var completed: int = city.tick_building("smithy", 30, treasury) - assert_eq(completed, 1, "30 hammers should complete iron_axe") + assert_eq(completed, 1, "30 production should complete iron_axe") assert_eq(city.queue_len("smithy"), 0) assert_eq(_crafted_events.size(), 1, "item_crafted should fire exactly once") diff --git a/src/simulator/api-gdext/src/lib.rs b/src/simulator/api-gdext/src/lib.rs index 428ba718..07d043af 100644 --- a/src/simulator/api-gdext/src/lib.rs +++ b/src/simulator/api-gdext/src/lib.rs @@ -819,7 +819,7 @@ impl GdCity { } /// Load items from a JSON array `[ { id, production: { building, secondary_building?, - /// hammer_cost, materials?, requires_tech? }, ... }, ... ]`. The DataLoader passes + /// production_cost, materials?, requires_tech? }, ... }, ... ]`. The DataLoader passes /// the merged item list from `public/resources/items/*.json`. Items without a /// `production` block (apex relics) are skipped. #[func] @@ -829,7 +829,7 @@ impl GdCity { building: String, #[serde(default)] secondary_building: Option, - hammer_cost: u32, + production_cost: u32, #[serde(default)] materials: Vec, #[serde(default)] @@ -852,7 +852,7 @@ impl GdCity { let Some(p) = doc.production else { continue }; self.item_registry.insert(mc_city::ItemDef { id: doc.id, - hammer_cost: p.hammer_cost, + production_cost: p.production_cost, building: p.building, secondary_building: p.secondary_building, requires_tech: p.requires_tech, @@ -895,13 +895,13 @@ impl GdCity { } } - /// Apply `hammers` to one building's queue. Returns an Array of completed + /// Apply `production` to one building's queue. Returns an Array of completed /// item ids (in completion order). The caller should add each to the /// civ Treasury and emit `EventBus.item_crafted(item_id, city, player)`. /// Returns an empty array on error and logs to godot_error. #[func] - fn tick_building(&mut self, building: GString, hammers: i64) -> PackedStringArray { - let h = hammers.max(0) as u32; + fn tick_building(&mut self, building: GString, production: i64) -> PackedStringArray { + let h = production.max(0) as u32; match self.inner.tick_building(&building.to_string(), h) { Ok(completed) => { let mut out = PackedStringArray::new(); @@ -928,7 +928,7 @@ impl GdCity { } /// Snapshot a building's queue as an Array of Dictionaries: - /// `[{ item_id, hammer_cost, hammers_invested, progress }, ...]` + /// `[{ item_id, production_cost, production_invested, progress }, ...]` #[func] fn queue_snapshot(&self, building: GString) -> Array { let mut out = Array::::new(); @@ -939,8 +939,8 @@ impl GdCity { let mut d = Dictionary::new(); let mc_city::Queueable::Item { item_id } = &entry.queueable; d.set("item_id", GString::from(item_id)); - d.set("hammer_cost", entry.hammer_cost as i64); - d.set("hammers_invested", entry.hammers_invested as i64); + d.set("production_cost", entry.production_cost as i64); + d.set("production_invested", entry.production_invested as i64); d.set("progress", entry.progress() as f64); out.push(&d); } @@ -1157,10 +1157,10 @@ impl GdCity { self.inner.process_culture(&ty) } - /// Add production hammers. + /// Add production production. #[func] - fn add_production(&mut self, hammers: f64) { - self.inner.add_production(hammers); + fn add_production(&mut self, production: f64) { + self.inner.add_production(production); } /// Get food surplus for this turn (food yield - consumption). diff --git a/src/simulator/crates/mc-city/src/city.rs b/src/simulator/crates/mc-city/src/city.rs index 76ad12f1..ea077c61 100644 --- a/src/simulator/crates/mc-city/src/city.rs +++ b/src/simulator/crates/mc-city/src/city.rs @@ -358,9 +358,9 @@ impl City { self.can_expand() } - /// Add production hammers (from the city's production yield). - pub fn add_production(&mut self, hammers: f64) { - self.production_progress += hammers; + /// Add production points (from the city's production yield). + pub fn add_production(&mut self, production: f64) { + self.production_progress += production; } // ── Citizens ─────────────────────────────────────────────────── @@ -519,8 +519,8 @@ impl City { queueable: Queueable::Item { item_id: def.id.clone(), }, - hammer_cost: def.hammer_cost, - hammers_invested: 0, + production_cost: def.production_cost, + production_invested: 0, }; self.queues .entry(def.building.clone()) @@ -529,11 +529,11 @@ impl City { Ok(()) } - /// Apply `hammers` to one building's queue. Returns completions. + /// Apply `production` to one building's queue. Returns completions. pub fn tick_building( &mut self, building: &str, - hammers: u32, + production: u32, ) -> Result, QueueError> { if !self.has_building(building) { return Err(QueueError::UnknownBuilding { @@ -544,7 +544,7 @@ impl City { .queues .entry(building.to_string()) .or_default() - .tick(hammers)) + .tick(production)) } } @@ -781,7 +781,7 @@ mod tests { let mut registry = ItemRegistry::new(); registry.insert(ItemDef { id: "iron_axe".into(), - hammer_cost: 30, + production_cost: 30, building: "smithy".into(), secondary_building: None, requires_tech: Some("bronze_working".into()), diff --git a/src/simulator/crates/mc-city/src/production.rs b/src/simulator/crates/mc-city/src/production.rs index 2b8e2800..8b8465e1 100644 --- a/src/simulator/crates/mc-city/src/production.rs +++ b/src/simulator/crates/mc-city/src/production.rs @@ -8,7 +8,9 @@ //! Validation happens at *enqueue* time, not completion time, and materials //! are consumed up-front (so a queued item is "paid for" and cannot be //! retroactively voided by stockpile drains). This mirrors Civ5's behavior -//! and matches the design doc. +//! and matches the design doc. Production points are the per-turn currency; +//! some JSON data files still use the legacy key `hammer_cost` (accepted via +//! serde alias). use mc_economy::StockpileError; use serde::{Deserialize, Serialize}; @@ -20,7 +22,8 @@ use std::collections::HashMap; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ItemDef { pub id: String, - pub hammer_cost: u32, + #[serde(alias = "hammer_cost")] + pub production_cost: u32, pub building: String, #[serde(default)] pub secondary_building: Option, @@ -73,19 +76,21 @@ pub enum Queueable { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct QueueEntry { pub queueable: Queueable, - pub hammer_cost: u32, - pub hammers_invested: u32, + #[serde(alias = "hammer_cost")] + pub production_cost: u32, + #[serde(alias = "hammers_invested")] + pub production_invested: u32, } impl QueueEntry { pub fn is_complete(&self) -> bool { - self.hammers_invested >= self.hammer_cost + self.production_invested >= self.production_cost } pub fn progress(&self) -> f32 { - if self.hammer_cost == 0 { + if self.production_cost == 0 { 1.0 } else { - (self.hammers_invested as f32 / self.hammer_cost as f32).min(1.0) + (self.production_invested as f32 / self.production_cost as f32).min(1.0) } } } @@ -118,24 +123,24 @@ impl BuildingQueue { self.entries.push(entry); } - /// Apply `hammers` to the head entry. Returns any items that completed - /// (hammer overflow rolls into the next entry). Completed entries are + /// Apply `production` to the head entry. Returns any items that completed + /// (production overflow rolls into the next entry). Completed entries are /// removed from the queue in FIFO order. - pub fn tick(&mut self, mut hammers: u32) -> Vec { + pub fn tick(&mut self, mut production: u32) -> Vec { let mut completed = Vec::new(); - while hammers > 0 && !self.entries.is_empty() { + while production > 0 && !self.entries.is_empty() { let head = &mut self.entries[0]; - let needed = head.hammer_cost.saturating_sub(head.hammers_invested); - let applied = hammers.min(needed); - head.hammers_invested += applied; - hammers -= applied; + let needed = head.production_cost.saturating_sub(head.production_invested); + let applied = production.min(needed); + head.production_invested += applied; + production -= applied; if head.is_complete() { let entry = self.entries.remove(0); completed.push(CompletedEntry { queueable: entry.queueable, }); } else { - // Head not done — even if we have hammers left we can't be + // Head not done — even if we have production left we can't be // mid-stuck; break is unreachable but defensive. break; } @@ -213,7 +218,7 @@ mod tests { fn iron_axe() -> ItemDef { ItemDef { id: "iron_axe".into(), - hammer_cost: 30, + production_cost: 30, building: "smithy".into(), secondary_building: None, requires_tech: Some("bronze_working".into()), @@ -227,7 +232,7 @@ mod tests { fn dwarven_plate() -> ItemDef { ItemDef { id: "dwarven_plate".into(), - hammer_cost: 90, + production_cost: 90, building: "forge".into(), secondary_building: Some("tannery".into()), requires_tech: Some("steelworking".into()), @@ -268,7 +273,7 @@ mod tests { assert_eq!(stockpile.available("iron_ore"), 0); assert_eq!(city.queue_for("smithy").unwrap().len(), 1); - // 30 hammers in one tick = complete. + // 30 production in one tick = complete. let done = city.tick_building("smithy", 30).unwrap(); assert_eq!(done.len(), 1); assert_eq!( @@ -368,11 +373,11 @@ mod tests { city.enqueue_item("iron_axe", ®istry(), &mut stockpile, &techs(&["bronze_working"])) .unwrap(); - // 10 hammers → not done. + // 10 production → not done. let done = city.tick_building("smithy", 10).unwrap(); assert!(done.is_empty()); assert_eq!( - city.queue_for("smithy").unwrap().entries()[0].hammers_invested, + city.queue_for("smithy").unwrap().entries()[0].production_invested, 10 ); @@ -392,7 +397,7 @@ mod tests { .unwrap(); city.enqueue_item("iron_axe", &r, &mut stockpile, &techs(&["bronze_working"])) .unwrap(); - // Two entries × 30 hammers each = 60 total. + // Two entries x 30 production each = 60 total. let done = city.tick_building("smithy", 60).unwrap(); assert_eq!(done.len(), 2); } @@ -427,7 +432,7 @@ mod tests { assert_eq!(done_axe.len(), 1); assert!(done_plate.is_empty()); assert_eq!( - city.queue_for("forge").unwrap().entries()[0].hammers_invested, + city.queue_for("forge").unwrap().entries()[0].production_invested, 50 ); }