Joins follow the travel direction: the next piece goes right after the end (t67 phase 2b)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+101
-79
@@ -7,29 +7,26 @@ run(`
|
||||
var joinCases = [];
|
||||
for (let v = 0; v < 8; v++) joinCases.push([BP_SCREW, v]);
|
||||
for (const c of [BP_BELT, BP_FILTER, BP_BELT_UP, BP_BELT_DOWN, BP_FILTER_UP, BP_FILTER_DOWN])
|
||||
for (const v of c === BP_FILTER || isFilter(c) ? [0, 128, 255] : [0, 1]) joinCases.push([c, v]);
|
||||
for (const v of c === BP_FILTER || isFilter(c) ? [0, 127, 128, 255] : [0, 1]) joinCases.push([c, v]);
|
||||
`);
|
||||
const cases = run('joinCases');
|
||||
const screw = run('BP_SCREW');
|
||||
const dirs = run('SCREW_DIRS');
|
||||
function describe([code, v]) {
|
||||
if (code === screw) {
|
||||
const [x, y] = dirs[v];
|
||||
return { family: 'screw', diagonal: !!(x && y), x: x ? 1 : 0, y: x ? (x * y) : 1 };
|
||||
}
|
||||
const grade = run(`beltGrade(${code})`);
|
||||
return { family: 'belt', diagonal: !!grade, x: 1, y: grade };
|
||||
if(code===screw)return {family:'screw',x:dirs[v][0],y:dirs[v][1],diagonal:!!(dirs[v][0]&&dirs[v][1])};
|
||||
const x=run(`isFilter(${code})`)?(v&128?1:-1):(v&1?1:-1),y=run(`beltGrade(${code})`)*x;
|
||||
return {family:'belt',x,y,diagonal:!!y};
|
||||
}
|
||||
function oracle(a, b, dx, dy, ae, be) {
|
||||
const ax = a.x * (ae ? 1 : -1), ay = a.y * (ae ? 1 : -1);
|
||||
const bx = b.x * (be ? 1 : -1), by = b.y * (be ? 1 : -1);
|
||||
if (a.family !== b.family && ((!a.x || !b.x) || (a.diagonal && b.diagonal))) return false;
|
||||
if (a.diagonal && b.diagonal)
|
||||
return a.family === b.family && ax === -bx && ay === -by && dx === ax && dy === ay;
|
||||
if (a.diagonal)
|
||||
return (b.x ? bx === -ax : by === -ay) && dx === ax && dy === (b.x && ay < 0 ? 0 : ay);
|
||||
if (b.diagonal) return oracle(b, a, -dx, -dy, be, ae);
|
||||
return ax === -bx && ay === -by && dx === ax && dy === ay;
|
||||
function directed(a,b,dx,dy) {
|
||||
if(a.x*b.x+a.y*b.y<=0)return false;
|
||||
if(a.family!==b.family&&((!a.x||!b.x)||(a.diagonal&&b.diagonal)))return false;
|
||||
if(a.diagonal&&b.diagonal&&(a.x!==b.x||a.y!==b.y))return false;
|
||||
return dx===a.x&&(dy===(a.family==='belt'&&a.y<0?0:a.y)||
|
||||
(a.family==='belt'&&b.family==='belt'&&!a.y&&b.y<0&&dy===-1));
|
||||
}
|
||||
function oracle(a,b,dx,dy,ae,be) {
|
||||
const ao=(a.x||a.y)>0?1:0,bo=(b.x||b.y)>0?1:0;
|
||||
return ae===ao&&be!==bo?directed(a,b,dx,dy):ae!==ao&&be===bo&&directed(b,a,-dx,-dy);
|
||||
}
|
||||
let tableErrors = [], symmetryErrors = [], pairCount = 0;
|
||||
for (const ac of cases) for (const bc of cases) {
|
||||
@@ -66,22 +63,22 @@ function pair(label, a, b, dx, dy, expected) {
|
||||
})()`) === expected);
|
||||
}
|
||||
const ids = run('[BP_SCREW,BP_BELT,BP_FILTER,BP_BELT_UP,BP_BELT_DOWN]');
|
||||
pair('downhill low end meets a horizontal in E', [screw,5], [ids[1],0], 1,1,true);
|
||||
pair('downhill low end leaves a same-row horizontal separate', [screw,5], [ids[1],0], 1,0,false);
|
||||
pair('uphill high end meets a same-row horizontal', [screw,4], [ids[2],128], 1,0,true);
|
||||
pair('uphill high end leaves a horizontal in E separate', [screw,4], [ids[2],128], 1,-1,false);
|
||||
for (const side of [-1,1]) {
|
||||
pair(`vertical screw meets diagonal in E at end ${side}`, [screw,4], [screw,0], side,-side,true);
|
||||
pair(`vertical screw directly above or below stays separate at end ${side}`, [screw,4], [screw,0], 0,side,false);
|
||||
}
|
||||
pair('opposite travel direction continues the same diagonal slope', [screw,4], [screw,6], 1,-1,true);
|
||||
pair('different diagonal slopes stay separate', [screw,4], [screw,5], 1,-1,false);
|
||||
pair('different diagonal families stay separate', [screw,4], [ids[3],0], 1,-1,false);
|
||||
pair('horizontal screw meets a sloped belt', [ids[4],0], [screw,3], 1,1,true);
|
||||
pair('vertical screw never meets a flat belt', [ids[1],0], [screw,0], 1,0,false);
|
||||
for(const [label,av,bv,dx,dy,yes] of [
|
||||
['H(right) → DR next same row',1,5,1,0,true],['DR → H(right) diagonally past',5,1,1,1,true],
|
||||
['H(right) → UR next same row',1,4,1,0,true],['UR → H(right) diagonally past',4,1,1,-1,true],
|
||||
['V(up) → UR directly above',0,4,0,-1,true],['V(up) → UL directly above',0,7,0,-1,true],
|
||||
['UR → V(up) diagonally past',4,0,1,-1,true],['DR → V(down) diagonally past',5,2,1,1,true],
|
||||
['DR → H same row stays free',5,1,1,0,false],['H → DR one right and lower stays free',1,5,1,1,false],
|
||||
['V(up) → UR diagonally above-right stays free',0,4,1,-1,false]
|
||||
])pair(label,[screw,av],[screw,bv],dx,dy,yes);
|
||||
pair('uphill belt outlet stays on its row',[ids[3],1],[ids[1],1],1,0,true);
|
||||
pair('uphill slope outlet also targets a slope on the same row',[ids[3],1],[ids[3],1],1,0,true);
|
||||
pair('uphill slope leaves a diagonally adjacent slope free',[ids[3],1],[ids[3],1],1,-1,false);
|
||||
pair('flat belt feeds uphill low end on same row',[ids[1],1],[ids[3],1],1,0,true);
|
||||
pair('flat belt also feeds uphill low end from below',[ids[1],1],[ids[3],1],1,-1,true);
|
||||
check('planned source, neighbour, direction and empty entries override the world', run(`(() => {
|
||||
blockP.fill(0); blockV.fill(0);
|
||||
const a=10*BW+10,b=a+1, p=new Map([[a,[BP_BELT,0]],[b,[BP_SCREW,3,'extra']]]);
|
||||
const a=10*BW+10,b=a+1, p=new Map([[a,[BP_BELT,1]],[b,[BP_SCREW,1,'extra']]]);
|
||||
blockP[a]=BP_WALL; blockP[b]=BP_SCREW; blockV[b]=0;
|
||||
if(endJoin(10,10,1,p)!==b*2 || endJoin(11,10,0,p)!==a*2+1) return false;
|
||||
p.set(b,[0,0]);
|
||||
@@ -93,56 +90,81 @@ check('non-transport pieces, invalid ends and world edges are open without row w
|
||||
endJoin(0,-1,0)===-1 && endJoin(0,BH,0)===-1 && endJoin(0,0,0)===-1 &&
|
||||
endJoin(BW-1,BH-1,1)===-1 && endJoin(1,1,2)===-1;
|
||||
})()`));
|
||||
// Drive the existing column carrier across a single boundary in both directions. No gravity,
|
||||
// power or cleat timing can disguise a wrong face height as a successful handoff.
|
||||
for (const grade of [-1,1]) for (const side of [-1,1]) for (const joined of [true,false]) for (const reverse of [false,true]) {
|
||||
flatWorld(run,96,96,92);
|
||||
const result = run(`(() => {
|
||||
const c=${grade<0?'BP_BELT_UP':'BP_BELT_DOWN'}, side=${side}, low=side*${grade}>0;
|
||||
const fy=10+(${joined} ? Number(low) : (low ? 0 : -1));
|
||||
placeBlock(c,1,10,10,true); placeBlock(BP_BELT,1,10+side,fy,true);
|
||||
const sx=side>0?43:40, fx=sx+side, sy=beltFaceY(c,10,10,sx), ty=fy*BS;
|
||||
const x=${reverse}?fx:sx, y=${reverse}?ty:sy, d=${reverse}?-side:side;
|
||||
const dest=(${reverse}?sy-1:ty-1)*W+(${reverse}?sx:fx);
|
||||
setCell((y-1)*W+x,SAND,20,0); tickStamp++;
|
||||
const step=conveyorStep(x,y,d);
|
||||
carryColumn((y-1)*W+x,d,null);
|
||||
return [endJoin(10,10,side>0?1:0)>=0,mat[dest]===SAND,step];
|
||||
})()`);
|
||||
check(`grade ${grade} ${side<0?'left':'right'} ${joined?'joined':'separate'} ${reverse?'flat to slope':'slope to flat'} sand handoff matches geometry`,result[0]===joined && result[1]===joined,JSON.stringify(result));
|
||||
}
|
||||
// Render adapters keep their established bit layouts and honour plans without world writes.
|
||||
const fs = require('fs');
|
||||
run(fs.readFileSync(require('path').join(__dirname, '../src/render/slope-belts.js'), 'utf8'));
|
||||
const setup = fs.readFileSync(require('path').join(__dirname, '../src/render/setup.js'), 'utf8');
|
||||
run(setup.slice(setup.indexOf('function beltLike('), setup.indexOf('\n', setup.indexOf('function beltLike('))));
|
||||
check('filter frames follow slope ends and horizontal screw joins', run(`(() => {
|
||||
blockP.fill(0); blockV.fill(0);
|
||||
const b=10*BW+10;
|
||||
blockP[b]=BP_FILTER; blockP[b+1]=BP_BELT_UP;
|
||||
if(beltLike(b,1)) return false;
|
||||
blockP[b+1]=BP_BELT_DOWN;
|
||||
if(!beltLike(b,1)) return false;
|
||||
blockP[b+1]=BP_SCREW; blockV[b+1]=1;
|
||||
return beltLike(b,1);
|
||||
check('same-shape inlet continuation beats an elbow',run(`(()=>{
|
||||
blockP.fill(0);blockV.fill(0);const b=10*BW+10;
|
||||
for(const [n,v] of [[b,4],[b+BW-1,4],[b-1,1]]){blockP[n]=BP_SCREW;blockV[n]=v;}
|
||||
return endJoin(10,10,0)===(b+BW-1)*2+1&&endJoin(9,10,1)===-1;
|
||||
})()`));
|
||||
check('planned flat caps and slope flat/run bits agree with the shared ends', run(`(() => {
|
||||
blockP.fill(0); blockV.fill(0);
|
||||
const b=10*BW+10,p=new Map([[b,[BP_BELT_DOWN,0]],[b-1,[BP_FILTER,128]],[b+BW+1,[BP_FILTER_DOWN,128]]]);
|
||||
check('equal-shape inlet ties use top to bottom then left to right',run(`(()=>{
|
||||
blockP.fill(0);blockV.fill(0);const b=10*BW+10;
|
||||
for(const [n,v] of [[b,1],[b-BW-1,5],[b+BW-1,4]]){blockP[n]=BP_SCREW;blockV[n]=v;}
|
||||
return endJoin(10,10,0)===(b-BW-1)*2+1&&endJoin(9,11,1)===-1;
|
||||
})()`));
|
||||
const fs=require('fs');
|
||||
run(fs.readFileSync('src/render/slope-belts.js','utf8'));
|
||||
const setup=fs.readFileSync('src/render/setup.js','utf8');
|
||||
run(setup.slice(setup.indexOf('function beltLike('),setup.indexOf('\n',setup.indexOf('function beltLike('))));
|
||||
check('filter frames open at an uphill low-end feed and compatible screws',run(`(()=>{
|
||||
blockP.fill(0);blockV.fill(0);const b=10*BW+10;
|
||||
blockP[b]=BP_FILTER;blockV[b]=255;blockP[b+1]=BP_BELT_UP;blockV[b+1]=1;
|
||||
if(!beltLike(b,1))return false;
|
||||
blockP[b+1]=BP_SCREW;blockV[b+1]=1;if(!beltLike(b,1))return false;
|
||||
blockV[b+1]=3;return !beltLike(b,1);
|
||||
})()`));
|
||||
check('planned flat caps and slope continuations use the shared ends',run(`(()=>{
|
||||
blockP.fill(0);blockV.fill(0);const b=10*BW+10;
|
||||
const p=new Map([[b,[BP_BELT_DOWN,1]],[b-1,[BP_FILTER,255]],[b+BW+1,[BP_FILTER_DOWN,128]]]);
|
||||
const before=JSON.stringify([Array.from(blockP),Array.from(blockV)]);
|
||||
if(slopeBeltConnections(10,10,BP_BELT_DOWN,p)!==9 || flatBeltCaps(b-1,p)!==1) return false;
|
||||
p.set(b,[0,0]);
|
||||
return flatBeltCaps(b-1,p)===3 && before===JSON.stringify([Array.from(blockP),Array.from(blockV)]);
|
||||
if(slopeBeltConnections(10,10,BP_BELT_DOWN,p)!==9||flatBeltCaps(b-1,p)!==1)return false;
|
||||
p.set(b,[0,0]);return flatBeltCaps(b-1,p)===3&&before===JSON.stringify([Array.from(blockP),Array.from(blockV)]);
|
||||
})()`));
|
||||
check('competing diagonal and flat neighbours keep every returned join reciprocal', run(`(() => {
|
||||
blockP.fill(0); blockV.fill(0);
|
||||
const b=10*BW+10;
|
||||
blockP[b]=BP_BELT_DOWN; blockP[b+BW+1]=BP_BELT_DOWN; blockP[b+BW+2]=BP_FILTER;
|
||||
blockP[b-1]=BP_FILTER; blockP[b-BW-1]=BP_BELT_DOWN;
|
||||
for(let y=8;y<14;y++) for(let x=8;x<14;x++) for(let e=0;e<2;e++) {
|
||||
const j=endJoin(x,y,e);
|
||||
if(j>=0 && endJoin((j>>1)%BW,((j>>1)/BW)|0,j&1)!==(y*BW+x)*2+e) return false;
|
||||
}
|
||||
return true;
|
||||
check('a flat belt picks the same-row uphill inlet before the below-low-end exception',run(`(()=>{
|
||||
blockP.fill(0);blockV.fill(0);const b=10*BW+10;
|
||||
for(const n of [b,b+1,b-BW+1]){blockP[n]=n===b?BP_BELT:BP_BELT_UP;blockV[n]=1;}
|
||||
return endJoin(10,10,1)===(b+1)*2&&endJoin(11,9,0)===-1;
|
||||
})()`));
|
||||
check('same-row uphill low end draws the drop in placed and planned geometry',run(`(()=>{
|
||||
blockP.fill(0);blockV.fill(0);const b=10*BW+10;
|
||||
const p=new Map([[b,[BP_BELT_UP,1]],[b-1,[BP_BELT,1]]]);
|
||||
const j=slopeBeltConnections(10,10,BP_BELT_UP,p);
|
||||
return j===129&&slopeBeltShape(40.5,43,b,j,BP_BELT_UP)<0&&flatBeltCaps(b-1,p)===1;
|
||||
})()`));
|
||||
check('uphill same-row slope joins draw a high elbow and a low drop',run(`(()=>{
|
||||
blockP.fill(0);blockV.fill(0);const b=10*BW+10,p=new Map([[b,[BP_BELT_UP,1]],[b+1,[BP_BELT_UP,1]]]);
|
||||
return slopeBeltConnections(10,10,BP_BELT_UP,p)===2&&slopeBeltConnections(11,10,BP_BELT_UP,p)===129;
|
||||
})()`));
|
||||
// Downhill face-to-face transfers (screens always travel downhill when placed).
|
||||
for(const grade of [-1,1])for(const filter of [false,true])for(const outgoing of [false,true])for(const joined of [false,true]) {
|
||||
flatWorld(run,96,96,92);
|
||||
const d=grade,c=filter?(grade<0?'BP_FILTER_UP':'BP_FILTER_DOWN'):(grade<0?'BP_BELT_UP':'BP_BELT_DOWN');
|
||||
const r=run(`(()=>{
|
||||
const d=${d},c=${c},out=${outgoing},bx=10,by=10;
|
||||
placeBlock(c,${filter?(d>0?128:0):(d>0?1:0)},bx,by,true);
|
||||
const fx=bx+(out?d:-d),fy=by+(out?1:0)+(${joined}?0:out?-1:1);
|
||||
placeBlock(BP_BELT,d>0?1:0,fx,fy,true);
|
||||
const sx=bx*BS+(d>0?3:0),ix=bx*BS+(d>0?0:3),x=out?sx:ix-d;
|
||||
const y=out?beltFaceY(c,bx,by,sx):fy*BS;
|
||||
const targetY=out?fy*BS:beltFaceY(c,bx,by,ix);
|
||||
setCell((y-1)*W+x,SAND,20,0);tickStamp++;
|
||||
carryColumn((y-1)*W+x,d,null);
|
||||
return [endJoin(bx,by,out?(d>0?1:0):(d>0?0:1))>=0,mat[(targetY-1)*W+x+d]===SAND];
|
||||
})()`);
|
||||
check(`grade ${grade} ${filter?'screen':'belt'} ${outgoing?'outlet':'inlet'} ${joined?'joined':'free'} sand handoff agrees`,r[0]===joined&&r[1]===joined,r);
|
||||
}
|
||||
// The same-row low-end feed drops onto the face before the uphill cleats carry it.
|
||||
for(const grade of [-1,1])for(const below of [false,true]) {
|
||||
flatWorld(run,160,128,120);
|
||||
const d=-grade,code=grade<0?'BP_BELT_UP':'BP_BELT_DOWN';
|
||||
const r=run(`(()=>{
|
||||
const d=${d},c=${code},v=${d>0?1:0},bx=20,by=20,fy=by+${below?1:0};
|
||||
placeBlock(BP_BELT,d>0?1:0,bx-d,fy,true);
|
||||
for(let k=0;k<4;k++)placeBlock(c,v,bx+k*d,by-k,true);
|
||||
rebuildPower();const source=fy*BS-1,x=(bx-d)*BS+(d>0?3:0);
|
||||
setCell(source*W+x,SAND,20,0);
|
||||
let seen=false;
|
||||
for(let k=0;k<320;k++) {simTick();for(let i=0;i<N;i++)if(mat[i]===SAND&&((i/W|0)<by*BS-4))seen=true;}
|
||||
return [endJoin(bx-d,fy,d>0?1:0)>=0,seen];
|
||||
})()`);
|
||||
check(`grade ${grade} belt low end accepts sand from ${below?'below':'same row'} and carries it up`,r[0]&&r[1],r);
|
||||
}
|
||||
done();
|
||||
|
||||
Reference in New Issue
Block a user