From a804ceb430ad32ad536fe1091c725b9c1833cd9b Mon Sep 17 00:00:00 2001 From: Natalie Date: Mon, 8 Jun 2026 17:51:48 -0700 Subject: [PATCH] =?UTF-8?q?fix(@projects/@magic-civilization):=20?= =?UTF-8?q?=F0=9F=90=9B=20fix=20axial-to-offset=20conversion=20for=20grid?= =?UTF-8?q?=20visibility?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Lilith Autocommit --- .../engine/src/modules/ai/ai_turn_bridge.gd | 20 +++++++-- src/simulator/api-gdext/src/lib.rs | 41 +++++++++++++++++++ 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/src/game/engine/src/modules/ai/ai_turn_bridge.gd b/src/game/engine/src/modules/ai/ai_turn_bridge.gd index 14db796f..820581c3 100644 --- a/src/game/engine/src/modules/ai/ai_turn_bridge.gd +++ b/src/game/engine/src/modules/ai/ai_turn_bridge.gd @@ -329,10 +329,18 @@ static func _refresh_learned_player_snapshot(gd_state: RefCounted) -> void: for u: RefCounted in p.units: if u == null or not u.is_alive(): continue + # CRITICAL (p1-29k root-cause fix): GDScript stores unit `position` + # in AXIAL (q, r), but the Rust grid + `compute_vision` + + # Move/Attack legal-action enumeration all operate in ODD-Q OFFSET + # (col, row) space (`neighbours_offset` parity-by-column + + # `wrap_coord` rectangular bounds). Passing raw axial put units + # off-grid → no tile under them → visible_tiles=0, move/attack=0. + # Convert to offset so units sit on the same grid the vision walk uses. + var u_off: Vector2i = HexUtilsScript.axial_to_offset(u.position) unit_dicts.append({ "id": pi * ID_STRIDE + ui, - "col": int(u.position.x), - "row": int(u.position.y), + "col": u_off.x, + "row": u_off.y, "hp": int(u.hp), "max_hp": int(u.max_hp), "attack": int(u.attack), @@ -349,9 +357,13 @@ static func _refresh_learned_player_snapshot(gd_state: RefCounted) -> void: for c: RefCounted in p.cities: if c == null: continue + # Same axial→offset conversion as units — city centres feed + # city-sourced vision (`compute_player_visible_set`) and the + # capital position, both in offset space. + var c_off: Vector2i = HexUtilsScript.axial_to_offset(c.position) city_dicts.append({ - "col": int(c.position.x), - "row": int(c.position.y), + "col": c_off.x, + "row": c_off.y, "population": maxi(1, int(c.population)), "food_yield": int(c.get("food_yield")) if c.get("food_yield") != null else 2, "prod_yield": int(c.get("prod_yield")) if c.get("prod_yield") != null else 2, diff --git a/src/simulator/api-gdext/src/lib.rs b/src/simulator/api-gdext/src/lib.rs index bacea680..39b1cfee 100644 --- a/src/simulator/api-gdext/src/lib.rs +++ b/src/simulator/api-gdext/src/lib.rs @@ -4883,6 +4883,47 @@ impl GdGameState { d.set("attack_bits", attack_bits as i64); d.set("queue_production_bits", queue_production_bits as i64); d.set("trivial_bits", trivial_bits as i64); + + // Grid introspection (hypothesis 1) — prove the grid is populated and + // that the player's first unit sits ON a real tile with a biome + // label. `tile_under_unit_biome` empty ⇒ the unit is off-grid + // (coordinate-space mismatch) — the exact failure mode behind + // visible_tiles=0. + match self.inner.grid.as_ref() { + Some(g) => { + d.set("grid_width", g.width as i64); + d.set("grid_height", g.height as i64); + d.set("grid_tiles", g.tiles.len() as i64); + let labelled = g + .tiles + .iter() + .filter(|t| !t.biome_label_id.is_empty()) + .count(); + d.set("grid_tiles_with_biome", labelled as i64); + if let Some(sample) = g.tiles.iter().find(|t| !t.biome_label_id.is_empty()) { + d.set( + "grid_sample_biome", + GString::from(sample.biome_label_id.as_str()), + ); + } + // First own unit: report its (col,row) and the biome of the + // tile it occupies (empty string = no tile under it). + if let Some(ps) = self.inner.players.iter().find(|p| p.player_index == player) { + if let Some(u0) = ps.units.first() { + d.set("unit0_col", u0.col as i64); + d.set("unit0_row", u0.row as i64); + let biome_under = g + .tile(u0.col, u0.row) + .map(|t| t.biome_label_id.clone()) + .unwrap_or_default(); + d.set("tile_under_unit_biome", GString::from(biome_under.as_str())); + } + } + } + None => { + d.set("grid_tiles", 0i64); + } + } d }