fix(simulation): address PR #108 review — LEFT JOIN, JSON output, atmosphere default
Review fixes: - corridor-status: INNER JOIN → LEFT JOIN so systems without gate records are included in counts instead of silently excluded - corridor-status: output as JSON (serde_json) matching all other atlas commands, instead of plain-text ASCII table - generate_body_matrix: emit atmosphere "standard" instead of "breathable" to match committed-system conventions - Doc header: add corridor-status usage example - CHANGELOG: note stale habitable_planet_count in pre-fix systems Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+3
-1
@@ -10,7 +10,9 @@ Format based on [Keep a Changelog](https://keepachangelog.com/).
|
|||||||
- `corridor-status` subcommand for atlas CLI — shows remaining unfinished systems grouped by geographic sector and hop distance (#744)
|
- `corridor-status` subcommand for atlas CLI — shows remaining unfinished systems grouped by geographic sector and hop distance (#744)
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- `habitable_planet_count` filter now accepts both "breathable" and "standard" atmosphere values — previously all committed systems reported 0 habitable planets (#762)
|
- `habitable_planet_count` filter now accepts both "breathable" and "standard" atmosphere values — previously all committed systems reported 0 habitable planets (#762). Systems committed before this fix may have stale `habitable_planet_count = 0`; re-commit to update.
|
||||||
|
- `corridor-status` uses LEFT JOIN so systems without gate records are included in counts
|
||||||
|
- `generate_body_matrix` now emits `atmosphere: "standard"` (was "breathable") to match committed-system conventions
|
||||||
|
|
||||||
## [v0.1.29] — 2026-04-03
|
## [v0.1.29] — 2026-04-03
|
||||||
|
|
||||||
|
|||||||
+37
-26
@@ -10,6 +10,7 @@
|
|||||||
//! tooling/atlas show-body "GJ 15Ab"
|
//! tooling/atlas show-body "GJ 15Ab"
|
||||||
//! tooling/atlas add-body --system "GJ 15A" --type planet --orbit 1 --id "GJ 15Ab"
|
//! tooling/atlas add-body --system "GJ 15A" --type planet --orbit 1 --id "GJ 15Ab"
|
||||||
//! tooling/atlas stats
|
//! tooling/atlas stats
|
||||||
|
//! tooling/atlas corridor-status # remaining systems by corridor/hop
|
||||||
//! tooling/atlas populate # bulk classifier pass
|
//! tooling/atlas populate # bulk classifier pass
|
||||||
//!
|
//!
|
||||||
//! # Direct:
|
//! # Direct:
|
||||||
@@ -911,7 +912,7 @@ fn generate_body_matrix(
|
|||||||
surface_gravity: Some(0.9),
|
surface_gravity: Some(0.9),
|
||||||
orbital_period_days: Some(365.0),
|
orbital_period_days: Some(365.0),
|
||||||
rotation_period_hours: Some(24.0),
|
rotation_period_hours: Some(24.0),
|
||||||
atmosphere: Some("breathable".into()),
|
atmosphere: Some("standard".into()),
|
||||||
biome_summary: Some("temperate".into()),
|
biome_summary: Some("temperate".into()),
|
||||||
hydrosphere: None,
|
hydrosphere: None,
|
||||||
economic_role: None,
|
economic_role: None,
|
||||||
@@ -943,7 +944,7 @@ fn generate_body_matrix(
|
|||||||
surface_gravity: Some(0.85),
|
surface_gravity: Some(0.85),
|
||||||
orbital_period_days: Some(400.0),
|
orbital_period_days: Some(400.0),
|
||||||
rotation_period_hours: Some(26.0),
|
rotation_period_hours: Some(26.0),
|
||||||
atmosphere: Some("breathable".into()),
|
atmosphere: Some("standard".into()),
|
||||||
biome_summary: Some("temperate".into()),
|
biome_summary: Some("temperate".into()),
|
||||||
hydrosphere: None,
|
hydrosphere: None,
|
||||||
economic_role: None,
|
economic_role: None,
|
||||||
@@ -1999,26 +2000,28 @@ fn format_population(pop: i64) -> String {
|
|||||||
fn cmd_corridor_status(conn: &Connection) {
|
fn cmd_corridor_status(conn: &Connection) {
|
||||||
// Query unfinished systems grouped by geographic_sector and hop_distance_from_gateway.
|
// Query unfinished systems grouped by geographic_sector and hop_distance_from_gateway.
|
||||||
// "Unfinished" = no rows in bodies for this system_id.
|
// "Unfinished" = no rows in bodies for this system_id.
|
||||||
|
// LEFT JOIN so systems with no gate record are still counted (hop = NULL → "?").
|
||||||
let mut stmt = conn
|
let mut stmt = conn
|
||||||
.prepare(
|
.prepare(
|
||||||
"SELECT s.geographic_sector, g.hop_distance_from_gateway, COUNT(*) AS remaining
|
"SELECT s.geographic_sector, g.hop_distance_from_gateway, COUNT(*) AS remaining
|
||||||
FROM star_systems s
|
FROM star_systems s
|
||||||
JOIN system_gates g ON s.system_id = g.system_id
|
LEFT JOIN system_gates g ON s.system_id = g.system_id
|
||||||
WHERE s.system_id NOT IN (SELECT DISTINCT system_id FROM bodies)
|
WHERE s.system_id NOT IN (SELECT DISTINCT system_id FROM bodies)
|
||||||
GROUP BY s.geographic_sector, g.hop_distance_from_gateway
|
GROUP BY s.geographic_sector, g.hop_distance_from_gateway
|
||||||
ORDER BY s.geographic_sector, g.hop_distance_from_gateway",
|
ORDER BY s.geographic_sector, g.hop_distance_from_gateway",
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
struct Row {
|
#[derive(Serialize)]
|
||||||
|
struct CorridorRow {
|
||||||
sector: Option<String>,
|
sector: Option<String>,
|
||||||
hop: Option<i32>,
|
hop: Option<i32>,
|
||||||
remaining: i64,
|
remaining: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
let rows: Vec<Row> = stmt
|
let rows: Vec<CorridorRow> = stmt
|
||||||
.query_map([], |row| {
|
.query_map([], |row| {
|
||||||
Ok(Row {
|
Ok(CorridorRow {
|
||||||
sector: row.get(0)?,
|
sector: row.get(0)?,
|
||||||
hop: row.get(1)?,
|
hop: row.get(1)?,
|
||||||
remaining: row.get(2)?,
|
remaining: row.get(2)?,
|
||||||
@@ -2029,31 +2032,39 @@ fn cmd_corridor_status(conn: &Connection) {
|
|||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
if rows.is_empty() {
|
if rows.is_empty() {
|
||||||
println!("All systems have bodies authored.");
|
#[derive(Serialize)]
|
||||||
|
struct EmptyResult {
|
||||||
|
total: i64,
|
||||||
|
message: &'static str,
|
||||||
|
corridors: Vec<()>,
|
||||||
|
}
|
||||||
|
println!(
|
||||||
|
"{}",
|
||||||
|
serde_json::to_string_pretty(&EmptyResult {
|
||||||
|
total: 0,
|
||||||
|
message: "All systems have bodies authored.",
|
||||||
|
corridors: vec![],
|
||||||
|
})
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Plain text table: corridor | hop | remaining
|
|
||||||
let total: i64 = rows.iter().map(|r| r.remaining).sum();
|
let total: i64 = rows.iter().map(|r| r.remaining).sum();
|
||||||
println!("{:<30} {:>4} {:>9}", "Corridor", "Hop", "Remaining");
|
|
||||||
println!("{}", "-".repeat(48));
|
#[derive(Serialize)]
|
||||||
let mut last_sector = String::new();
|
struct CorridorResult {
|
||||||
for row in &rows {
|
total: i64,
|
||||||
let sector = row.sector.as_deref().unwrap_or("(unknown)");
|
corridors: Vec<CorridorRow>,
|
||||||
let hop = row
|
|
||||||
.hop
|
|
||||||
.map(|h| h.to_string())
|
|
||||||
.unwrap_or_else(|| "?".to_string());
|
|
||||||
if sector != last_sector {
|
|
||||||
if !last_sector.is_empty() {
|
|
||||||
println!();
|
|
||||||
}
|
|
||||||
last_sector = sector.to_string();
|
|
||||||
}
|
|
||||||
println!("{:<30} {:>4} {:>9}", sector, hop, row.remaining);
|
|
||||||
}
|
}
|
||||||
println!("{}", "-".repeat(48));
|
println!(
|
||||||
println!("{:<30} {:>4} {:>9}", "TOTAL", "", total);
|
"{}",
|
||||||
|
serde_json::to_string_pretty(&CorridorResult {
|
||||||
|
total,
|
||||||
|
corridors: rows,
|
||||||
|
})
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user