feat(@projects/@magic-civilization): ✨ update p2 objectives statuses
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
f0ddc1669b
commit
ed77f92011
6 changed files with 226 additions and 19 deletions
|
|
@ -16,9 +16,9 @@
|
|||
|---|---|---|---|---|---|---|---|
|
||||
| **P0** | 43 | 0 | 0 | 0 | 0 | 0 | 43 |
|
||||
| **P1** | 43 | 1 | 7 | 0 | 14 | 1 | 66 |
|
||||
| **P2** | 46 | 1 | 6 | 1 | 7 | 6 | 67 |
|
||||
| **P2** | 48 | 1 | 5 | 1 | 6 | 6 | 67 |
|
||||
| **P3 (oos)** | 3 | 0 | 0 | 0 | 1 | 19 | 23 |
|
||||
| **total** | **135** | **2** | **13** | **1** | **22** | **26** | **199** |
|
||||
| **total** | **137** | **2** | **12** | **1** | **21** | **26** | **199** |
|
||||
|
||||
</td><td valign='top' style='padding-left:2em'>
|
||||
|
||||
|
|
@ -29,7 +29,7 @@
|
|||
| [warcouncil](../team-leads/warcouncil.md) | 6 |
|
||||
| [asset-sprite](../team-leads/asset-sprite.md) | 6 |
|
||||
| [shipwright](../team-leads/shipwright.md) | 5 |
|
||||
| [combat-dev](../team-leads/combat-dev.md) | 4 |
|
||||
| [combat-dev](../team-leads/combat-dev.md) | 2 |
|
||||
| [terraformer](../team-leads/terraformer.md) | 2 |
|
||||
| [simulator-infra](../team-leads/simulator-infra.md) | 1 |
|
||||
| [asset-audio](../team-leads/asset-audio.md) | 1 |
|
||||
|
|
@ -206,15 +206,15 @@
|
|||
| [p2-50](p2-50-rng-determinism-pin.md) | ✅ done | Deterministic RNG + seed-derivation pin across mc-mapgen / mc-climate / mc-ecology | [terraformer](../team-leads/terraformer.md) | 2026-05-01 |
|
||||
| [p2-51](p2-51-world-shape-knobs.md) | ✅ done | Player-facing world-shape parameters on new-game screen | [terraformer](../team-leads/terraformer.md) | 2026-05-01 |
|
||||
| [p2-52](p2-52-substrate-flora-cover-ontology-split.md) | ✅ done | Split terrain enum into substrate × flora-cover layers (resolve biome ontology) | [terraformer](../team-leads/terraformer.md) | 2026-05-01 |
|
||||
| [p2-53](p2-53-action-vocabulary-design-game-gap.md) | ❌ missing | Action vocabulary — gap analysis between design page and shipped Rust/Godot game | [wireguard](../team-leads/wireguard.md) | 2026-05-01 |
|
||||
| [p2-53](p2-53-action-vocabulary-design-game-gap.md) | 🟡 partial | Action vocabulary — gap analysis between design page and shipped Rust/Godot game | [wireguard](../team-leads/wireguard.md) | 2026-05-03 |
|
||||
| [p2-53a](p2-53a-sentry-guard-action-kind.md) | ✅ done | Sentry/Guard ActionKind — add Sentry/Unsentry to mc-core with wake-on-vision | [wireguard](../team-leads/wireguard.md) | 2026-05-01 |
|
||||
| [p2-53b](p2-53b-building-action-registry.md) | ✅ done | Building action registry — `BuildingActionKind`, `building_actions.json`, `GdBuildingActions` bridge | [simulator-infra](../team-leads/simulator-infra.md) | 2026-05-01 |
|
||||
| [p2-53c](p2-53c-rally-vocabulary-expansion.md) | ✅ done | Rally vocabulary expansion — Hold / Fortify / JoinFormation + two-waypoint Patrol | [shipwright](../team-leads/shipwright.md) | 2026-05-01 |
|
||||
| [p2-53d](p2-53d-building-specifics.md) | ✅ done | Building specifics — Garrison, Repair, Toggle Active + 18 archetype-specific actions | [shipwright](../team-leads/shipwright.md) | 2026-05-03 |
|
||||
| [p2-53e](p2-53e-siege-pillage-embark.md) | 🟡 partial | Siege handlers (Pack/Deploy/Bombard) + Pillage UI wiring + Embark/Disembark handlers | [combat-dev](../team-leads/combat-dev.md) | 2026-05-01 |
|
||||
| [p2-53f](p2-53f-infantry-specifics.md) | ✅ done | Infantry specifics — Shield Wall, Brace, Shove, Rage, Cleave, War Cry | [combat-dev](../team-leads/combat-dev.md) | 2026-05-01 |
|
||||
| [p2-53g](p2-53g-ranged-specifics.md) | 🟡 partial | Ranged specifics — Volley, Aimed Shot, Fire Arrows | [combat-dev](../team-leads/combat-dev.md) | 2026-05-01 |
|
||||
| [p2-53h](p2-53h-cavalry-specifics.md) | 🟡 partial | Cavalry specifics — Charge, Pursue, Wheel | [combat-dev](../team-leads/combat-dev.md) | 2026-05-01 |
|
||||
| [p2-53g](p2-53g-ranged-specifics.md) | ✅ done | Ranged specifics — Volley, Aimed Shot, Fire Arrows | [combat-dev](../team-leads/combat-dev.md) | 2026-05-03 |
|
||||
| [p2-53h](p2-53h-cavalry-specifics.md) | ✅ done | Cavalry specifics — Charge, Pursue, Wheel | [combat-dev](../team-leads/combat-dev.md) | 2026-05-03 |
|
||||
| [p2-53i](p2-53i-engineer-pioneer-medic-scout.md) | ✅ done | Support specifics — Engineer, Pioneer, Medic, Scout | [shipwright](../team-leads/shipwright.md) | 2026-05-03 |
|
||||
| [p2-54](p2-54-resource-visibility-three-axis.md) | 🔵 in_progress | Resource visibility — three-axis (visibility/yield_gate/improvement_gate) refactor | [terraformer](../team-leads/terraformer.md) | 2026-05-01 |
|
||||
| [p2-54a](p2-54a-deposits-three-axis-migration.md) | ✅ done | Migrate deposits/*.json to three-axis visibility schema | [terraformer](../team-leads/terraformer.md) | 2026-05-01 |
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
id: p2-54c
|
||||
title: Renderer reads observations + indicator decorations for tech-gated resources
|
||||
priority: p2
|
||||
status: partial
|
||||
status: done
|
||||
scope: game1
|
||||
owner: terraformer
|
||||
updated_at: 2026-05-01
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"generated_at": "2026-05-03T00:07:00Z",
|
||||
"generated_at": "2026-05-03T01:01:16Z",
|
||||
"totals": {
|
||||
"in_progress": 2,
|
||||
"missing": 22,
|
||||
"stub": 1,
|
||||
"partial": 12,
|
||||
"oos": 26,
|
||||
"partial": 13,
|
||||
"done": 135,
|
||||
"in_progress": 2,
|
||||
"missing": 21,
|
||||
"stub": 1,
|
||||
"done": 137,
|
||||
"total": 199
|
||||
},
|
||||
"objectives": [
|
||||
|
|
@ -1624,10 +1624,10 @@
|
|||
"id": "p2-53",
|
||||
"title": "Action vocabulary — gap analysis between design page and shipped Rust/Godot game",
|
||||
"priority": "p2",
|
||||
"status": "missing",
|
||||
"status": "partial",
|
||||
"scope": "game1",
|
||||
"owner": "wireguard",
|
||||
"updated_at": "2026-05-01",
|
||||
"updated_at": "2026-05-03",
|
||||
"summary": "The design page at `/unit-actions` (`.project/designs/app/src/pages/UnitActions.tsx`) curates an exemplar per unit/building category and lists per-archetype action vocabularies. Cross-checking that vocabulary against the shipped Rust action registry (`mc-core/src/action.rs::ActionKind`), the JSON capability map (`unit_actions.json`), and the Godot panel (`scenes/hud/unit_panel.gd`) reveals four classes of gap. This objective is the gap analysis only — implementation work splits out into child objectives once the design vocabulary is ratified."
|
||||
},
|
||||
{
|
||||
|
|
@ -1694,20 +1694,20 @@
|
|||
"id": "p2-53g",
|
||||
"title": "Ranged specifics — Volley, Aimed Shot, Fire Arrows",
|
||||
"priority": "p2",
|
||||
"status": "partial",
|
||||
"status": "done",
|
||||
"scope": "game1",
|
||||
"owner": "combat-dev",
|
||||
"updated_at": "2026-05-01",
|
||||
"updated_at": "2026-05-03",
|
||||
"summary": "Three new `ActionKind` variants gating on `keywords: [\"ranged\"]`:\n\n- **Volley** — area attack: hits target hex's centre + 2 random edge slots; lower per-target damage, more chances to hit\n- **Aimed Shot** — skip-turn that ignores 50% of target's defence on next attack; sets `aimed_shot_pending = true`\n- **Fire Arrows** — toggle posture; ranged attacks ignite tile (uses Fire system from `mc-ecology` if available); persistent damage, smoke obscures vision"
|
||||
},
|
||||
{
|
||||
"id": "p2-53h",
|
||||
"title": "Cavalry specifics — Charge, Pursue, Wheel",
|
||||
"priority": "p2",
|
||||
"status": "partial",
|
||||
"status": "done",
|
||||
"scope": "game1",
|
||||
"owner": "combat-dev",
|
||||
"updated_at": "2026-05-01",
|
||||
"updated_at": "2026-05-03",
|
||||
"summary": "Three new `ActionKind` variants gating on `keywords: [\"cavalry\"]`:\n\n- **Charge** — move 2+ hexes in straight line, then attack: +30% damage; may push target back; cancellable by Brace (p2-53f)\n- **Pursue** — if target dies/routs, advance into its hex without spending movement (passive trigger; manual confirm if multiple options)\n- **Wheel** — reorient to a new edge slot without leaving the hex; useful to dodge first-strike"
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ use mc_climate::{ClimatePhysics, EcologyPhysics, step_atmospheric_chemistry};
|
|||
use mc_mapgen::{MapGenerator, seed::{derive as derive_seed, SeedDomain}};
|
||||
use mc_save::{PlayerObservations, TileObservation};
|
||||
|
||||
pub mod resources;
|
||||
|
||||
/// WASM-exposed grid handle wrapping GridState.
|
||||
#[wasm_bindgen]
|
||||
pub struct WasmGrid {
|
||||
|
|
@ -304,6 +306,15 @@ impl WasmGrid {
|
|||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// Return resource metadata for a single tile as a JSON string.
|
||||
/// Includes visibility, yield_gate, and indicator_decorations from the
|
||||
/// 3-axis schema (p2-54). Returns `null` if the tile has no resource or
|
||||
/// the coordinates are out of range.
|
||||
#[wasm_bindgen(js_name = "tileResourceJson")]
|
||||
pub fn tile_resource_json(&self, col: i32, row: i32) -> Option<String> {
|
||||
crate::resources::tile_resource_json_impl(&self.inner, col, row)
|
||||
}
|
||||
}
|
||||
|
||||
/// WASM-exposed climate physics engine.
|
||||
|
|
|
|||
154
src/simulator/api-wasm/src/resources.rs
Normal file
154
src/simulator/api-wasm/src/resources.rs
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
//! Resource catalog WASM bridge — exposes per-tile resource metadata
|
||||
//! including the 3-axis visibility schema landed in p2-54.
|
||||
//!
|
||||
//! The catalog is baked at compile time from `public/resources/resources.json`
|
||||
//! and parsed once via `OnceLock` on first access.
|
||||
|
||||
use mc_core::grid::GridState;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
struct CatalogEntry {
|
||||
id: String,
|
||||
name: String,
|
||||
#[serde(default)]
|
||||
visibility: mc_core::resources::Visibility,
|
||||
#[serde(default)]
|
||||
yield_gate: Option<String>,
|
||||
#[serde(default)]
|
||||
indicator_decorations: Vec<IndicatorDecorationRaw>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
struct IndicatorDecorationRaw {
|
||||
decoration_id: String,
|
||||
#[allow(dead_code)]
|
||||
name: String,
|
||||
#[allow(dead_code)]
|
||||
description: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct ResourcesJson {
|
||||
#[serde(default)]
|
||||
bonus: Vec<CatalogEntry>,
|
||||
#[serde(default)]
|
||||
luxury: Vec<CatalogEntry>,
|
||||
#[serde(default)]
|
||||
strategic: Vec<CatalogEntry>,
|
||||
}
|
||||
|
||||
static CATALOG: OnceLock<HashMap<String, CatalogEntry>> = OnceLock::new();
|
||||
|
||||
fn catalog() -> &'static HashMap<String, CatalogEntry> {
|
||||
CATALOG.get_or_init(|| {
|
||||
const JSON: &str = include_str!("../../../../public/resources/resources.json");
|
||||
let bundle: ResourcesJson = serde_json::from_str(JSON).unwrap_or(ResourcesJson {
|
||||
bonus: vec![],
|
||||
luxury: vec![],
|
||||
strategic: vec![],
|
||||
});
|
||||
bundle
|
||||
.bonus
|
||||
.into_iter()
|
||||
.chain(bundle.luxury)
|
||||
.chain(bundle.strategic)
|
||||
.map(|e| (e.id.clone(), e))
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct TileResourceDto {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub visibility: String,
|
||||
pub yield_gate: Option<String>,
|
||||
pub indicator_decorations: Vec<String>,
|
||||
}
|
||||
|
||||
pub fn tile_resource_json_impl(grid: &GridState, col: i32, row: i32) -> Option<String> {
|
||||
let tile = grid.tile(col, row)?;
|
||||
if tile.resource_id.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let entry = catalog().get(&tile.resource_id)?;
|
||||
let dto = TileResourceDto {
|
||||
id: entry.id.clone(),
|
||||
name: entry.name.clone(),
|
||||
visibility: visibility_str(&entry.visibility).to_owned(),
|
||||
yield_gate: entry.yield_gate.clone(),
|
||||
indicator_decorations: entry
|
||||
.indicator_decorations
|
||||
.iter()
|
||||
.map(|d| d.decoration_id.clone())
|
||||
.collect(),
|
||||
};
|
||||
serde_json::to_string(&dto).ok()
|
||||
}
|
||||
|
||||
fn visibility_str(v: &mc_core::resources::Visibility) -> &'static str {
|
||||
match v {
|
||||
mc_core::resources::Visibility::Always => "always",
|
||||
mc_core::resources::Visibility::Scout => "scout",
|
||||
mc_core::resources::Visibility::TechGated => "tech_gated",
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use mc_core::grid::GridState;
|
||||
|
||||
fn make_grid_with_resource(resource_id: &str) -> GridState {
|
||||
let mut g = GridState::new(2, 2);
|
||||
g.tiles[0].resource_id = resource_id.to_owned();
|
||||
g
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn iron_deposit_returns_correct_dto() {
|
||||
let grid = make_grid_with_resource("iron");
|
||||
let json = tile_resource_json_impl(&grid, 0, 0).expect("should return Some for iron");
|
||||
let val: serde_json::Value = serde_json::from_str(&json).unwrap();
|
||||
assert_eq!(val["id"], "iron");
|
||||
assert_eq!(val["visibility"], "tech_gated");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn always_visible_resource_has_no_decorations() {
|
||||
let grid = make_grid_with_resource("deer");
|
||||
let json = tile_resource_json_impl(&grid, 0, 0)
|
||||
.expect("should return Some for deer");
|
||||
let val: serde_json::Value = serde_json::from_str(&json).unwrap();
|
||||
assert_eq!(val["visibility"], "always");
|
||||
assert_eq!(val["indicator_decorations"].as_array().unwrap().len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_resource_id_returns_none() {
|
||||
let grid = make_grid_with_resource("");
|
||||
assert!(tile_resource_json_impl(&grid, 0, 0).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn out_of_bounds_returns_none() {
|
||||
let grid = GridState::new(2, 2);
|
||||
assert!(tile_resource_json_impl(&grid, 99, 99).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_resource_id_returns_none() {
|
||||
let grid = make_grid_with_resource("dragon_bones");
|
||||
assert!(tile_resource_json_impl(&grid, 0, 0).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn catalog_loads_known_entries() {
|
||||
let cat = catalog();
|
||||
assert!(cat.contains_key("iron"));
|
||||
assert!(cat.contains_key("furs"));
|
||||
}
|
||||
}
|
||||
|
|
@ -369,6 +369,12 @@ impl TurnProcessor {
|
|||
// their rally destination (Hold/Fortify/JoinFormation).
|
||||
Self::apply_rally_arrival_actions(state);
|
||||
|
||||
// Phase 5a-pillage: drain GDScript-queued Pillage requests (worker units).
|
||||
// Runs before bombard so the order is: pillage (worker), bombard (siege),
|
||||
// volley (ranged AoE), charge (cavalry melee). Each phase has been
|
||||
// confirmed to leave subsequent phases' inputs valid.
|
||||
Self::process_pillage_requests(state, &mut result);
|
||||
|
||||
// Phase 5a-bombard: drain GDScript-queued Bombard requests (siege units).
|
||||
Self::process_bombard_requests(state, &mut result);
|
||||
|
||||
|
|
@ -1975,6 +1981,42 @@ impl TurnProcessor {
|
|||
///
|
||||
/// Calls `mc_combat::siege::resolve_bombard` for each request; applies damage
|
||||
/// to any unit or city on the target hex. The queue is cleared after drain.
|
||||
/// Drain `GameState::pending_pillage_requests`.
|
||||
///
|
||||
/// Each request: validate the worker unit exists; call
|
||||
/// `GameState::pillage_improvement(col, row)`. Severable improvements stay
|
||||
/// with `pillaged = true`; non-severable get removed entirely. Negative or
|
||||
/// out-of-`u16`-range coordinates skip silently. Bad player or unit indices
|
||||
/// skip silently. The TurnResult counter `improvements_pillaged` records
|
||||
/// each successful pillage for telemetry.
|
||||
fn process_pillage_requests(state: &mut GameState, result: &mut TurnResult) {
|
||||
let requests = std::mem::take(&mut state.pending_pillage_requests);
|
||||
for req in requests {
|
||||
let pi = req.player_index as usize;
|
||||
if pi >= state.players.len() {
|
||||
continue;
|
||||
}
|
||||
if state.players[pi].units.get(req.unit_index).is_none() {
|
||||
continue;
|
||||
}
|
||||
// Reject negative or out-of-u16 coords (tile_improvements is keyed on u16).
|
||||
let Ok(col) = u16::try_from(req.target_col) else {
|
||||
continue;
|
||||
};
|
||||
let Ok(row) = u16::try_from(req.target_row) else {
|
||||
continue;
|
||||
};
|
||||
// pillage_improvement returns true for severable (marked but kept),
|
||||
// false for non-severable (removed) or no improvement. Either of the
|
||||
// first two counts as a successful pillage event.
|
||||
let had_improvement = state.tile_improvements.contains_key(&(col, row));
|
||||
state.pillage_improvement(col, row);
|
||||
if had_improvement {
|
||||
result.improvements_pillaged = result.improvements_pillaged.saturating_add(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn process_bombard_requests(state: &mut GameState, result: &mut TurnResult) {
|
||||
use mc_combat::siege::{resolve_bombard, BombardTarget};
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue