feat: version campaigns and document original puzzle design

Restore v0.1 and v0.2 archives with separate progress, publish the curved v0.3 campaign, and document the original 24-level design with reproducible evidence. Refine board presentation and movement, and keep video lab tooling within its own directory.
This commit is contained in:
cabinw
2026-09-09 13:17:30 +08:00
parent 70b021d4cc
commit e92cccba82
52 changed files with 76449 additions and 15076 deletions
+13 -7
View File
@@ -5,13 +5,10 @@ import { routePoints } from './geometry.js';
export function exitTiming(route) {
const points=routePoints({...route,end:null},{size:1,ox:0,oy:0});
const distance=points.slice(1).reduce((sum,p,i)=>sum+Math.hypot(p.x-points[i].x,p.y-points[i].y),0);
const travelMs=Math.max(MOTION.exitMinTravelMs,Math.min(MOTION.exitMaxTravelMs,distance*MOTION.exitMsPerUnit));
const shortDuration=travelMs+MOTION.exitEffectMs;
const longDuration=Math.min(MOTION.exitLongMaxMs,MOTION.exitLongBaseMs+route.steps.length*MOTION.exitLongCellMs);
// Blend into the original long-route pacing without slowing edge departures.
const blend=smoothMotion((route.steps.length-MOTION.exitLongStartCells)/(MOTION.exitLongFullCells-MOTION.exitLongStartCells));
const duration=shortDuration+(longDuration-shortDuration)*blend;
return {duration,travelFraction:(duration-MOTION.exitEffectMs)/duration};
const rampMs=Math.min(MOTION.exitRampMs,distance*MOTION.exitMsPerUnit/3);
const travelMs=distance*MOTION.exitMsPerUnit+rampMs;
const duration=travelMs+MOTION.exitEffectMs;
return {duration,travelMs,distance,travelFraction:travelMs/duration};
}
// Zero endpoint velocity: a soft launch and a settled finish, without overshoot.
@@ -19,3 +16,12 @@ export function smoothMotion(value) {
const t=Math.max(0,Math.min(1,value));
return t*t*(3-2*t);
}
// Fixed-duration acceleration and braking; the middle travels at a shared speed.
export function travelProgress(progress,travelMs){
const t=Math.max(0,Math.min(1,progress))*travelMs;
const ramp=Math.min(MOTION.exitRampMs,travelMs/4),area=travelMs-ramp;
if(t<ramp)return t*t/(2*ramp*area);
if(t>travelMs-ramp)return 1-(travelMs-t)**2/(2*ramp*area);
return (t-ramp/2)/area;
}