remove food from ambient energy forms, size solar sails to actual power needs
Biological Feed (food) was treated as an "ambient" energy source, so range_fuel always reported the domain's ceiling regardless of how much food was carried -- stopping to eat is a resupply, the same category as refuelling a tank, not a genuinely external/inexhaustible source like sun or wind. Removed "biological" from AMBIENT_ENERGY_FORMS; food now uses the normal storage-mass-limited range formula like any fuel. Solar Sail was still special-cased to a fixed footprint-derived mass (100m^2 -> 5kg) regardless of what a domain's power/velocity target actually needed -- the same "fixed reference instead of a requirement floor" bug biological actuators had before last session's fix. Folded it into the same general requirement-floor + joint-optimizer path: declared footprint becomes a FLOOR (SAIL_AREAL_DENSITY_KG_PER_M2), not a fixed value, so sail size scales with what's actually needed -- "enough panels to supply enough power for actuator impulse" is now enforced the same way structural/mass-ceiling requirements already are, instead of relying on a product-spec constant that happened to work or not. No actuator type is special-cased for mass sizing anymore. Confirmed the fix surfaces an honest result rather than hiding one: a solar sail's declared 0.01 W/kg specific power can never reach interplanetary_travel's 10 W/kg power_density floor at any sail size (the ratio is capped by the sail's own power_density regardless of scale), so it correctly still scores 0 there -- a real technology/domain mismatch, not a sizing bug. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -106,6 +106,16 @@ ENERGY_FORM_RELIABILITY: dict[str, float] = {
|
|||||||
# validated it against the platform's ceiling), so it's used as the point
|
# validated it against the platform's ceiling), so it's used as the point
|
||||||
# estimate rather than an invented one.
|
# 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
|
# 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
|
# 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
|
# 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 a_min, s_min
|
||||||
return max(a, a_min), max(s, s_min)
|
return max(a, a_min), max(s, s_min)
|
||||||
|
|
||||||
# Ambient energy forms (sun, wind, gravity, food) aren't a depletable
|
# Ambient energy forms (sun, wind, gravity) aren't a depletable onboard
|
||||||
# onboard store the way a fuel tank is -- "distance before running out"
|
# store the way a fuel tank is -- "distance before running out" doesn't
|
||||||
# doesn't apply (a sailboat doesn't run out of wind). Rather than
|
# apply (a sailboat doesn't run out of wind). Rather than degenerate to 0
|
||||||
# degenerate to 0 (mass_min=0, energy_density often undeclared entirely),
|
# (mass_min=0, energy_density often undeclared entirely), range_fuel
|
||||||
# range_fuel reports the domain's own declared ceiling for these: full
|
# reports the domain's own declared ceiling for these: full marks is the
|
||||||
# marks is the physically honest answer, not an error.
|
# physically honest answer, not an error. Food is deliberately NOT here:
|
||||||
AMBIENT_ENERGY_FORMS: set[str] = {"biological", "wind", "radiation_pressure", "gravitational"}
|
# 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 --
|
# Resistive energy cost of travel, J per kg of vehicle per meter --
|
||||||
# rolling resistance for ground vehicles, cruise-flight lift/drag for
|
# rolling resistance for ground vehicles, cruise-flight lift/drag for
|
||||||
@@ -931,13 +944,16 @@ class Pipeline:
|
|||||||
performance target (accel/thrust, or target_velocity/resistance)
|
performance target (accel/thrust, or target_velocity/resistance)
|
||||||
sets a FLOOR -- a rotorcraft that can't produce enough thrust to
|
sets a FLOOR -- a rotorcraft that can't produce enough thrust to
|
||||||
hover isn't a rotorcraft, regardless of how a smaller/cheaper
|
hover isn't a rotorcraft, regardless of how a smaller/cheaper
|
||||||
engine might score. Biological actuators (a rider's own body) get
|
engine might score. Biological actuators (a rider's own body) and
|
||||||
the same treatment with one addition: BIOLOGICAL_OPERATOR_MASS_KG
|
radiation-pressure actuators (a solar sail) get the same treatment
|
||||||
sets a floor under the floor -- at least one real operator, even if
|
with one addition: BIOLOGICAL_OPERATOR_MASS_KG / a footprint-derived
|
||||||
the performance-derived requirement would otherwise ask for less
|
floor (see SAIL_AREAL_DENSITY_KG_PER_M2) sets a floor under the
|
||||||
-- but above that, mass is a free variable exactly like a
|
floor -- at least one real operator, or the sail's own declared
|
||||||
mechanical actuator's; "bigger" here means more or bigger
|
minimum footprint, even if the performance-derived requirement
|
||||||
operators, not a fixed physiological constant. That floor also
|
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
|
sets the smallest platform mass that could structurally carry it
|
||||||
(CARGO_KG_PER_STRUCTURAL_KG again, applied to the platform
|
(CARGO_KG_PER_STRUCTURAL_KG again, applied to the platform
|
||||||
carrying its own actuator+storage instead of cargo) -- below that,
|
carrying its own actuator+storage instead of cargo) -- below that,
|
||||||
@@ -970,16 +986,6 @@ class Pipeline:
|
|||||||
return float(dep.value)
|
return float(dep.value)
|
||||||
return None
|
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
|
# Step 1: the required floor (same solve as before -- now a floor
|
||||||
# for the search below, not the final answer).
|
# for the search below, not the final answer).
|
||||||
min_accel = dep_value(ctx.platform, "min_effective_accel", "range_min")
|
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
|
# performance solve above would have asked for -- see the
|
||||||
# BIOLOGICAL_OPERATOR_MASS_KG module comment.
|
# BIOLOGICAL_OPERATOR_MASS_KG module comment.
|
||||||
required_actuator = max(required_actuator, BIOLOGICAL_OPERATOR_MASS_KG[ctx.actuator_energy_form])
|
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:
|
if ctx.p_max is None:
|
||||||
# No declared mass ceiling (e.g. Spaceship) -- no bounded
|
# 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) --
|
minimum (platform is also ceiling-clamped to its declared max) --
|
||||||
never silently allowed below what pass 1 would have rejected.
|
never silently allowed below what pass 1 would have rejected.
|
||||||
|
|
||||||
Returns None for combos with no free actuator mass to explore
|
Returns None only for combos with no declared platform mass
|
||||||
(radiation-pressure sails -- thrust is area-driven, not a mass
|
ceiling to bound a weight-class slider (e.g. Spaceship). Every
|
||||||
choice, see _decide_masses) or with no declared platform mass
|
actuator type gets sliders, including biological (rider/operator
|
||||||
ceiling to bound a weight-class slider. Biological actuators DO
|
mass, see BIOLOGICAL_OPERATOR_MASS_KG) and radiation-pressure
|
||||||
get sliders: rider/operator mass is a real, budget-competing
|
(effective sail mass derived from footprint, see
|
||||||
variable like any other actuator (see BIOLOGICAL_OPERATOR_MASS_KG).
|
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}
|
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}
|
units_by_name = {mb.metric_name: mb.unit for mb in domain.metric_bounds}
|
||||||
ctx = self._physics_context(combo, bounds_by_name)
|
ctx = self._physics_context(combo, bounds_by_name)
|
||||||
if ctx is None or ctx.p_max is None:
|
if ctx is None or ctx.p_max is None:
|
||||||
return 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
|
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(
|
default_actuator, default_storage, default_platform, _feasible = self._decide_masses(
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
{% if explore_result is none %}
|
{% if explore_result is none %}
|
||||||
<p class="empty">No free mass allocation to explore for this combination — its
|
<p class="empty">No free mass allocation to explore for this combination — its
|
||||||
actuator's mass isn't a design choice (a footprint-derived quantity, like a
|
platform has no declared mass ceiling to bound the sliders.</p>
|
||||||
radiation-pressure sail), or the platform has no declared mass ceiling to
|
|
||||||
bound the sliders.</p>
|
|
||||||
{% else %}
|
{% else %}
|
||||||
{% set r = explore_result %}
|
{% set r = explore_result %}
|
||||||
<div class="optimize-summary">
|
<div class="optimize-summary">
|
||||||
|
|||||||
@@ -155,7 +155,7 @@
|
|||||||
<output id="out_platform_mass">{{ "%.1f"|format(r.platform_mass) }}kg</output>
|
<output id="out_platform_mass">{{ "%.1f"|format(r.platform_mass) }}kg</output>
|
||||||
</div>
|
</div>
|
||||||
<div class="weight-slider-row">
|
<div class="weight-slider-row">
|
||||||
<label for="actuator_mass">actuator (motor size, or operator count/size for muscle power)</label>
|
<label for="actuator_mass">actuator (motor/collector size, or operator count/size for muscle power)</label>
|
||||||
<input type="range" min="{{ r.actuator_min }}" max="{{ r.actuator_slider_max }}" step="0.1"
|
<input type="range" min="{{ r.actuator_min }}" max="{{ r.actuator_slider_max }}" step="0.1"
|
||||||
id="actuator_mass" name="actuator_mass" value="{{ r.actuator_mass }}"
|
id="actuator_mass" name="actuator_mass" value="{{ r.actuator_mass }}"
|
||||||
oninput="document.getElementById('out_actuator_mass').textContent = (+this.value).toFixed(1) + 'kg'">
|
oninput="document.getElementById('out_actuator_mass').textContent = (+this.value).toFixed(1) + 'kg'">
|
||||||
|
|||||||
Reference in New Issue
Block a user