diff --git a/src/physcom/engine/pipeline.py b/src/physcom/engine/pipeline.py index bd2ca47..77558be 100644 --- a/src/physcom/engine/pipeline.py +++ b/src/physcom/engine/pipeline.py @@ -106,6 +106,16 @@ ENERGY_FORM_RELIABILITY: dict[str, float] = { # validated it against the platform's ceiling), so it's used as the point # estimate rather than an invented one. +# Radiation-pressure actuators (solar sails) don't declare a "mass" at +# all -- thrust scales with sail area, not carried mass -- so their +# effective mass is derived from declared footprint via a thin deployable +# sail film's areal density. Used the same way as BIOLOGICAL_OPERATOR_MASS_KG +# below: converts the entity's declared footprint FLOOR into a mass floor, +# not a fixed value -- above it, effective mass is a free, budget-competing +# variable like any other actuator (bigger sail = more collected power), +# sized by the same joint optimizer, not a one-off product spec. +SAIL_AREAL_DENSITY_KG_PER_M2: float = 0.05 + # Human/animal actuators declare mass_min=0 (there's no minimum purchase # quantity for a rider the way there is for an engine), but treated as a # literal floor that lets the optimizer size a payload down toward 0kg of @@ -187,13 +197,16 @@ def _solve_two_requirement_masses( return a_min, s_min return max(a, a_min), max(s, s_min) -# Ambient energy forms (sun, wind, gravity, food) aren't a depletable -# onboard store the way a fuel tank is -- "distance before running out" -# doesn't apply (a sailboat doesn't run out of wind). Rather than -# degenerate to 0 (mass_min=0, energy_density often undeclared entirely), -# range_fuel reports the domain's own declared ceiling for these: full -# marks is the physically honest answer, not an error. -AMBIENT_ENERGY_FORMS: set[str] = {"biological", "wind", "radiation_pressure", "gravitational"} +# Ambient energy forms (sun, wind, gravity) aren't a depletable onboard +# store the way a fuel tank is -- "distance before running out" doesn't +# apply (a sailboat doesn't run out of wind). Rather than degenerate to 0 +# (mass_min=0, energy_density often undeclared entirely), range_fuel +# reports the domain's own declared ceiling for these: full marks is the +# physically honest answer, not an error. Food is deliberately NOT here: +# stopping to eat is a resupply, the same category as refuelling a tank, +# not a genuinely external/inexhaustible power source -- Biological Feed +# uses the normal storage-mass-limited range_fuel formula. +AMBIENT_ENERGY_FORMS: set[str] = {"wind", "radiation_pressure", "gravitational"} # Resistive energy cost of travel, J per kg of vehicle per meter -- # rolling resistance for ground vehicles, cruise-flight lift/drag for @@ -931,13 +944,16 @@ class Pipeline: performance target (accel/thrust, or target_velocity/resistance) sets a FLOOR -- a rotorcraft that can't produce enough thrust to hover isn't a rotorcraft, regardless of how a smaller/cheaper - engine might score. Biological actuators (a rider's own body) get - the same treatment with one addition: BIOLOGICAL_OPERATOR_MASS_KG - sets a floor under the floor -- at least one real operator, even if - the performance-derived requirement would otherwise ask for less - -- but above that, mass is a free variable exactly like a - mechanical actuator's; "bigger" here means more or bigger - operators, not a fixed physiological constant. That floor also + engine might score. Biological actuators (a rider's own body) and + radiation-pressure actuators (a solar sail) get the same treatment + with one addition: BIOLOGICAL_OPERATOR_MASS_KG / a footprint-derived + floor (see SAIL_AREAL_DENSITY_KG_PER_M2) sets a floor under the + floor -- at least one real operator, or the sail's own declared + minimum footprint, even if the performance-derived requirement + would otherwise ask for less -- but above that, mass is a free + variable exactly like a mechanical actuator's; "bigger" means more + or bigger operators, or a bigger sail, not a fixed constant. That + floor also sets the smallest platform mass that could structurally carry it (CARGO_KG_PER_STRUCTURAL_KG again, applied to the platform carrying its own actuator+storage instead of cargo) -- below that, @@ -970,16 +986,6 @@ class Pipeline: return float(dep.value) return None - if ctx.actuator_energy_form == "radiation_pressure": - # thrust scales with sail area, not carried mass -- derive an - # effective mass from declared footprint and a thin-film areal - # density estimate rather than the (undeclared) mass attribute. - # No meaningful "bigger sail" mass slider here (area-driven, - # not budget-driven), so this stays its own case. - footprint = dep_value(ctx.actuator, "footprint", "range_min") or 0.0 - actuator_mass = footprint * 0.05 # kg/m^2, thin deployable sail film - return actuator_mass, ctx.s_min, ctx.p_rep, True - # Step 1: the required floor (same solve as before -- now a floor # for the search below, not the final answer). min_accel = dep_value(ctx.platform, "min_effective_accel", "range_min") @@ -1026,6 +1032,13 @@ class Pipeline: # performance solve above would have asked for -- see the # BIOLOGICAL_OPERATOR_MASS_KG module comment. required_actuator = max(required_actuator, BIOLOGICAL_OPERATOR_MASS_KG[ctx.actuator_energy_form]) + elif ctx.actuator_energy_form == "radiation_pressure": + # At least the entity's own declared minimum sail footprint, + # regardless of what the bare performance solve above would + # have asked for -- see the SAIL_AREAL_DENSITY_KG_PER_M2 + # module comment. + footprint_floor = dep_value(ctx.actuator, "footprint", "range_min") or 0.0 + required_actuator = max(required_actuator, footprint_floor * SAIL_AREAL_DENSITY_KG_PER_M2) if ctx.p_max is None: # No declared mass ceiling (e.g. Spaceship) -- no bounded @@ -1296,20 +1309,19 @@ class Pipeline: minimum (platform is also ceiling-clamped to its declared max) -- never silently allowed below what pass 1 would have rejected. - Returns None for combos with no free actuator mass to explore - (radiation-pressure sails -- thrust is area-driven, not a mass - choice, see _decide_masses) or with no declared platform mass - ceiling to bound a weight-class slider. Biological actuators DO - get sliders: rider/operator mass is a real, budget-competing - variable like any other actuator (see BIOLOGICAL_OPERATOR_MASS_KG). + Returns None only for combos with no declared platform mass + ceiling to bound a weight-class slider (e.g. Spaceship). Every + actuator type gets sliders, including biological (rider/operator + mass, see BIOLOGICAL_OPERATOR_MASS_KG) and radiation-pressure + (effective sail mass derived from footprint, see + SAIL_AREAL_DENSITY_KG_PER_M2) -- both are real, budget-competing + variables like any mechanical actuator's mass. """ bounds_by_name = {mb.metric_name: mb for mb in domain.metric_bounds} units_by_name = {mb.metric_name: mb.unit for mb in domain.metric_bounds} ctx = self._physics_context(combo, bounds_by_name) if ctx is None or ctx.p_max is None: return None - if ctx.actuator_energy_form == "radiation_pressure": - 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( diff --git a/src/physcom_web/templates/results/_explore_result.html b/src/physcom_web/templates/results/_explore_result.html index dad062f..8278b7d 100644 --- a/src/physcom_web/templates/results/_explore_result.html +++ b/src/physcom_web/templates/results/_explore_result.html @@ -1,8 +1,6 @@ {% if explore_result is none %}

No free mass allocation to explore for this combination — its -actuator's mass isn't a design choice (a footprint-derived quantity, like a -radiation-pressure sail), or the platform has no declared mass ceiling to -bound the sliders.

+platform has no declared mass ceiling to bound the sliders.

{% else %} {% set r = explore_result %}
diff --git a/src/physcom_web/templates/results/detail.html b/src/physcom_web/templates/results/detail.html index dae8af3..6d8b343 100644 --- a/src/physcom_web/templates/results/detail.html +++ b/src/physcom_web/templates/results/detail.html @@ -155,7 +155,7 @@ {{ "%.1f"|format(r.platform_mass) }}kg
- +