Add optional shoreline Motorboats and recommended addon controls
continuous-integration/drone/push Build is passing

This commit is contained in:
2026-11-07 00:32:01 +01:00
parent a7452f8be6
commit 161100cf08
31 changed files with 624 additions and 56 deletions
+46
View File
@@ -0,0 +1,46 @@
"""Execute production coastal topology and island-facing selection, not an engine test."""
from policy_harness import ROOT,body,run
p=ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_ShoreMap.c'
methods='\n'.join(body(p,s) for s in ['void Setup(', 'vector Point(', 'int Index(', 'int Root(', 'protected void Join(', 'int Neighbor(', 'bool Ready(', 'void Step(', 'bool FacingIsland(', 'static int Chance('])
methods=methods.replace('protected ','').replace('vector.Lerp','Vec::Lerp').replace('vector ','Vec ').replace('Math.Max','Max').replace('Math.Ceil','ceil').replace('Math.Floor','floor').replace('Math.Clamp','Clamp').replace('Vector(','Vec(').replace('System.GetTickCount()','0').replace('ERR_CampTerrain.Rotate','Rotate')
run(r'''
#include <cassert>
#include <cmath>
#include <algorithm>
#include <vector>
#include <functional>
float Max(float a,float b){return std::max(a,b);} int Clamp(int v,int a,int b){return std::clamp(v,a,b);}
struct Vec { float v[3]; Vec(float x=0,float y=0,float z=0):v{x,y,z}{} float& operator[](int i){return v[i];}
Vec operator+(Vec b){return {v[0]+b[0],v[1]+b[1],v[2]+b[2]};} Vec operator*(float n){return {v[0]*n,v[1]*n,v[2]*n};}
static Vec Lerp(Vec a,Vec b,float t){return a*(1-t)+b*t;} };
Vec Rotate(Vec v,float angle){float a=angle*3.14159265/180;return {v[0]*cosf(a)+v[2]*sinf(a),0,v[2]*cosf(a)-v[0]*sinf(a)};}
template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;void Clear(){this->clear();}void Insert(T v){this->push_back(v);}int Count(){return this->size();}};
struct Map {
Vec Low;float Cell=64;int Width=0,Height=0,m_Cursor=0,m_Phase=0;Array<int> Parents,Edges;
std::function<bool(Vec)> terrain=[](Vec){return false;};int samples=0;
bool Land(Vec p){samples++;return terrain(p);}
''' + methods + r'''
void Build(){for(int i=0;i<10000&&!Ready();i++){int n=samples;Step();assert(samples-n<=64*7);}assert(Ready());}
};
int main(){
Map m;m.Setup({0,0,0},{1024,0,1024});assert(m.Width==16&&m.Height==16&&m.Cell==64);
// Two islands separated by a four-cell sea channel.
m.terrain=[](Vec p){return p[0]<256 || (p[0]>=512&&p[0]<768);};m.Build();
assert(m.Root(0)!=m.Root(8));assert(m.FacingIsland(0,{256,0,400},{1,0,0}));
assert(!m.FacingIsland(0,{0,0,400},{-1,0,0})); // open sea
assert(m.Index({-1,0,0})==-1&&m.Index({1024,0,0})==-1);
assert(m.Neighbor(15,0)==-1&&m.Neighbor(0,1)==-1);
assert(m.Chance(true,20,70)==70&&m.Chance(false,20,70)==20);
assert(m.Chance(true,80,30)==80&&m.Chance(false,0,70)==0);
// A land connection along the north edge makes the same shape a bay, not islands.
m.Setup({0,0,0},{1024,0,1024});m.terrain=[](Vec p){return p[2]<64||p[0]<256||(p[0]>=512&&p[0]<768);};m.Build();
assert(m.Root(0)==m.Root(8));assert(!m.FacingIsland(0,{256,0,400},{1,0,0}));
// Channels smaller than one cell still prevent a link when intermediate samples see water.
m.Setup({0,0,0},{256,0,256});m.terrain=[](Vec p){return p[0]<50||p[0]>78;};m.Build();assert(m.Root(0)!=m.Root(1));
// All water / empty terrain creates no shore candidates; very large worlds stay bounded.
m.Setup({0,0,0},{256,0,256});m.terrain=[](Vec){return false;};m.Build();assert(m.Edges.empty());
m.Setup({0,0,0},{0,0,0});m.Build();assert(m.Edges.empty());
m.Setup({0,0,0},{100000,0,100000});assert(m.Width<=256&&m.Height<=256);
}
''')
print('PASS: actual bounded shoreline grid, connected bays versus separate islands, narrow channels, outward fan, map limits and probability boost')