diff --git a/src/physcom/engine/pipeline.py b/src/physcom/engine/pipeline.py index e01b455..bd3ff9f 100644 --- a/src/physcom/engine/pipeline.py +++ b/src/physcom/engine/pipeline.py @@ -925,13 +925,19 @@ class Pipeline: storage_mass: float, bounds_by_name: dict[str, MetricBound], units_by_name: dict[str, str], - cargo_capacity_kg: float, platform_mass: float | None = None, ) -> dict[str, float]: - """power_density/range_fuel/cost_efficiency for an EXPLICIT mass - allocation. `platform_mass` defaults to the platform's - representative mass (ctx.p_rep) -- pass an explicit value to - explore a specific weight class instead (see evaluate_allocation).""" + """power_density/range_fuel/cost_efficiency/cargo_capacity for an + EXPLICIT mass allocation. `platform_mass` defaults to the + platform's representative mass (ctx.p_rep) -- pass an explicit + value to explore a specific weight class instead (see + evaluate_allocation). Cargo capacity is derived from THIS build's + actual assembled mass (floor_total below), not a separate + declared-floor constant -- a deadweight/lightship-style ratio of + "how much this vehicle could additionally carry, proportional to + its own (empty of cargo) mass" only makes sense against the mass + it's actually built to, so it responds to the same optimizer/ + explore-slider choices every other metric here does.""" p_mass = ctx.p_rep if platform_mass is None else platform_mass out: dict[str, float] = {} floor_total = p_mass + actuator_mass + storage_mass @@ -940,6 +946,19 @@ class Pipeline: if "power_density" in bounds_by_name: out["power_density"] = power_density_value + # Deadweight/lightship-style cargo capacity, proportional to this + # build's own actual mass -- two conventions coexist because heavy + # freight/maritime vehicles genuinely carry a much larger multiple + # of their own mass in cargo than light personal/delivery vehicles + # do (see CARGO_KG_PER_STRUCTURAL_KG's module comment); which one + # a domain scores is just which metric_name it declares. + cargo_capacity_2_5x = floor_total * CARGO_KG_PER_STRUCTURAL_KG + cargo_capacity_0_3x = floor_total * 0.3 + if "cargo_capacity" in bounds_by_name: + out["cargo_capacity"] = cargo_capacity_2_5x + if "cargo_capacity_kg" in bounds_by_name: + out["cargo_capacity_kg"] = cargo_capacity_0_3x + # Achieved steady-state cruise speed, DERIVED from this specific # build's actual power_density, the medium's mass-proportional # resistance, and (ground only, see DRAG_POWER_COEFF_BY_MEDIUM) a @@ -994,7 +1013,13 @@ class Pipeline: cost_per_m = amortized_per_m + operating_per_m if units_by_name.get("cost_efficiency") == "$/(kg·m)": - out["cost_efficiency"] = cost_per_m / max(cargo_capacity_kg, 1.0) + # Divide by whichever cargo convention this domain actually + # scores, so cost-per-cargo-kg and the cargo_capacity number + # shown alongside it always agree; default to the heavy- + # vehicle ratio if a domain scores $/(kg·m) without scoring + # either cargo metric explicitly (matches prior behavior). + cargo_basis = out.get("cargo_capacity_kg", out.get("cargo_capacity", cargo_capacity_2_5x)) + out["cost_efficiency"] = cost_per_m / max(cargo_basis, 1.0) else: out["cost_efficiency"] = cost_per_m @@ -1005,7 +1030,6 @@ class Pipeline: ctx: "_PhysicsContext", bounds_by_name: dict[str, MetricBound], units_by_name: dict[str, str], - cargo_capacity_kg: float, ) -> tuple[float, float, float, bool]: """Pick the platform/actuator/storage mass for the build this domain actually scores. First, the platform's declared physical @@ -1142,7 +1166,7 @@ class Pipeline: def objective(platform_mass: float, actuator_mass: float, storage_mass: float) -> float: raw = self._raw_physics_from_masses( ctx, actuator_mass, storage_mass, - bounds_by_name, units_by_name, cargo_capacity_kg, + bounds_by_name, units_by_name, platform_mass=platform_mass, ) scores, weights = [], [] @@ -1285,7 +1309,6 @@ class Pipeline: # drives the untouched blocks below). power_density = 0.0 # W/kg energy_density = 0.0 # J/kg - mass_total = 0.0 # kg, extensive — components share one vehicle thrust_profile: str | None = None energy_form: str | None = None infra_matches: list[float] = [] @@ -1295,8 +1318,6 @@ class Pipeline: power_density = max(power_density, float(dep.value)) if dep.key == "energy_density" and dep.constraint_type == "provides": energy_density = max(energy_density, float(dep.value)) - if dep.key == "mass" and dep.constraint_type == "range_min": - mass_total += float(dep.value) if dep.key == "thrust_profile" and dep.constraint_type == "provides": thrust_profile = dep.value if dep.key == "energy_form" and dep.constraint_type == "requires": @@ -1305,20 +1326,19 @@ class Pipeline: match = INFRASTRUCTURE_AVAILABILITY.get((dep.key, dep.value)) if match is not None: infra_matches.append(match) - mass = mass_total if mass_total > 0 else 100.0 # kg, default if undeclared - cargo_capacity_kg = mass * CARGO_KG_PER_STRUCTURAL_KG # ── platform/actuator/storage-specific extraction, for - # power_density / range_fuel / cost_efficiency only ────────────── + # power_density / range_fuel / cost_efficiency / cargo_capacity + # only ───────────────────────────────────────────────────────── ctx = self._physics_context(combo, bounds_by_name) feasible = True if ctx is not None: actuator_mass, storage_mass, platform_mass, feasible = self._decide_masses( - ctx, bounds_by_name, units_by_name, cargo_capacity_kg + ctx, bounds_by_name, units_by_name ) raw.update(self._raw_physics_from_masses( ctx, actuator_mass, storage_mass, - bounds_by_name, units_by_name, cargo_capacity_kg, + bounds_by_name, units_by_name, platform_mass=platform_mass, )) @@ -1340,11 +1360,8 @@ class Pipeline: if "range_degradation" in raw: raw["range_degradation"] = 365 * 86400 - if "cargo_capacity" in raw: - raw["cargo_capacity"] = cargo_capacity_kg - - if "cargo_capacity_kg" in raw: - raw["cargo_capacity_kg"] = mass * 0.3 + # cargo_capacity / cargo_capacity_kg are set by _raw_physics_from_masses + # above, from the actual build mass -- not recomputed here. if "environmental_impact" in raw: raw["environmental_impact"] = max(0.0, power_density * 2e-7) @@ -1391,9 +1408,8 @@ class Pipeline: if ctx is None or ctx.p_max is None: return None - cargo_capacity_kg = (ctx.p_min + ctx.a_min + ctx.s_min) * CARGO_KG_PER_STRUCTURAL_KG default_actuator, default_storage, default_platform, _feasible = self._decide_masses( - ctx, bounds_by_name, units_by_name, cargo_capacity_kg + ctx, bounds_by_name, units_by_name ) p_mass = default_platform if platform_mass is None else platform_mass a_mass = default_actuator if actuator_mass is None else actuator_mass @@ -1405,7 +1421,7 @@ class Pipeline: raw = self._raw_physics_from_masses( ctx, a_mass, s_mass, - bounds_by_name, units_by_name, cargo_capacity_kg, + bounds_by_name, units_by_name, platform_mass=p_mass, )