Spaces:
Running on Zero
Running on Zero
Fix track preview start marker alignment (#3)
Browse files- Fix track preview start marker alignment (9f816c2e2e87e62f52de7a2845fca88a17753f0d)
- Update release manifest for start marker fix (2be1a6951a45bc14ff6366a71690d1633fed0d85)
- RELEASE_MANIFEST.json +1 -1
- track-map.js +32 -16
RELEASE_MANIFEST.json
CHANGED
|
@@ -83,7 +83,7 @@
|
|
| 83 |
"star_check.py": "5c17080957b81ffccf0de8314635b471562e55c92c97a1207d9034a99279b2c3",
|
| 84 |
"steam_leaderboard.py": "d76397c39cae58f636ddf9a9be1a46ae3d77fefd6b7269feac73d34d97165481",
|
| 85 |
"track-library.js": "351ec4c026b49420ce963f4310cb000d9e987fd28e6dac5ecd0176509340f6e6",
|
| 86 |
-
"track-map.js": "
|
| 87 |
"training_config.py": "6eb7282bbee3b3a6273bddc29c949d9904067414c26e501d815012f033173a1c",
|
| 88 |
"vendor/gradio-client/LICENSE": "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4",
|
| 89 |
"vendor/gradio-client/dist/index.min.js": "fdd776db8633354f118fe2908c980592283968737e6b91dc3aa86d648da5c3fd",
|
|
|
|
| 83 |
"star_check.py": "5c17080957b81ffccf0de8314635b471562e55c92c97a1207d9034a99279b2c3",
|
| 84 |
"steam_leaderboard.py": "d76397c39cae58f636ddf9a9be1a46ae3d77fefd6b7269feac73d34d97165481",
|
| 85 |
"track-library.js": "351ec4c026b49420ce963f4310cb000d9e987fd28e6dac5ecd0176509340f6e6",
|
| 86 |
+
"track-map.js": "547b737d123d9479fc166ddc564568c0ff4b2099200c78e36bb46ec461bc754c",
|
| 87 |
"training_config.py": "6eb7282bbee3b3a6273bddc29c949d9904067414c26e501d815012f033173a1c",
|
| 88 |
"vendor/gradio-client/LICENSE": "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4",
|
| 89 |
"vendor/gradio-client/dist/index.min.js": "fdd776db8633354f118fe2908c980592283968737e6b91dc3aa86d648da5c3fd",
|
track-map.js
CHANGED
|
@@ -14,6 +14,25 @@
|
|
| 14 |
throw Error('Track coordinates exceed preview bounds');
|
| 15 |
return [x,y];
|
| 16 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
async function decode(file){
|
| 18 |
if(!file||file.size<1||file.size>1048576)throw Error('Choose a .track file under 1 MB');
|
| 19 |
if(typeof DecompressionStream!=='function')throw Error('This browser cannot preview gzip tracks');
|
|
@@ -39,14 +58,20 @@
|
|
| 39 |
const id=item?.Block?.TileSetId;
|
| 40 |
const block=atlas?.blocks?.[`${id?.AtlasId}:${id?.AlternativeId}`];
|
| 41 |
if(!block||![0,1,2].includes(block.surface))throw Error('Track uses a piece not in the simulator atlas');
|
| 42 |
-
|
|
|
|
| 43 |
});
|
| 44 |
if(new Set(tiles.map(t=>`${t.x},${t.y}`)).size!==tiles.length)throw Error('Duplicate track tile');
|
| 45 |
const start=vector(saved.StartTileCoords);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
const minX=Math.min(...tiles.map(t=>t.x)),maxX=Math.max(...tiles.map(t=>t.x));
|
| 47 |
const minY=Math.min(...tiles.map(t=>t.y)),maxY=Math.max(...tiles.map(t=>t.y));
|
| 48 |
if(maxX-minX>30||maxY-minY>30)throw Error('Track preview is too large');
|
| 49 |
-
return {tiles,start,bounds:{minX,maxX,minY,maxY}};
|
| 50 |
}
|
| 51 |
function render(canvas,track){
|
| 52 |
const context=canvas.getContext('2d');
|
|
@@ -63,25 +88,16 @@
|
|
| 63 |
context.beginPath();context.roundRect(x+2,y+2,size-4,size-4,Math.max(4,size*.13));context.fill();
|
| 64 |
context.strokeStyle='#d7e8f2';context.lineWidth=Math.max(1,size*.018);
|
| 65 |
context.stroke();
|
| 66 |
-
const edge=connection=>{
|
| 67 |
-
const offset=(connection.index-1)*size*.16;
|
| 68 |
-
switch(connection.side){
|
| 69 |
-
case 0:return [x+size/2+offset,y+2];
|
| 70 |
-
case 1:return [x+size/2+offset,y+size-2];
|
| 71 |
-
case 2:return [x+2,y+size/2+offset];
|
| 72 |
-
case 3:return [x+size-2,y+size/2+offset];
|
| 73 |
-
default:return [x+size/2,y+size/2];
|
| 74 |
-
}
|
| 75 |
-
};
|
| 76 |
if(Array.isArray(tile.connections)&&tile.connections.length>=2){
|
| 77 |
-
const a=
|
| 78 |
context.beginPath();context.moveTo(...a);
|
| 79 |
context.quadraticCurveTo(x+size/2,y+size/2,...z);
|
| 80 |
context.strokeStyle='#f3a967';context.lineWidth=Math.max(3,size*.055);
|
| 81 |
context.lineCap='round';context.stroke();
|
| 82 |
}
|
| 83 |
}
|
| 84 |
-
const
|
|
|
|
| 85 |
context.beginPath();context.arc(sx,sy,Math.max(6,size*.105),0,Math.PI*2);
|
| 86 |
context.fillStyle='#66e5aa';context.fill();context.strokeStyle='#0c1926';context.lineWidth=3;context.stroke();
|
| 87 |
}
|
|
@@ -106,10 +122,10 @@
|
|
| 106 |
if(current!==version)return;
|
| 107 |
render(canvas,track);
|
| 108 |
const counts=[0,0,0];track.tiles.forEach(tile=>counts[tile.surface]++);
|
| 109 |
-
status.textContent=source+' 路 '+track.tiles.length+' pieces 路 green dot marks the
|
| 110 |
legend.textContent=NAMES.filter((_,i)=>counts[i]).map((name,index)=>{
|
| 111 |
const i=NAMES.indexOf(name);return name+' '+counts[i];
|
| 112 |
-
}).join(' 路 ')+' 路
|
| 113 |
}catch(error){
|
| 114 |
if(current!==version)return;
|
| 115 |
renderEmpty();status.textContent='Map unavailable: '+error.message;legend.textContent='Training selection is unaffected.';
|
|
|
|
| 14 |
throw Error('Track coordinates exceed preview bounds');
|
| 15 |
return [x,y];
|
| 16 |
}
|
| 17 |
+
function connection(value,message='Track has an invalid piece connection'){
|
| 18 |
+
if(!value||typeof value!=='object'||Array.isArray(value)||
|
| 19 |
+
!Number.isInteger(value.side)||value.side<0||value.side>3||
|
| 20 |
+
!Number.isInteger(value.index)||value.index<0||value.index>2||
|
| 21 |
+
!Number.isInteger(value.type)||value.type<0)throw Error(message);
|
| 22 |
+
return {side:value.side,index:value.index,type:value.type};
|
| 23 |
+
}
|
| 24 |
+
function connectionPoint(value,x,y,size){
|
| 25 |
+
const {side,index}=connection(value);
|
| 26 |
+
// Native Connection.position uses offsets 128,384,640 in a 768-unit tile.
|
| 27 |
+
// Shared boundaries keep adjacent path endpoints and the saved start aligned.
|
| 28 |
+
const offset=[128,384,640][index]/768*size;
|
| 29 |
+
switch(side){
|
| 30 |
+
case 0:return [x+offset,y];
|
| 31 |
+
case 1:return [x+offset,y+size];
|
| 32 |
+
case 2:return [x,y+offset];
|
| 33 |
+
case 3:return [x+size,y+offset];
|
| 34 |
+
}
|
| 35 |
+
}
|
| 36 |
async function decode(file){
|
| 37 |
if(!file||file.size<1||file.size>1048576)throw Error('Choose a .track file under 1 MB');
|
| 38 |
if(typeof DecompressionStream!=='function')throw Error('This browser cannot preview gzip tracks');
|
|
|
|
| 58 |
const id=item?.Block?.TileSetId;
|
| 59 |
const block=atlas?.blocks?.[`${id?.AtlasId}:${id?.AlternativeId}`];
|
| 60 |
if(!block||![0,1,2].includes(block.surface))throw Error('Track uses a piece not in the simulator atlas');
|
| 61 |
+
if(!Array.isArray(block.connections)||block.connections.length!==2)throw Error('Track piece connections are unavailable');
|
| 62 |
+
return {x,y,surface:block.surface,connections:block.connections.map(value=>connection(value))};
|
| 63 |
});
|
| 64 |
if(new Set(tiles.map(t=>`${t.x},${t.y}`)).size!==tiles.length)throw Error('Duplicate track tile');
|
| 65 |
const start=vector(saved.StartTileCoords);
|
| 66 |
+
const startTile=tiles.find(tile=>tile.x===start[0]&&tile.y===start[1]);
|
| 67 |
+
if(!startTile)throw Error('Start tile is not present in this track');
|
| 68 |
+
const startConnection=connection({side:saved.StartTileConnection?.Side,index:saved.StartTileConnection?.Index,type:saved.StartTileConnection?.Type},'Track has an invalid or missing start connection');
|
| 69 |
+
if(!startTile.connections.some(item=>item.side===startConnection.side&&item.index===startConnection.index&&item.type===startConnection.type))
|
| 70 |
+
throw Error('Saved start connection does not match the start tile');
|
| 71 |
const minX=Math.min(...tiles.map(t=>t.x)),maxX=Math.max(...tiles.map(t=>t.x));
|
| 72 |
const minY=Math.min(...tiles.map(t=>t.y)),maxY=Math.max(...tiles.map(t=>t.y));
|
| 73 |
if(maxX-minX>30||maxY-minY>30)throw Error('Track preview is too large');
|
| 74 |
+
return {tiles,start,startConnection,bounds:{minX,maxX,minY,maxY}};
|
| 75 |
}
|
| 76 |
function render(canvas,track){
|
| 77 |
const context=canvas.getContext('2d');
|
|
|
|
| 88 |
context.beginPath();context.roundRect(x+2,y+2,size-4,size-4,Math.max(4,size*.13));context.fill();
|
| 89 |
context.strokeStyle='#d7e8f2';context.lineWidth=Math.max(1,size*.018);
|
| 90 |
context.stroke();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 91 |
if(Array.isArray(tile.connections)&&tile.connections.length>=2){
|
| 92 |
+
const a=connectionPoint(tile.connections[0],x,y,size),z=connectionPoint(tile.connections[1],x,y,size);
|
| 93 |
context.beginPath();context.moveTo(...a);
|
| 94 |
context.quadraticCurveTo(x+size/2,y+size/2,...z);
|
| 95 |
context.strokeStyle='#f3a967';context.lineWidth=Math.max(3,size*.055);
|
| 96 |
context.lineCap='round';context.stroke();
|
| 97 |
}
|
| 98 |
}
|
| 99 |
+
const startX=left+(track.start[0]-b.minX)*size,startY=top+(track.start[1]-b.minY)*size;
|
| 100 |
+
const [sx,sy]=connectionPoint(track.startConnection,startX,startY,size);
|
| 101 |
context.beginPath();context.arc(sx,sy,Math.max(6,size*.105),0,Math.PI*2);
|
| 102 |
context.fillStyle='#66e5aa';context.fill();context.strokeStyle='#0c1926';context.lineWidth=3;context.stroke();
|
| 103 |
}
|
|
|
|
| 122 |
if(current!==version)return;
|
| 123 |
render(canvas,track);
|
| 124 |
const counts=[0,0,0];track.tiles.forEach(tile=>counts[tile.surface]++);
|
| 125 |
+
status.textContent=source+' 路 '+track.tiles.length+' pieces 路 green dot marks the saved starting point';
|
| 126 |
legend.textContent=NAMES.filter((_,i)=>counts[i]).map((name,index)=>{
|
| 127 |
const i=NAMES.indexOf(name);return name+' '+counts[i];
|
| 128 |
+
}).join(' 路 ')+' 路 Orange line shows the approximate route; collision shapes and car positions are not shown.';
|
| 129 |
}catch(error){
|
| 130 |
if(current!==version)return;
|
| 131 |
renderEmpty();status.textContent='Map unavailable: '+error.message;legend.textContent='Training selection is unaffected.';
|