Fix low-terrain startup and extraction placement; record Workshop 0.0.121
continuous-integration/drone/push Build is passing

This commit is contained in:
2026-10-25 14:01:49 +01:00
parent 3768ce7ed8
commit 6397fe89b6
18 changed files with 211 additions and 38 deletions
+29 -6
View File
@@ -31,28 +31,42 @@ int main(){
d.m_Began=Clock::now;d.ReportProgress(&site);assert(reports.size()==3);d.RecordRejection("obstructed footprint");assert(d.RejectionSummary()=="obstructed footprint=1");
}
''')
# Execute the actual water, elevation, flatness and physical-trace short circuits.
# Execute actual terrain-height water queries, low dry land and physical checks.
clear=body(p,'protected bool Reject(')+'\n'+body(p,'bool Clear(')
clear=clear.replace('protected ', '').replace('out float lowest','float& lowest').replace('float.MAX','FLT_MAX').replace('Math.','Math::').replace('TraceBox trace','TraceBox* trace').replace('trace.','trace->').replace('TraceFlags.','TraceFlags::').replace('null','nullptr').replace('ChimeraWorldUtils.','ChimeraWorldUtils::')
clear=clear.replace('protected ', '').replace('out float lowest','float& lowest').replace('float.MAX','FLT_MAX').replace('Math.','Math::').replace('TraceBox trace','TraceBox* trace').replace('trace.','trace->').replace('TraceFlags.','TraceFlags::').replace('null','nullptr').replace('ChimeraWorldUtils.','ChimeraWorldUtils::').replace('ERR_ForceRules.','ERR_ForceRules::')
dry=body(p.parent/'ERR_Actors.c','static bool DryGround(').replace('ERR_ForceRules.','ERR_ForceRules::').replace('ChimeraWorldUtils.','ChimeraWorldUtils::')
for a,b in [('"-1.42 0 -1.42"','Vector(-1.42,0,-1.42)'),('"1.42 0.1 1.42"','Vector(1.42,.1,1.42)')]:clear=clear.replace(a,b)
run(r'''
#include <cassert>
#include <cfloat>
#include <cmath>
#include <limits>
#include <string>
#include <algorithm>
using string=std::string;
struct vector{float p[3];float operator[](int i){return p[i];}};vector Vector(float x,float y,float z){return {{x,y,z}};}vector operator+(vector a,vector b){return Vector(a[0]+b[0],a[1]+b[1],a[2]+b[2]);}
struct vector{float p[3];float& operator[](int i){return p[i];}};vector Vector(float x,float y,float z){return {{x,y,z}};}vector operator+(vector a,vector b){return Vector(a[0]+b[0],a[1]+b[1],a[2]+b[2]);}
namespace Math{float Min(float a,float b){return std::min(a,b);}float Max(float a,float b){return std::max(a,b);}}
namespace TraceFlags{int ENTS=1,WORLD=2;}struct TraceBox{vector Start,End,Mins,Maxs;int Flags;};
struct World{bool water=false,blocked=false,slope=false;float height=42;int traces=0;float GetSurfaceY(float x,float){return height+(slope?x:0);}float TraceMove(TraceBox*,void*){traces++;return blocked?0:1;}};
struct World{bool water=false,puddle=false,blocked=false,slope=false;float height=42,waterLevel=43;int traces=0;float GetSurfaceY(float x,float){return height+(slope?x:0);}float TraceMove(TraceBox*,void*){traces++;return blocked?0:1;}};
struct Game{World world;World&GetWorld(){return world;}}game;Game&GetGame(){return game;}
namespace ChimeraWorldUtils{bool TryGetWaterSurfaceSimple(World&w,vector){return w.water;}}
namespace ERR_ForceRules{bool Finite(float n){return std::isfinite(n);}}
namespace ChimeraWorldUtils{bool TryGetWaterSurfaceSimple(World&w,vector p){return (w.water||(w.puddle&&p[0]==2&&p[2]==2))&&p[1]<w.waterLevel;}}
namespace ERR_Actors{'''+dry+r'''}
struct Terrain{string m_Rejection;vector Rotate(vector p,float){return p;}
'''+clear+r'''
};
int main(){Terrain t;float floor;auto check=[&](){return t.Clear(Vector(0,0,0),0,10,.6,floor);};
game.world.water=true;assert(!check()&&t.m_Rejection=="water"&&game.world.traces==0);
game.world.water=false;game.world.height=.5;assert(!check()&&t.m_Rejection=="ground below minimum elevation");
// Placeholder Y=0 must not make high dry land look submerged.
game.world.waterLevel=0;assert(check()&&t.m_Rejection.empty());
game.world.water=false;
for(float h:{.5f,0.f,-25.f}){game.world.height=h;assert(check()&&floor==h&&ERR_Actors::DryGround(Vector(0,999,0)));}
// A terrain-relative ocean/lake still rejects both high and low submerged ground.
for(float h:{42.f,-25.f,.5f}){game.world.height=h;game.world.waterLevel=h+.05f;game.world.water=true;assert(!check()&&t.m_Rejection=="water"&&!ERR_Actors::DryGround(Vector(0,999,0)));}
game.world.water=false;game.world.height=42;game.world.waterLevel=43;game.world.puddle=true;
assert(!check()&&t.m_Rejection=="water");game.world.puddle=false; // Water between the old nine sample points.
game.world.height=std::numeric_limits<float>::quiet_NaN();assert(!check()&&t.m_Rejection=="invalid terrain height"&&!ERR_Actors::DryGround(Vector(0,0,0)));
game.world.traces=0;
game.world.height=42;game.world.slope=true;assert(!check()&&t.m_Rejection=="uneven ground"&&game.world.traces==0);
game.world.slope=false;game.world.blocked=true;assert(!check()&&t.m_Rejection=="obstructed footprint"&&game.world.traces==1);
game.world.blocked=false;assert(check()&&t.m_Rejection.empty()&&floor==42);
@@ -65,3 +79,12 @@ for filename in ('ERR_GeneratedSites.c','ERR_TerrainSites.c'):
assert 'ReportSearch(site, "placed"); ClearDiagnostics();' in source
assert 'Checks: ' in source and 'RejectionSummary();' in source
print('PASS: actual terrain refusal reasons, counted summaries, site/attempt/time context, 30-second rate limit and per-site reset')
# Every downstream startup gate uses actual dry terrain rather than map altitude.
for filename in ('ERR_SiteService.c','ERR_SpawnPlacement.c','ERR_AirfieldPlacement.c'):
source=(p.parent/filename).read_text()
assert 'ERR_Actors.DryGround(' in source
assert '[1] < 1' not in source and '[1] < 2' not in source
source=(p.parent.parent/'10_AI/ERR_MortarSystem.c').read_text()
assert 'ERR_Actors.DryGround(position)' in source and 'position[1] < 1' not in source
print('PASS: actual low/negative dry terrain, height-correct shallow/lake/ocean/interior water refusal and downstream startup dry-ground gates')