123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115(* Claude Code
*
* Copyright (C) 2026 Yoann Padioleau
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public License
* (LGPL) as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*)typevec=float*floattypevehicle={position:vec;velocity:vec;max_speed:float;max_force:float}(*****************************************************************************)(* Vectors *)(*****************************************************************************)letadd(ax,ay)(bx,by)=(ax+.bx,ay+.by)letsub(ax,ay)(bx,by)=(ax-.bx,ay-.by)letscalek(x,y)=(k*.x,k*.y)letdot(ax,ay)(bx,by)=(ax*.bx)+.(ay*.by)letlength(x,y)=Float.sqrt((x*.x)+.(y*.y))letdirection(v:vec):vec=letl=lengthvinifl=0.then(0.,0.)elsescale(1./.l)v(* [v] no longer than [max] *)letclamp(max:float)(v:vec):vec=iflengthv>maxthenscalemax(directionv)elsevletheading(v:vehicle):vec=iflengthv.velocity=0.then(1.,0.)elsedirectionv.velocity(*****************************************************************************)(* Behaviours *)(*****************************************************************************)letseek(target:vec)(v:vehicle):vec=scalev.max_speed(direction(subtargetv.position))letflee(target:vec)(v:vehicle):vec=scale(-.v.max_speed)(direction(subtargetv.position))letarrive?(slowing=100.)(target:vec)(v:vehicle):vec=letoffset=subtargetv.positioninletd=lengthoffsetinletspeed=ifd<slowingthenv.max_speed*.d/.slowingelsev.max_speedinscalespeed(directionoffset)(* where [target] will be when [v] gets there: its distance over [v]'s
* top speed, the time to catch it *)letpredicted(target:vehicle)(v:vehicle):vec=lettime=length(subtarget.positionv.position)/.v.max_speedinaddtarget.position(scaletimetarget.velocity)letpursue(target:vehicle)(v:vehicle):vec=seek(predictedtargetv)vletevade(target:vehicle)(v:vehicle):vec=flee(predictedtargetv)vletwander?(distance=80.)?(radius=40.)~(angle:float)(v:vehicle):vec=lethx,hy=headingvinletcentre=addv.position(scaledistance(hx,hy))in(* the angle is from the heading: turned by it *)letc=Float.cosangleands=Float.sinangleinletpoint=addcentre(scaleradius((c*.hx)-.(s*.hy),(s*.hx)+.(c*.hy)))inseekpointvletavoid?(ahead=100.)?(size=10.)(obstacles:(vec*float)list)(v:vehicle):vec=leth=headingvinletleft=(-.sndh,fsth)in(* each obstacle in [v]'s frame: how far ahead, how far to the left *)letin_the_way=List.filter_map(fun(centre,r)->letoffset=subcentrev.positioninletalong=dotoffsethandside=dotoffsetleftinifalong>0.&&along<ahead+.r&&Float.absside<r+.sizethenSome(along,side,r)elseNone)obstaclesinmatchList.sortcomparein_the_waywith|[]->v.velocity|(along,side,r)::_->(* away from its side (to the right when dead ahead), the harder
* the nearer: 1 touching it, 0 at the corridor's end *)letaway=ifside>=0.thenscale(-1.)leftelseleftinleturgency=1.-.(along/.(ahead+.r))inadd(scalev.max_speedh)(scale(2.*.urgency*.v.max_speed)away)(* the point of segment [a]-[b] nearest to [p] *)letnearest_on(a:vec)(b:vec)(p:vec):vec=letab=subbainletl2=dotababinifl2=0.thenaelseadda(scale(Float.max0.(Float.min1.(dot(subpa)ab/.l2)))ab)letfollow?(ahead=50.)~(width:float)(path:veclist)(v:vehicle):vec=letfuture=addv.position(scaleahead(headingv))inletrecsegments=functiona::(b::_asrest)->(a,b)::segmentsrest|_->[]inmatchsegmentspathwith|[]->(matchpathwith[p]->arrivepv|_->v.velocity)|segs->let(point,(a,b))=List.fold_left(fun(best,seg)(a,b)->letq=nearest_onabfutureiniflength(subqfuture)<length(subbestfuture)then(q,(a,b))else(best,seg))(nearest_on(fst(List.hdsegs))(snd(List.hdsegs))future,List.hdsegs)segsiniflength(subpointfuture)<=widththenv.velocityelseseek(addpoint(scaleahead(direction(subba))))v(*****************************************************************************)(* Forces *)(*****************************************************************************)letsteer(v:vehicle)(desired:vec):vec=clampv.max_force(subdesiredv.velocity)letblend(forces:(float*vec)list):vec=List.fold_left(funacc(w,f)->addacc(scalewf))(0.,0.)forcesletmove~(dt:float)(force:vec)(v:vehicle):vehicle=letvelocity=clampv.max_speed(addv.velocity(scaledtforce))in{vwithvelocity;position=addv.position(scaledtvelocity)}