Skip to content

Commit 06ed1ee

Browse files
committed
Visual polish: painterly terrain blending, coherent snowcaps, deeper rivers, beat-card fix
- Terrain colors blend across biome borders (neighbor-weighted pass) so the greens read as painterly gradients instead of posterized bands; per-vertex jitter calmed - Rock whitens smoothly with height for coherent snowcaps on the spine - Rivers deeper blue and near-opaque so they read at grazing angles - Forests denser and darker so woods read as masses from altitude - Director beat card no longer lingers after the user takes manual camera control Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LJUuoQXWUkMk2YSxRM2MSp
1 parent 24c5d6f commit 06ed1ee

1 file changed

Lines changed: 34 additions & 11 deletions

File tree

index.html

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -870,7 +870,11 @@ <h3 id="insptitle">—</h3>
870870
case B.ROCK:c=C1.setHex(sp.rock);break;
871871
default:c=C1.setHex(0xe8ecef);
872872
}
873-
c.multiplyScalar(0.88+n*0.24);
873+
c.multiplyScalar(0.92+n*0.16);
874+
if(bio===B.ROCK||bio===B.SNOW){ // coherent snowcaps: whiten smoothly with height
875+
const t=clamp((h-470)/130,0,1);
876+
c.lerp(new THREE.Color(0xe9edf2),t*t);
877+
}
874878
if(W.roadCells[i]&&bio!==B.WATER)c.lerp(new THREE.Color(0x8a7448),0.8); // roads read at map altitude
875879
if(W.drought&&(bio===B.MEADOW||bio===B.FARM))c.lerp(new THREE.Color(0xb3a35c),0.55);
876880
// winter snow accumulation on flat/high ground
@@ -929,7 +933,7 @@ <h3 id="insptitle">—</h3>
929933
const rg=new THREE.BufferGeometry();
930934
rg.setAttribute('position',new THREE.Float32BufferAttribute(riv.p,3));
931935
rg.setAttribute('normal',new THREE.Float32BufferAttribute(riv.n,3));
932-
G.rivers=new THREE.Mesh(rg,new THREE.MeshPhongMaterial({color:0x3a7096,transparent:true,opacity:0.88,shininess:110,specular:0x99bbcc,
936+
G.rivers=new THREE.Mesh(rg,new THREE.MeshPhongMaterial({color:0x2e6ea0,transparent:true,opacity:0.95,shininess:110,specular:0x99bbcc,
933937
polygonOffset:true,polygonOffsetFactor:-2,polygonOffsetUnits:-2}));
934938
scene.add(G.rivers);
935939
// roads: king's roads (wide) + settlement streets (narrow), one merged mesh
@@ -958,17 +962,30 @@ <h3 id="insptitle">—</h3>
958962
const sp={};for(const k of['mead','farm','forest','pine','rock','marsh']){
959963
const c0=new THREE.Color(sp0[k]),c1=new THREE.Color(sp1[k]);sp[k]=c0.lerp(c1,blend*blend).getHex();
960964
}
961-
const col=G.terrain.geometry.attributes.color;
962-
for(let i=0;i<col.count;i++){
965+
const col=G.terrain.geometry.attributes.color,N=col.count;
966+
if(!G._cbuf)G._cbuf=new Float32Array(N*3);
967+
const buf=G._cbuf;
968+
for(let i=0;i<N;i++){
963969
const c=colorForVertex(i,sp,snowT,SEED);
964-
col.setXYZ(i,c.r,c.g,c.b);
970+
buf[i*3]=c.r;buf[i*3+1]=c.g;buf[i*3+2]=c.b;
971+
}
972+
// neighbor blend calms biome banding into painterly gradients
973+
for(let cz=0;cz<GRID;cz++)for(let cx=0;cx<GRID;cx++){
974+
const i=cIdx(cx,cz);
975+
let r=buf[i*3]*0.44,g=buf[i*3+1]*0.44,b=buf[i*3+2]*0.44,ws=0.44;
976+
for(const[dx,dz]of[[1,0],[-1,0],[0,1],[0,-1]]){
977+
if(!inB(cx+dx,cz+dz))continue;
978+
const j=cIdx(cx+dx,cz+dz);
979+
r+=buf[j*3]*0.14;g+=buf[j*3+1]*0.14;b+=buf[j*3+2]*0.14;ws+=0.14;
980+
}
981+
col.setXYZ(i,r/ws,g/ws,b/ws);
965982
}
966983
col.needsUpdate=true;
967984
// trees seasonal tint
968985
if(G.decid){
969-
const dc=new THREE.Color([0x4c7a3c,0x557a36,0xa05e24,0x6e6250][si]);
986+
const dc=new THREE.Color([0x40682f,0x4a6c2e,0x96541e,0x5e5344][si]);
970987
G.decid.material.color.copy(dc);
971-
G.pine.material.color.setHex(si===3?0x39543f:0x2e5138);
988+
G.pine.material.color.setHex(si===3?0x314a37:0x28472f);
972989
}
973990
}
974991

@@ -985,11 +1002,11 @@ <h3 id="insptitle">—</h3>
9851002
const i=cIdx(cx,cz),bio=W.biome[i];
9861003
if(bio!==B.FOREST&&bio!==B.PINE)continue;
9871004
if(W.settlements.some(s=>dist2d(cellX(cx),cellZ(cz),s.pos.x,s.pos.z)<s.radius*0.8))continue;
988-
const dens=bio===B.FOREST?2:2;
1005+
const dens=bio===B.FOREST?3:2;
9891006
for(let t=0;t<dens;t++){
9901007
const x=cellX(cx)+ (nj(cx*3+t*7,cz*3)-0.5)*CELL*1.4, z=cellZ(cz)+(nj(cx*3,cz*3+t*13)-0.5)*CELL*1.4;
9911008
const arr=bio===B.FOREST?spots.dec:spots.pine;
992-
if(arr.length<5200)arr.push({x,z,s:0.8+nj(cx+t,cz-t)*0.9});
1009+
if(arr.length<7000)arr.push({x,z,s:1.0+nj(cx+t,cz-t)*1.0});
9931010
}
9941011
}
9951012
G.decid=new THREE.InstancedMesh(decidG,new THREE.MeshLambertMaterial({color:0x4c7a3c}),Math.max(1,spots.dec.length));
@@ -2641,8 +2658,14 @@ <h3 id="insptitle">—</h3>
26412658
if(this.queue.length>12)this.queue.sort((a,b)=>b.ev.pri-a.ev.pri).length=12;
26422659
},
26432660
update(now,dt){
2644-
if(cam.mode!=='director'&&!document.body.classList.contains('watch'))return;
2645-
if(now<cam.manualUntil)return;
2661+
if(cam.mode!=='director'&&!document.body.classList.contains('watch')){
2662+
if(this.shot){this.shot=null;hideBeat();} // don't leave a stale card when the user takes the wheel
2663+
return;
2664+
}
2665+
if(now<cam.manualUntil){
2666+
if(this.shot){this.shot=null;hideBeat();}
2667+
return;
2668+
}
26462669
// expire stale
26472670
this.queue=this.queue.filter(q=>now-q.born<50&&(!this.shot||q.ev!==this.shot.ev));
26482671
// pick a beat

0 commit comments

Comments
 (0)