 let me get a drink all right so we got this pathfinding working and that one failed because I was in midair probably when I when I did one of the endpoints so there's some optimization to do to it and then I'd like to get things pathfinding like path following and I'd actually like to do them in the other order because the optimization will be more important once we actually have them active but because I have all this stuff set up to visualize what's going on and I've got to rewrite one of the data structures it's probably a good idea to do it the optimization first while I have that visualization late start on that annotation so what do we got to do so what I want to do though is is keep the old implementation around so we can compare the performance in a real world situation because here I'm going to be doing it in a synthetic situation with its testing so I also kind of want to put all these globals into a data structure because I think I'm going to want to be able to have a long-term path finder that takes multiple frames and short-term path finds take a single frame it's you'll need to be able to run two at once you'll need to be able to pause the long-term one and you could pause by copying everything out to it somewhere else but it's kind of dumb so it might be right it might be better to get this right upfront instead of instead of having to retrofit that so and it's not upfront now but it's a little late to be upfront now but so for now we'll just put all the things in unchanged so anything that uses that means it's now that pc is not autocompleting which implies let's have a oh no it just means something weird don't know what that means so that's not autocompleting the clear one here of course that's gonna be on the stack and huge 5000 times our big a path node is oh what happened to the path node declaration oh right I put it into so that's eight bytes and then that's eight times 5000 that's 40 kilobytes on the stack it's not great but we can live with that for now it will move out once we need to have the multiples but uh we're not gonna worry about that yet okay so now we have to initialize it so we zero it which that's let's omit that stuff so we don't need to do that because we zeroed the whole thing both the compiler find the things we forgot and find the rest in that function in this function I probably should have uh yeah I'll go ahead and do that actually right now because eventually we'll be passing it in so we can just go ahead and get this correct up front all right we don't need to zero that now we can use uh right the uh the visualizer needed to direct the access that stuff so we actually need to expose it to the visualizer so we need to make it not on the stack um and then uh we don't want to export that so what we want to do is oh it's just directing that file okay see if we broke anything with this oh we still are searching with the large the large object which is why it seems to stick out into space here we'll actually be able to actually have some large objects when we get the path following going and be able to actually have that make sense um so let's go up to the path behavior and put that back to normal okay so uh that is um so now we will do the optimization so let's talk about the optimization the normal way that you optimize an a star pathfinder not normally optimized but the normal way you fix this issue which is that I have an unsorted open list is to create a priority queue with a heap heaps are order log n data structures that have the ability to insert delete the minimum and to change the value of an existing thing to a smaller value which are the operations exactly the operations we need update on the list is always reducing the cost but it's login I'd have to look it up and it's got some special cases and our costs and estimates are all integers and that lets us do a slightly different strategy unfortunately they're not bounded in a star the same way they are in a dykstra um so we can't quite do the super efficient approach that the dykstra approach allows but it's a basic idea is that in this queue we're going to have a bunch of items with the cost a total cost of 20 um and a bunch with the total cost of 21 and a bunch with the total cost of 22 and then maybe there's one with 35 or 36 or whatever uh but what we can do is keep a linked list for each integer possibility um and although linked lists are you're not great for data oriented design style programming we're only ever going to be fetching the head of the list we're never exploring down the list so the way that works first we have to add to the path nodes to let them have a link internally uh and they probably don't have free storage right because we pushed them to the uh we we bit packed those down so whoops wrong one so those are actually going to have to get bigger than they used to be yeah because I have a 16 bit cost and 60 minute estimate and we need to add another UN16 next so I'm just going to do that in a parallel array to start with on that way when we test the performance of the old version versus the new the old version won't have to pay the cost of ever touching this array so next path nodes uh except to do that I'd need the integer number and I actually have pointers normally so maybe I don't want to do that just take going back from the pointers to the oh it's just a pointer subtract it's not too bad all right and then what else do we need now we need a list of pointer head so head um let's actually use ants so because we know we're never gonna have more than five thousand nodes so that way we can have an unsigned value meaning empty so we have max lists pq lists let's say and we can have a whole bunch of those but the point is it's still the range of allowed values might exceed 64 like if they all fall within 64 and they all can fit in this list because it will be a circular table and head base value so that's the value because it's wraparound actually you could make it not wraparound no that's probably better to make it wraparound um and then you have to put all the other ones that don't fit into another list that will be unsorted and that you have to process how to do that I've only ever done this with the dykstra one where you can just use a fix of lists so you really need maybe you don't wrap around how many times you have to scan that list oh but the update the update is expensive because these are singly linked lists so when you want to update and move it to a different list and it's not doubly linked so you really need doubly linked maybe the log n prior to q would be easier where's my structure go so what you're gonna have to do is say you have like 32 lists and when you get through all the lists you wanted a single time traverse this big list and distribute them so I guess I just can have some spacing max pq what's spacing uh spacing and say that they are every 16 so the base idea would be that from 0 to 31 I think anything with a value of 0 to 31 has a dedicated list like dedicate to that then anything from 32 to 48 would have a list and then there's a list for everything from 48 to 64 these are maxes have say 16 of these and I'm going to assume that that spread is enough that a single step should never exceed that your single step could you know fall down eight levels and so whatever your penalty for falling down eight levels is plus the amount that that increases your estimate by which is ballpark 64 I can't imagine it would you never actually get up to like a hundred this all make more sense whenever I write the code that processes it um okay something like this so now we're replacing this really small chunk of code with a much more complicated set of code so if old path find new one all of that gets zeroed automatically it's startup so then when we go to add a node what do we do first we want to handle the special case that it's empty I guess I should not call it non-secondary let's call it primary so if there are none in the primary lists then I make the secondary spacing be half of the let's make that always be forced to be half of the half of that if both of them are empty then we do something special we're going to list we're going to put it in the very first head entry and we're going to set base value of cost and the secondary base value two so we're setting the starting state that the first list has in fact that whatever that cost was it's actually cost plus estimated remaining well you should let me do that differently we should store that in the thing instead of storing no yeah I mean it's just bug prone that I might remember miss or forget to do it I've got to be constantly dealing with that value here so it seems like if I just stored it always it would be better let's go ahead and switch that switch the meaning of that so just find every reference to it and have to change ever to it because it means something different now oops so if cost plus m estimated remaining we compute it here and then it's already added when you pass it in it's already added when we pass it in why are we passing an m cost that seems like a bug yeah that's a bug so there was already a bug in the existing a star it was when it found a cheaper path it was replacing it with the cheaper path but not updating the cost correctly so don't know how it ever worked it's one of those things about a stars you can get reasonable results all the time with bugs um okay so we now we add an estimated cost we don't need to do that and oh if we already had a bug in that we reason cost and not estimated cost okay yeah okay that all looks fine so let's test that now that we've redefined that that the stuff still works I moved to this I reordered the code so now this definition isn't early enough so path item here to here it's up over this okay all seems reasonable I'll accept this that looks like a reasonable search it's not quite reasonable actually here it does this zigzag it shouldn't do that zigzag right it should just go through these two so there's still some bug in here but I remember seeing a zigzag like that before so it might be the same one as before so I don't think that's a bug that I just introduced although it's possible hmm interesting I mean I could rerun that search but it's such a long search that trying to debug what's going on it's going to be a nightmare that search failed no oh it just over explored a lot why did it have to explore so far in that direction that doesn't seem right and why is it midair over here what's up with these midair searches that looks like it's searching the wrong the wrong database well this looks a bit like the slope of those things over there right this slope up this thing right there looks kind of like that is that a bug I just introduced or is that a bug that was already there all right so let's go to UI go to here and then break and press L to redo the search and see what it is the search is from last pause to pre-run so it's from negative 13 11 75 to negative 32 negative 26 74 get the stash we want that one 9 1 a b 7 here do a search and then we'll trigger that let's see last pause negative 13 11 75 negative 2 76 74 and we run and it did not do that crazy thing so we did break something okay so something broke in those changes all right all right returned on the old Pathfinder okay let's put it back to computing the estimate on the fly and let's put this bug back maybe I'm misunderstanding this code and somehow that's correct but I'll see how that's possible and then was there something else did I have yeah this compares it directly no changes there okay where is it okay press L 13 and it's okay okay the other possibility is that this will be another bug where it only happens if you run it multiple times like maybe I'm not triggering it because you it depends on some previous state so now I want to go back to the this version check it again is that it have you ever coded for 24 hours straight I don't know probably not do you just get bisect for finding errors like that um yeah I have never used get bisect um in this case I didn't check in the intermediate states enough um and I don't know how I could deal with the get bisect having to type in the in the debugger having to type in the things I think get bisect is really only for catching compilation bikes no I guess it must be designed for that as well I don't know how it works um so why is this not on the right line why does this get moved away from the correct line yeah it would make a lot of sense if it was stale data from a previous run that would allow the you to get that shape from a different place um okay so now it doesn't have the weird stale data but it did extend a lot the wrong direction which seems wrong it does not seem like it should um so let's see if we start getting yeah here's some bogus data so yeah once we run multiple searches it's getting some stale data somehow um so we need to figure out why it's getting stale data and let's um check if that happened from just this update or right so I should get rid of that and then we'll see if we get still data running this one it is extruding out the wrong way but it doesn't look stale necessarily oh they're still okay so it is still already at this point it is doing still data at that point yeah I like to look at all these out here okay so that's with its stash so that's with the version that I checked in that I said was working it's actually still having stale data okay so something in this um so what about just the move pathfinding data from what will destruct a7d7 it's really not the stuff I checked in yesterday that was would have been obvious okay the first one is obviously fine then we try another one and just just directly there oh oh no it's not directly there it's got a bunch of stale data okay wait so let's go back to 918b7 that I checked before I assume this one is fine yeah that one does not have any stale data anywhere okay so that was just moving it into the struct moving into the struct we somehow left some stale data now you remember I memset the structure I know what it is I bet so remember we used to have stale data problems and that was the bug I chased forever yesterday that turned out to be numb open wasn't being updated now I moved it wasn't being zeroed at the beginning and now I moved everything into the struct and memset the structure um to make sure that everything was getting zeroed and we wouldn't have that kind of bug but I also changed it I initially had it on the stack so it was declared locally and I was memsetting it and then I moved it out and copied the thing into a pointer to anticipate being a pointer and I bet you what I did is the memset says I'm not looking at yet because I know what this bug is I bet you I wrote this originally right to zero the thing on the stack but then I changed it so that right it was this but then I changed it to this something like that and I fixed that but I didn't fix this so it's only zeroing a pointer I bet that's what's going on so let's check that theory search for memset yep I'm not even gonna test it I know that that's what it is all right so now I need to get check out master get apply stash no get stash apply all right now let's check what's going on okay that's a terrible search it somehow spread all the way out so I still have some kind of bug here this makes no sense at all let's do it since that's the only substantial change I made that I can think of nope now it's still doing the spread search one too many let's go back to this state and make sure that this is reasonable oh wait I have to do the memset okay that's a relatively direct thing it has a little bit of offshoots to the side but probably not unreasonable there's a very direct one it's a little a little out but not the big crazy thing so a little bit out I guess that's caused by that dip like here we should be able to street shot it yep it just searches a straight shot that's perfectly reasonable the ones next to it it never actually expanded I'm not calling the closed sources open but presumably it never expanded these ones next to it it just had to when it visits this one it always expands all the one or explores all the ones next to it and adds them to the open list but it doesn't so yet you do have to get them along the edge so that's perfectly reasonable and so that means that the stuff I was getting on the diagonals is also probably reasonable yeah this is that eternal problem that all of that data is kind of valid but all of that search space is not about yet more of a strict diagonal yeah I am not sure why it goes one out more but hey that's the lower bound estimate that seems already reasonable okay so so that one's working this is in fact breaking something um oh get stash apply I didn't notice it was the same change from both this is just that trying to propagate estimated remaining in and making that change okay so I'm missing something so first of all if we undo that change my my reflection was it was still going wrong so something that I've done here is not sufficient um yeah so this is still searching this big area even with this bug fixed so my attempt here to propagate it into cost everywhere I've like missed somewhere something is not something did not get updated that the cost is now the cost plus the estimate but I don't know which it's supposed to pass in a cost it's already updated this only looks at cost because it's supposed to already be updated the other these look at cost passing a cost that's already updated we can pair a cost that's already updated to a cost that's updated passing a cost that's updated I don't see what's missing what have I not done here who could look at that who could be broken by that cost did these things look at cost no these two but let's comment it out um how could this happen the initial node we create we create with cost zero we never said it's estimated cost oh that was always a bug we never said it's estimated cost it's pretty bad maybe that doesn't matter because it never gets compared to anything yeah it's probably probably didn't matter very much until we added this dependency and then cost is now zero plus yeah so the problem was searching for cost didn't find it because it's a parameter to add to open list and I just wasn't looking at that case all right so that should fix that geez stupid bugs spent almost an hour not even getting to that oh it didn't fix that great yeah because that really shouldn't matter that that one is zero like I said it's only looked at this very first time um so it actually shouldn't matter so that was a bug that shouldn't have mattered and in fact didn't matter when I ran it didn't change the outcome at all um why are we allowed to get that far away should I just break in here and try to look at things it's kind of big maybe I'll see something going wrong when I step through it all right so we create a new node if you estimate costs from there why is our cost already so big we didn't go very far because we're starting with n error cost there's the bug because this now needs to be that there's one place that needs to know the actual old cost so let's do one thing at a time put this bug back in all right good it starts directly boom okay so maybe that crazy zigzag is caused by this bug that I was saying the zigzag that's not very reproducible so fix that bug okay 50 minutes 50 minutes to get all the bugs out maybe nope that path went failed because I was was I in mid-air yep I was in mid-air okay okay so there goes around instead of going over because of the penalty for going over straight lines let's make it go a long way see what it did oh it just avoided that whole mess okay seems reasonable so back to what we were doing which was trying to write a new open list implementation so now our cost includes the estimated cost so it's the actual value we want to sort on and we can just use it directly here which is what we the whole point of that whole excursion uh this is supposed to be equal zero all right so that's our starting case if the lists are all empty we can do that I don't know if it's possible for the list to all be empty after the very first time but it'll handle that case um you have to add see no primary no that add to list well no we do have to let's actually do this then we say add to list add to primary list how do we add to the list we figure out which list it is that's one spacing no no we're not going to mask we'll actually move the arrays around so we don't have to do the mask so primary at this point okay so very easy you just say pc head okay so we have to update the okay so x equals n minus pc arrow nodes so x is our index into the list into the nodes array so pc arrow links node link x next equals pc head list one pc head list equals x there's a list insertion into the primary list secondary list pc head secondary value shift right two spacing that's currently four spacing two usually I would define one in terms of the other but I don't really feel like dealing with it right now so I'll just do that um let's pq secondary and spacing all right so we shift we subtract out the base we shift it right boom then secondary this is second okay so we get to here and we say if cost is less than pc head base value plus primary otherwise we have to add to a secondary list every time I look like this I'm looking at chat I just don't reply to things directed at me that aren't questions because I don't uh want to switch out of programming mentality mode just for pleasantries so I usually skip pleasantries just you can pretend I did them then we add to secondary list so I probably could have inlined all that code here because they don't actually end up doing anything else and I don't think I'm going to be calling them anywhere else no I am going to call them elsewhere okay so what do we have to do and then we have to update and then we have to get smallest so update works like this if cost so let's then head base value plus num pq primary so we're looking at the existing value of it so currently on primary list currently on secondary list so it's currently on a primary list then the new cost is lower so that's also going to be a primary list we know for a fact so we remove from list pc n actually set we never actually assigned that um so we move from where it currently is and then we will insert it to the right place and we know it's primary list so we can just call that directly and I don't know why we're still passing out around the cost now that I signed it explicitly I guess I thought I would do the assignment here instead and that doesn't assert so if my assertion that it should always be on a primary list is wrong okay it's currently on a secondary list it might be moved on to a primary list it might be moved on to a different secondary list or it might stay in the current one so what we want to do is compute secondary uh let's call lists it's the same code as what's up here so figure out which list it's currently on which is n cost this is the current one and then new list uh let's do something slightly different if the new cost is less than pc head oops that doesn't work it's value it's moving to a primary list let's just handle that explicitly from list pc n do we I'm just going to have remember to most be shared because some of the code is common but it still needs to compute which list it's on and it's going to be again computed redundantly here and inside it all right well we can always optimize more later have you compared your current and future future set to other boxing games out there I'm interested in whether this is just for fun learning or potential work stream I have not compared it to anything out there um it's not exactly for fun learning because uh well I mean it's for fun it's not for learning because I've done I've already have a Minecraft viewer I didn't do a lot of this other stuff but it's more in the hopes of creating an engine that is potentially usable either by me or by others I don't know which but it's also primary to create educational material um that's why it's all being programmed on stream why did that not autocomplete add to primary list do I have a bug above it I don't see a bug above it okay so if we're moving to a different secondary list but if we're moving to a second you know this we need to find out which list it would be if they're the same list we don't do anything otherwise we remove from list we add to secondary oh there that's where the end cost update was try to compile it numpyq lists primary what is it called secondary base value let's rename the other one actually too I'll have an implement remove from the list I will answer questions I just don't do chat don't do chit chat um so yeah this won't compile because I don't have to move see nodes it's less than I'm doing this function a lot I should wrap this in a function okay no compile errors I have to actually finish writing remove from list though um yeah it's a mix of features I still use immediate mode for UI rendering and stuff like that so it's using a compatibility context and it's using vertex buffers but I don't remember it's using vertex array objects I think it's not and but it is using things like integers in shaders which I don't remember when that got added which is part of why it's such a mess I never remember when different stuff got added and the array objects were busy work for a while the you know a lot of drivers the array objects weren't any faster they were actually slower people had posted lots of tests that showed that that it was better to just use one array object and keep resetting the things inside it which is kind of stupid so we're removing so we find out if it's primary and then we want to know which list it's on so if primary then it's list is and across my sqc arrow a base value or secondary base value shift right to use secondary spacing long two and that's another one I should put into a thing yeah I'm working on an open gel framework I've mentioned it before and one of the things it does is it provides uh is it uses a core open gel context and then provides its own immediate mode api that's just a clone of the gel open api immediate mode api you should be able to use it with unchange code but just so because like the mac is uh doesn't support compatibility contexts it just has two modes one's like an old compatible context that's like I don't know 2.0 or 1.3 or whatever and then the three point whatever that mac does 3.2 is core so it doesn't have immediate mode on the other hand open gel uh is kind of dying on mac because they want everyone on metal so it may not matter but I figured it's worth doing just not ever have to depend on the compatibility mode but that's in that framework that and I won't even switch obbg over to that framework anyway although I could isolate maybe that part of the code but the compatibility simulation okay so wherever we're computing the secondary value right here it's called secondary list index okay we're back to get small stupid so we still got finished remove list here remove from list okay so now we've got which index it is so what we do we need to do we need to undo all of this so if link x dot read is negative one then we have to clean up the header if primary list equals next secondary was next else we need to make our preve so that's not negative one so it's a valid value so we need to index into it and change its next point to our next and then finally we have to do the reverse of that to change next dot preve to preve but we only do that if this is not negative one that's a less than zero I believe that is correct in the limitation of remove we clean up the header we clean up the previous we clean up the next so at that point it should be removed so I think we've implemented that all correctly now we have to finally get to the big part of this and a reminder this was what the old implementation like like that's it that's all we did but it was super slow because it was doing a linear search every time however like how big is the frontier it's maybe 20 30 nodes so the priority queue with a log-in tree but with the heap I don't know like that's still a linear search is not so bad at that size maybe but this should actually be reasonably fast because it's only just going to do one of these operations every time there's just no sign that mac is going to ever support a new version of open jail ever maybe they will but people are assuming they're not some people I have no idea I don't really have my hand in there okay so we just need to implement get smallest open we have a whole bunch of lists and we just have to search all the lists so let's actually you never insert something smaller than the current minimum yeah because you're always adding because costs are always increasing so we can actually also store oh I need to update these I haven't been updating these we also need to store first non-empty I'm just referring to the primaries okay so so let's update those counts so this one the increment primary this one we implement increment secondary and this one minus it okay so what do we actually have to do here zero first non-empty which we need to initialize let's go initialize that up here add to open list probably don't need to do it still see because we're whole thing is initialized to zero and then we're going to loop that to primary but we're not going to go all the way up we're going to stop halfway now then we check if this list is not empty which is head by gradient equals zero okay great if we found that it's not empty we reset first non-empty to the one that we found non-empty although it might become might be about to become empty here and we say to get my chances globally without the function and we say n equals pc arrow nodes of pc arrow head of i and then we say list so we search down the lists if the list is not empty we return the first item otherwise we search around and keep going through all the lists so here's the place where we'll loop but we won't loop by very much the spacing currently is four you move by four and six so it's possible that this will populate like 20 24 28 and you have to skip by four at a time you visit four empty ones so we could shrink that down make it two and three instead of four and six to improve the performance there i made it four and six so that i have more resolution for the vertical offsetting but but it might be worth giving up that extra precision here by cutting all these in half and losing that extra precision there so that becomes seven that becomes one that becomes one this has to become two i guess for quality uh we could just make it one and then compute the estimate it's become three and two and when we compute the cost where do we add the cost right here we add two and one which is a total of three okay so that cut those sizes in half that'll help the performance a little bit and now because you have twos and threes you know they you can get five you get seven so you can get all the odds as well as all the events so you should hit every single one so this loop shouldn't ever need to actually loop over stuff if you go in a straight line if you like have walls that prevent you from going diagonally and you just go in a straight line you'd only hit every other one okay so when we get to the end of this we still have half of our primaries available um but uh we don't want to go through them exactly directly what we want to do is let me move let me see arrow head over two we want to move all the remaining ones down and yeah the wiggle proc address stuff um I've always up till now I always have just used gl 1.1 headers and wiggle proc address extensions and that's part of what that new framework is for is so that I'm actually I wiggle proc address or get proc wiggle get proc address everything now instead of keeping direct the direct linking to the ones that are defined in gl 1.1 and only having to get someone I know get all of them just to keep it all clean and consistent um and and you don't link with the gl opengl32.lib at all since they're all coming out of the thing okay so we move all of the primaries we move the second half the primaries down to the first half which means we we have to change the primary base value by this much and then we move the secondaries well let's not do that yet let's say then now we need to replace uh we need to memset um well let's use a loop for i equal u primary over two i less than so we just moved all of these we want to store them back to negative one now we want to take the first secondary list because it corresponds to this set of things so we want to go through the first the the secondary list um so while the first list is not empty we want to equals pc nodes of pc secondary of zero we want to get the the thing from the list we want to remove it from the list that it's currently in which we know is that list so i could i could try to optimize this and remove it directly here or walk down the list rather than what i'm doing is deleting it and always checking the first one i could also just walk down the list so we move it from the list and then we want to add it to the primary because the whole point of this whole design is that we have the secondary list that store in this case 16 different possible values and once we've uh shifted shift shifted our window over we now we put them actually into the primaries where they're broken down one so we traverse this list once and during this traversal we spread it out to all these other ones and that is how uh um is that how i had to do this stuff when i updated oh it was an updated cost there yeah and this has asserts to make sure they're in range so that'll check that logic okay so now they've all evolved and spread back out over that we've updated the primary base value um as far as we know the first non-empty is now back at zero because we slid that down and we just want to go back and start at the beginning again and it shouldn't ever loop through this multiple times you'd have to have this really sparse set of values which we don't have in that case but i believe that is correct and then i have to move sorry i forgot to do that i move pc secondary c secondary plus one um num pq secondary minus one times size of pc secondary again this would probably be clearer if i drew a during illustration does somebody want does anybody want me to draw an illustration of what the heck this algorithm is doing i could do that now that i have my my uh tablet and software installed i could go quickly through and show what this algorithm is doing because it's too hard to draw isky art yes i see that it's a yes to my question should have done this first but i didn't think of it oh hey look i still have this old thing how do i new milton calibre is all right yeah so it's too bad i didn't do this first sorry just didn't think of it so all right so the basic idea a star explores this frontier of of things and assigns them a cost so it's searching from here trying to get to here you know so these are nodes this should be a grid but in like it's a continuous representation of a grid and so at each node the cost that it's storing so in a normal depth first search or dykstra's search you're expanding out and you're storing how far you went from from the origin to get here at each of each of these things so it looks like this i was just going to talk about the particle but i'll give the quick quick explanation of what the a star is doing as well not how it works but what it's storing right and then these are all ones around this so it keeps doing this and eventually one of them expands to here and voila um and you have to be a little bit careful because you know this one says oh here's a thing i can get into in seven and maybe here it comes around and says wait i got to here in five and i can get to here in six but i've already stored the seven so you have to still be able to update the priority queue in this as well um the value you've added in the past but once you process it once you finally hit this seven or six maybe it turned into a six once you finally process it you can never get to this one again in this algorithm you'll never come back to the six it's once it's six once it's explored you can never get back there any cheaper because you've already explored all of the cheaper nodes so anything you explore after this has to cost at least as much or more to get just to get there and then to get to here again would cost even more so you'll never go more than once in a star we don't store the cost from here to here we store the cost from here to here plus the estimated cost to get there so if like the total distance vertically here is about 10 right we start with an estimated to cost of 10 when we get to here this one maybe has a cost to get here of three and its estimated cost is like eight so it has an 11 here uh and this has an 11 here and in the case of a straight line what we'll find is our if our estimate is perfect as we explore down this way it'll just keep saying 10 all the way to here and it'll get to the end with 10 and all the nodes next to it will be 11 and it will never have explored them because they were all cheaper than 10 I mean all more expensive than 10 but if there's an obstacle or you have varying costs the a star is particularly interesting when you have varying costs or if your estimate is not accurate like maybe your estimate says the cost to move in that direction is one but it turns out in the area you're exploring the cost of moving that direction is two then what happens um then our estimate cost from the start is 10 and when we go one thing here it took two to get here so it's 12 and then it's 14 and then it's 16 so eventually this is what we're going to discover right and it's going to take us all the way up to 30 to get here um and so since that is that expensive this one right next to it is uh costless two to get here right because I said every move is going to cost two costless two to get here but our estimate is still a little more than 10 let's say the estimate is 11 so this is say 13 as the estimate the low the estimated cost it thinks because it thinks it might only take 10 to get here because it doesn't haven't looked at all this yet when it computes this it doesn't know what the state of all this is doesn't know that all this is going to cost it too so it assumes the lower bound tells it that it's maybe going to only cost one and says it could it's possible it could get here uh in 13 so what happens is it explores this one this these say maybe our 15 um and then it's looking at this and it's like I'm not going to explore 14 now I'm going to go explore 13 and so it explores these two so the number that it's storing in this queue it's this different number that increases in a different way and can result in when you're in a slow area can result in you having to expand out and do a lot of irrelevant searching but compared to a thing that always just goes out from the center without prioritizing going towards the destination it's still cheaper so that's the basic idea of what's going on in a star and the important thing here is that these numbers so here these numbers are just constantly increasing right they just go up as you explore out and there's it's very easy to do here the numbers never get smaller because the you're supposed to have an admissible lower bound which means they'll never get smaller but they increase in a different way like here they all started at 10 and we ended at 10 this one never actually increased all of the things it was searching were 10s and then all the other ones were 11s and in this case though when it was slower once there's obstacles or once there's varying speed terrain it does start increasing okay so now let's talk about how we deal with that frontier so we're storing this frontier and each node has a number and we always want to grab the lowest one so the existing implementation just stores them all in an array that's randomly sorted and it just scans the whole array every time because what it wants to do every time to expand the frontier is find the cheapest one so it'll you know first time it'll search that second time it'll search that next time it'll search that and it just scans the whole array to find them wondering also wondering if the search includes nodes in all directions beyond the adjacent eight or whether it follows straight lines north south east west from the current position includes nodes in all directions beyond the adjacent eight or whether it follows straight lines from the current position I can't quite parse what that means every time you expand a node it looks at the adjacent eight well it's worse because it's actually in 3d but pretending it's 2d it looks in the adjacent eight and then relies on the frontier expansion beyond that maybe the stuff I just explained already answered your question I don't know okay so the normal the ideal it's not exactly normal because plenty of people do this optimization that I'm currently doing they're in a different way so normal thing to do instead of this linear search is to build a balanced binary tree and a balanced binary tree lets you find the minimum element very fast but there's actually a special kind of balanced but that's not a balanced binary tree it's a special day structure called a heap that is only good at finding the minimum the balanced binary tree lets you keep them all sorted basically so the heap is a tree a binary tree that only stores up pointers no oh you actually there's its whole implicit encoding in that you do it where you store it in an array with you always store this one in the first node and then this in the second this in the third like you store this array you actually leave out zero so here's the zero slot and you store the root here and then you store these two here so this is level zero this is level one this level two then you store level two here and the rule in the heap is that say you're trying to make it so you can find the minimum minimum easier the rule is that for any given node the children are always larger but you don't care about the order of them and what that turns out to do is that if you insert a new uh say say that your uh your tree is only oh this doesn't have any eraser I keep so used to my old thing all right so if this is what your tree is the state of your tree currently is when you add a new node you add it here and then you check is it does is this rule obeyed for this connection and if it's not you swap it up and you actually also have to then check for this connection but the result is that you just have to do this log two traversal of the tree and then when you need to find the minimum it's always that but you have to delete this now say you swap the last one down to there and then bubble that one down the tree so that's also log in so you can find the minimum in log in time and insert a new one in log in time okay so now the new algorithm that we're doing uh I'm using the magic number 32 in it but we'll use the magic number eight for this description so I don't have to do too much what we do conceptually as we keep eight linked lists with all the values of that thing remember in this I had two elevens and one ten right so here I have three things of zeros well this is the head like this this is the head of the list so it's not actually a node here's this that's one say there are no zeros say there are three threes right so when I want to insert a new one if it goes into six it's fine I just put it at the head of the list right if I insert another one then I break that link let's start over I insert one at six I'm going to draw it like that now I insert another one right so I break this link make the head point to the new one make it point to the next one let's just standard list insertion so that's I can insert things at six now in that this case the dykstra one this one where I'm always expanding out if I know the maximum edge length is say four right um then I'm always going to expand and visit all the twos I'll visit all the twos before I get to the threes the moment I start doing the threes the if the maximum edge cost is four then as soon as I start doing threes I might start inserting sevens but when I was doing twos I couldn't insert sevens because from a two the edge cost to get seven would be five and I said that the maximum edge cost is four so the range of valid values while I'm doing threes is three to seven and the range of value values while I'm doing two is two to six the range of valid values while I'm doing one is that so what we can do for the dykstra algorithm not for a star if we have that edge bound is I only needed five lists so what I can do is I can have five lists and then when I'm done with zero I reuse this list and start using that as the five lists and when I'm done with one I start reusing that as the six lists and so it's like a circular buffer I recycle these lists to larger and larger numbers now I do have a bounded edge size but it's a star so there's the computed value is this weirder thing and I don't know what the even though I know what my maximum edge size is I don't really know what the constraints on that are I don't know how that works out in a star so I'm just pretending I don't know I don't have a very very good bound I do have a bound so instead of doing this I'm doing something a little bit different I know that I might have to add things outside this list so what I do is I make another list which is 8 to 11 and then I make a list that's 12 to 15 and then I make a list that's 16 to 19 and I keep making these lists until I have enough lists that I think I'd never run out like and so I go up currently to you know in this scale maybe it's 64 say so a lot of these little lists okay and each of these is a list and they can have things on it and where these lists up here everything on here is a zero everything in this list is one everything in this list is three everything in this list is 8 to 11 but not in any particular order so there might be an 11 and an 8 and as I keep exploring nodes and adding to the lists I just keep pushing them on to this list. Occasionally I need to update things and say they're cheaper than they used to be so to do that I delete it from whichever list it's currently on and add it to the whichever new list it is so if I have an 11 and well say I have a 13 and this 13 gets revised to 9 then I delete it from this list and I put it on this list if the 13 gets revised to 7 I delete it from this list and put it on this list right straightforward straightforward when I can visualize it here and to show you what's going on so now the big operation so that's what the ads and update needed to do it was just all that no that's all very straightforward and we we saw how with the Dijkstra one there's this rotating uh circular buffer style rotation through the lists they change meanings and I was going to do that for this but the handling of the secondary lists meant I needed to do it a little different so what I do for this is in the Dijkstra one as soon as I was done with zero I could reuse it for something else but I'm not doing that for this so for this whoops why does it think the mouse is down it's not down what it's not down it's not being down okay uh tablet's fault not Milton's fault I'm sure um so what I do in this algorithm since each of these is covers four a range of four values I block this up into groups of four values so I have the first half and I have the second half of the primary and what I do is when I when zero is empty and I start looking at one which is the first empty variable when we go back to the code the first empty is telling me where I'm looking so like once these three are all empty first empty we'll be pointing to here uh I don't I don't do anything with these but once this one goes empty and I move to this one now what I want to do is basically swap the meaning of these two lists but I actually just do it with a mem copy so um I copy this list down to here all right so this is beyond four five six seven and it has whatever is currently here so I've copied these pointers down to here then I have to mem set these pointers because these pointers are now becoming eight nine ten and eleven but they're still currently pointing to these same things so I zero that stuff out now the eight to eleven was currently a secondary list so I walk through this list and I start adding them over here so I see the eleven I delete the eleven from where it is and I added here I see the eight I delete the eight from where it is and I added here and at the end of that I've erased this whole thing so then as a final step I move all of these lists up one and then I remember everything so this becomes twelve to fifteen etc and this down here is expanded to cover one higher thing so I now can cover once I advance by four and flip everything then my second my last secondy one also advances by four and I can now start recording four higher values therefore higher the node front here is the forward direction from the starting node it's not a direction the front here is the set of values like if we look at the Dijkstra algorithm once I've explored all the ones and twos but and I have a list of threes that I haven't explored yet and I'm about to start exploring all the threes all those threes are the frontier they're the nodes that I've no about but I haven't visited yet that's the definition of the frontier in the a star case it depends like in this case the frontier might look like this it'll be all the 16s and it'll be everything else that I've discovered but aren't visiting actually it's the outer so like when I was in the game and you could see that all the dots of all the places I did explored the frontier is the outer edge of all the dots basically when it found a path from here to here the frontier is the nodes that you haven't explored yet but like I said I'm not really explaining a star I'm just trying to explain this data structure here so that's this data structure so now if we go back to the code right I can explain what's going on here in the code again in terms of that thing we just saw so this loop starts from first empty and goes halfway through the list right so that is going here first empty whichever one it knows is non is because it might know some of them are empty it might know the ones above here are empty so first empty starts here and then goes to the halfway point so that's this loop goes through the halfway point looking for a non-empty list when it finds one it grabs that node okay when it gets to the end of that it's exhausted the first half then it starts doing that thing I was talking about so it copies the second half over the first half that's this mem move it updates the base value oh I don't update the secondary base value need to do that it updates the base value which means the base value tells you what number is stored here base value right that's telling you what number is stored in the very first slot and the others are all implicit because they're always one higher and then secondary value is what's stored here and that should always be synchronized to this but I'm just storing an explicit for clarity right so we update that value we update the non-empty because we just moved this data up so we don't know what's non-empty anymore so we point non-empty to there we walked through the second half and zero them so that was this filling that with an empty thing that I mentioned then we go through the first list of the secondary set which is this right here and we remove them from that list and add them to a primary list so that's what I was saying about taking each of these values and inserting here then here we move all these pointers up by one to switch from this state to this state and then I just need to add updating pc arrow secondary base value equals pq secondary okay I've seen any of the explanation that's anybody have any any questions about that that is obbg 47 it's not actually done yet but it's close we have to debug it okay nobody has said they have any questions so I think that other than bugs should be everything there's a question in french so we're going to run it and it's ink on a work there we ran it and it opened one node all right so let's debug we have to actually record them open or we have to change this to be that's that's actually the data structure that's using accessing the data structure so if do this let's use annul to indicate that let's fix the old one first so get smallest open if open and then this one we even had this whole thing where we incoming primary and incoming secondary so then when we get to smallest one thing we can do is if primary we don't even need to scan it if we know there are no primaries and if we get to the end and there are no secondaries then we're done and we didn't ever actually need this path there then it's not equal we just needed it here you're supposed to at least answer by saying that was a great explanation thanks so I know I didn't waste my time doing it all right all right list is negative six so pc primary base value so something went wrong with that logic so who did we call it from add to open list claimed it was primary cost is 97 zero primary one or three yeah that seems in range did I subtract in the wrong direction did I pass in the wrong cost oh did I look when I was looking at this was I looking the wrong direction yes 97 is less than the base value that should never happen how did that happen is that possible no what's n cost n cost with 72 how can n cost of 72 be 72 a pc primary base value was 103 okay thank you for the feedback um like I said that all would have been so much clearer if I just did that explanation first before programming it that's too bad maybe I'll put in the video jump forward to hear for the explanation and then jump back no I probably won't um oh I guess I just got to step through I shouldn't have restarted I should have rerun it okay n cost 72 pc primary base value 72 okay let's break in this okay pc primary base value changed which it shouldn't have it was 72 before oh but we deleted the last one so it was empty again so it could reset it and when it resets it it needs to actually zero everything I remember saying I wasn't resetting everything because I didn't need to so I need to set everything here what else is there to set I'm probably no second error zero I'm open we don't touch first not empty is the only thing I actually need to zero that so I shouldn't have broke because that is already actually being done let's do this but why was it being cheaper that that's weird now the cost it's okay so the first cost it adds is 72 but then we come back around to the second time now the cost is only 34 how did that happen n cost is 72 how did that get cheaper right so pre-cost was zero remaining is this not a valid lower bound that's the only way that should happen how did that lower bound change by that much it's the very second it's just the absolute second noted node 000 to 111 000 to 100 I mean um that's going to distance the lower bound we're supposed to pass in dest do we pass in dest or do we pass and start passing dest we de-realize and pass in dest whereas this one we don't de-realize all right so the very first one had a bogus thing not sure whether that causes the problem but it's certainly bogus and it probably didn't matter it wasn't affecting anything else because the cost as I mentioned before the cost of the the starting node doesn't actually matter to anything the the value stored in the priority queue okay so now we come around the second time the new cost is exactly the same as the old one so that's good um let's just run it oh it worked this time oh now it fails all right okay now we're back to this case where this list is negative so we got a cost that was lower than our primary base value so we want to know pc arrow num primary zero and num secondary pc arrow primary base value zero secondary so that was the very first node that we inserted was at 16 so we've advanced this once to 20 but then we got a cost of 17 how did we advance past the 20 and get a cost of 17 we pulled out a thing of 16 uh okay I see how this happens yeah we're not going to see the performance difference on pushing because the performance wasn't visibly slow um we'll see if we can get a test later that shows it but this has taken so long that I don't know how much of that I'm going to get done um okay so uh as long as so what happens here's what's going wrong it's putting one thing in which is staying scored it's 16 let's say and then it's exploring and so there's only one thing in the list so it takes out a list and the whole thing is empty again and the thing it has is 16 and one neighbor of the 16 costs 20 and one neighbor of the 16 costs 17 so it goes to add the 20 but because the whole thing is empty currently it resets the whole thing and advances forward and makes 20 be the new small value and then the next thing it inserts is 17 and that can't fit anymore because the base is now 20 so what we need to do is have this only run once and I think the way to do that is to just do it outside of this so this is not old path lined so it's not like that dang it so somebody isn't incrementing these things correctly but we're doing it when we add to the lists and remove from the lists right so that it should just always be consistent so here we just remove something from a primary list and decremented num primary no we decremented num secondary why is that set to primary oh because we've already update mm-hmm yep because we already updated primary base value here we can't do that ooh we can't add this so what we have to do is lie about the cost here to add it to the correct spot but then it'll store the wrong yeah I intentionally updated this here so that attempts to add to the primary list would look at this value and work correctly but to make that work I need to do this so it doesn't try to look at whether it's primary not but I wanted to avoid replicating all this code was the thing in which case we might as well just do them all this is moving from a secondary list from a secondary list and then we change this to be from primary list it's not so bad and I can actually shrink it a little bit because of my very style we got the primary list let's try it again that failed but I might have been in mid-air yeah I was so that'll fail again and then this should succeed uh no it explored a little bit and then failed explored a little bit and then failed yeah so exploring exactly one and not managing to pull anything out of the open list it's never finding anything from the open list all right so it'll find the very first one then when it gets here again no it found something it found something it seems to be finding things you know it's never actually adding anything though all right let's rerun it okay it finds the first thing as its neighbors see you let's see somewhere where we add it there boom so we added we add the eight neighbors okay now we come in find another one I'm going to guess is that we're going to hit this case and the cost is going to be equal every time because we're actually always re-grabbing the same node I bet for some reason I don't know why but I bet we're just re-grabbing the same node every time PC data is that I address a PC node zero because that's the first thing in the structure yeah yeah so we're just constantly always grabbing the same node so remove from primary list is not removing so some I somehow broke this one I was in here still zero this PC node link x next so some the remove is no I don't know how did I I was pretty careful when I deleted that stuff I don't think I broke anything while I deleted it did I primary it should all be in the primary what did I change add in your fact why you are coding in C do I not have that in there no I don't yeah it might be somewhere in the STB libs info but um yeah yeah I could add that to the fact so what did I change when I did this I haven't checked this in have I so I can't diff against the before I changed it and I've exited and restarted so I don't have undo secondary in head that's the only should be the only difference between this is secondary in head and then the list computation this should call that function secondary list index that won't make any difference it is the same how did that break that I only think I changed with some remove right did I change something else I changed these to explicit call particular ones currently on the secondary so both of these should be removed from secondary this should be removed from primary and then the get has to remove it so get removes it from primary remove some secondary adds to primary when we add to the primary we've already updated primaries so that works remove from secondary haven't updated secondaries so that works we're setting this self to next node links of x.prev next is the same as node link of x prev next is the same well I mean this was the same in both in the previous function I didn't touch that but prev next goes to next and next prev goes to prev is correct for link list deletion the the normal version of that is p prev next equals p next and p next prev equals p prev that is actually correct you just have to work through you know the p prev is a box with in it with the preven next arrows and p is a box right if I were if I had built enough I could do draw the diagram but I don't want to actually do the work I just am saying you guys you can do it for yourself this has these pointers that point to the adjacent ones right this is so if this is x or p I was using p for that example here so if this is p then p arrow next points to this guy and p arrow prev points to this guy so then p arrow prev next means go back to this guy and change to this pointer to point to p next to point to this guy so you're trying to edit that so that this so this guy this pointer points to here and this pointer points to here that's the whole point of that code and I didn't change it so I don't think that's what I broke it's just using indexes and a little cursor to read because of the the indexes and fetching it out of the context um yeah I don't see how splitting this could have changed that maybe this is a different bug exposed by getting past the previous bug and it doesn't have anything to do with changing this we add to primary add secondary as long as there's nobody else manipulating these lists outside of those four functions I don't see how those could get out of sync and do I manipulate the list directly this guy manipulates the list no but he uses the removes and adds um move add he manipulates the lists in a particular way that shouldn't affect the counts he's just moving them around yeah I don't see anything here that should affect the counts and then nothing out in here this is doesn't this is abstracted away it doesn't know the details so it can't uh and we did memset it right we memset it here um so that seems reasonable to me god damn bugs I hate having bugs I want to make progress um anyway the performance here is going to be mainly on long searches also short searches would have been fine with the old stuff because this is doing a fair amount of work it only has to do it once it never iterates but it's doing a fair amount of work and they were all indirected and stuff so it's a little expensive um one thing I didn't really explain uh the idea here is that updating a list updating a node is always order one because it's a doubly linked list so you can remove it from where it is currently find the right list to add it to add it to that list and it's fine um then the so the main extra thing that we do here is this while loop but we're taking nodes from a secondary and expanding them into the primary so any one node this can only happen once to that node that node if that node is ever in this secondary list that gets expanded it gets to leave from the secondary list add it to a primary list primary list never go through this path so any node single node this can only happen to it once so what that means is that this is amortized over the cost of all the nodes so it is still order one per uh per um add every time you add a node to it uh it's order one amortized this thing might on a single call to this function to get small step it might iterate a bunch of times here and take a little while but it will be amortized it does have to if you do have sparse values I changed it so we don't have sparse values anymore if you had sparse values you could loop here multiple times um and pay a potential cost um but it's usually a constant bound even then like this when the values were sparse they were sparse by a factor of two or four meaning this could loop four times every every time which is still actually a constant so in terms of big O it's not a big deal and this and the amount of work it does while looping it's just testing this value which is a 16 bit value so it cranks through doesn't touch much memory it's pretty fast okay so I don't know how that happens and I don't know how to debug it because it's just a total so verifying that it's staying in sync yeah I guess the easiest thing to do is is verify it check in to count primary pc equals zero in my primary zero and we found one and we advanced to the next one we link and so pc equals pc and um primary all right so hopefully that'll catch it when it gets the first time it's wrong unless I have a bug in that and it's always wrong which is certainly possible I got caught in an infinite loop so that I have a bug in it the infinite loops I forgot to increment something um my cursor is okay there's my cursor um I always advance x i is always incrementing it's to the fixed size so this would only happen if I end up with a circular loop so let's check that that's what's happening oh oh yep all right all right n is garbage oh n is garbage yeah x x is zero okay so it's linked to itself so there's some problem in the remove even though I don't see how what I changed could break anything um well actually let's uh let's check why I didn't check what I was inside of at the time I broke let's see which function is calling it oh yeah I have to wait until it actually dies and we're in add to open list and I can't move my mouse because of sdl so I can't step up the stack to find out which uh where in add to open list I was but okay we'll add to open list so either add to primary add to secondary we turned over that and did this assert but add to primary has that asserted the end so it must have been in add to secondary I don't see how that happens add to secondary list it went wrong inside add to secondary list all right let's check it can't let the cursor do I have a thing in sdl to deal with that I think I do work around bargain behavior and v60 bugging I'm I'm setting it by default I guess I can try just killing it but then I can't look around properly um so uh it was happening here so let's try before we update anything see if it's still wrong around it's weird weird all right so now break here this time it's in add to primary list not add to secondary list which could just be that the test is broken still kind of missing something here this time it's add to secondary at the beginning so it was already broken before we got here so it was already broken when we came in here which is at the very beginning it's broken at the very beginning how is that possible why is it adding to secondary at the very beginning it shouldn't be adding to secondary because I used end cost not an estimated remaining still in an infinite loop still at the beginning it's an infinite loop because I don't see the lists to negative one that's why important they're all set to zero instead of being set to one then to negative one so that would in fact make it till we break initialize your data I'm set to zero is invalid notice negative one oh somebody explained it and I didn't see it okay now we hit that assert again but this time it's after we did the remove from primary 72 it seems unlikely we'd have 72 nodes don't know what count returned let's uh step out here no secretary so go back to that step in go to there 70 so it thinks there were 70 on the list do I miss the list heads or something x is greater than zero and now I think that's right so it says there's only 70 in the lists it only went wrong though after we added 72 things that's a lot that's a lot to step through and I won't be able to recreate it anyway because I'm gonna have to break out of this what did I do wrong how did these get out of sync I just removed oh it doesn't assert at the beginning of this okay let's assert at the beginning of this too so that we have a good bracket on where it goes wrong oh that one worked all right failed here so the count from there to there changed so the count there matched the pc primary is now 25 24 okay so the count up here none primary must have been 26 so when we're up here the count must have returned 26 and so somehow we deleted two items in here so something's wrong in this linked list logic because we somehow deleted two items are we using the wrong variable somewhere node link should always be one of those that should be the only reference to that we point it to node x.next we don't know which path we went through oh but we haven't overwritten that yet so we can look at that you see node link x.free see here our node list should be 18 okay so we went through this path that we updated the head of the list we didn't go through this path we updated this so we didn't change it's 13 we changed this pre to go to negative one that's all correct how does this subtract two from the list how do you lose two items from the list by writing this code here's a check we assert pc head list equals x secondary this got out of sync somehow that could cause badness what happens when you remove the first item in the list it does the right thing i mean it's it looks like it does the right thing it changes the head to point to the next item in the list i don't know what it takes to trigger the bug that search failed very badly oh garbage data is the data structure not being initialized properly i'm getting garbage data maybe that's the problem i can't rotate properly because i disabled the rotation stuff so it's hard to look at the oh shoot i hit a i was moving so i hit a i should change my keyboard bindings just so that the movement controls don't uh that that search failed i might have been in the air garbage data all right well never mind this bug okay no we're gonna trigger it aha that assertion failed so the problem is there are two different ways that you can tell if the current node is the head you can check that the head is pointing to it or you can check that its previous is less than zero the problem is those disagreed it was negative one but it was not the head of the list so that's going wrong somewhere and the easiest thing to do is to check that here if x if pc node link of x dot preve equals negative one assert pc head and because we have assertions that are doing the count primary all over the place uh we'll hit that assertion let's see if it goes wrong hopefully okay we hit that assertion we hit it here after add so we made the head point to x and we made x's previous be negative one and we forgot to make the next if pc head of list is greater than equals zero then pc head of list pc node c head of list dot preve equals used to be negative one right here and we change it to point to this guy so we forgot to update when we added it to the head we got to update the previous the following we forgot to update the next nodes pre employer that's why i always use singular linked lists instead of doubly linked lists too many cases all right no crash but i can't see why it didn't the fat path might field oh i was in there though okay they look reasonable so maybe i fixed it so now i'm going to restore my mouse look around so i can at least see what's going on okay maybe you fixed all the bugs this is why people tell you never do linked lists by hand because you have linked list bugs and who needs to spend half an hour debugging a linked list bug okay was that i was in midair yeah i was in midair so let's get really far away and do a path find boom and you can see it just went directly very very little spread until it got to here and it had to start going uphill again so it started spreading around looking because it's going uphill even though the target is down and so it starts looking because it doesn't know that there isn't a low route over here somewhere so they're going all the way over here looking maybe there's a route that goes down maybe there's a hole right here it doesn't know that when it gets to here it doesn't know that there's not a gap right through here and that would have saved it so but maybe not enough who knows um all right so this seems to work so now we uh we'll check that in find working and uh i'm going to delete all those asserts although i could want them for the future but they're easy to put back in um just because it blots the code pretty bad i'll leave the function available but so why are you doing like this my hand uh because it's going to be more efficient uh because they're embedded in the data structure whereas if you've tried to use like a stl linked list or something like that it creates its own nodes that it has to allocate and it's super inefficient so since this was a whole point of this was to do an optimization it was necessary to do it by hand but there's true it's a truism like it's a thing that people say and it's generally true don't do linked lists by hand i do them all the time and i still get them wrong um doing them indirectly through the array makes it a lot worse though the code it's less obvious i and having that whole having to have two copies for the primary and the secondary and all that stuff just was added an extra cognitive load that made it more likely that i'd have a bug there all right so we think we have this working i did a really long pass find and it looked pretty reasonable we fixed multiple bugs since yesterday so if it's only a semi-diagonal does it explore the whole region no it doesn't it actually i don't know that i don't think that's optimal it oh no i guess it is yeah okay it is i thought it went up and down but it doesn't it just goes consistently down the whole way it seems like yeah you might prefer it to go more that way some but you can't control that when you just have orthogonal and it basically tries to go all orthogonal and then all straight in this particular one although it's not guaranteed to do that there's nothing in the algorithm to guarantee that so you know the so the we'll come to this in the path following so let's talk a little bit about the path following since that's what i'm about to start um this is perfectly viable paths in a discrete world like if you had a turn-based world and everybody just moves on tiles this is all perfectly reasonable but if these are going to be you know creatures and they're trying to get from here to the point which is like there i think because it's because of the way the path turns um oh it's closer than that yeah it's here then you go on this weird angle that isn't doesn't follow the things and you could try to do that in the path by going you know diagonal to the straight diagonal straight diagonal straight to get sort of the approximate path but it's hard to convince the pathfire to do that and so um there are really two issues with the continuous path following kind of thing one is that you might kind of would like the path to cut across more directly and the other is that when you go to follow the path if the thing is moving um using sort of physics then instead of like going in a like a turn-based thing it would just go from here to here go from here to here and you could approximate that in real time by lurking between them you could say okay i'm currently here my next stop is here lurp over the next quarter second from here to here and then there and you would watch the thing move smoothly but it would turn it on a dime and and smoothly move and turn on a diamond smoothly move turn on a diamond smoothly move etc um which is maybe not how you want it to look maybe you want it to look a little bit more physical like it's moving and one way people do that it's to run physics on the things and then have them sort of steer you know control the same way a player does and if you do that one of the things you run into is that the thing doesn't stay on the path we had this problem at looking glass uh notorious issue and system shock and terra nova that we had these grid based worlds and grid based pathfinding and then the creatures were going through a physics engine and they just would you know they would pathfind along a narrow path like up here on this thing and then when they went to go up it they would go okay here i am i'm going up it and they would go up it and then they would do that um so we have to deal with that kind of issue when we go to do pathfinding so that's an issue with continuous path following from a discrete path and the same thing is actually also true of that diagonal problem is a problem of continuously following a discrete path the whole point is that we actually want a continuous path that isn't even a discrete doesn't even can't be described as this great path because we really just kind of want to go straight at this thing so we've gone look at solving both of those problems is there any other way to do ask someone uh do they mean to do the pathfinding so that you don't have this problem or do they mean uh is there any other way to do pathfinding other than a star or do they mean is there any other way to do pathfinding uh or did any other way to do path following uh path following um there is an approach that works probably better with nav meshes and grids which is instead of thinking of these as points you can think of them as blocks here little square so you can say well there's a little square that i'm allowed to be in here a little square i'm allowed to be in here and you need to do it a little bit differently like you wouldn't allow uh diagonals to do this method so you would have to only allow straight things and what you would do is well there's a square i'm allowed to be in here and a square i'm allowed to be in here and a square i'm allowed to be in here and a square i'm allowed to be in here a square i'm allowed and then you move always only crossing between the edges between the squares so you're like while i'm pathfinding following i'm allowed to go anywhere inside the square and then cross into the square And now I shouldn't ever go back to that square, but I'm allowed to go anywhere in the square. And I just need to cross this edge. And I can optimize where I'm crossing that edge and do that. But you'd end up closely following the path still. You wouldn't ever cut across the diagonal. So you still need something else to solve the cross diagonal. So any other way to do the linked list, what's the question was? You don't use third party libraries, I think. If you want the linked list embedded in the data structure for performance, it's difficult. People do make libraries that support that. But here I'm using 16-bit integers instead of pointers. And probably the libraries would use pointers and stuff. So there's just no way I was going to do it for this. And I'm using 16-bit integers because that uses less memory. And using less memory means more things to cache, which means they're actually faster. Now, of course, it has to turn those 16-bit indices into pointers while it's processing things. But that's just a shift. It's a small shift. It's 8 bytes, or four bytes. One of the data structures is 8 bytes and one is 4 bytes. And the x86 actually has an address calculation that multiplies by 8 or multiplies by 4 embedded in it. And on the old machines, that was actually a single cycle or whatever. On the modern machines, it's multiple microbes, probably possibly still. It might be cracking that and turning that into a, it might not have a custom times four multiplier anymore. It might throw it into the regular multiplier. But probably it does. It probably reuses an adder for that. But I bet the shift is free. But maybe not. Who knows? But anyway, so often that stuff is free, and then you're getting the cache savings. So it's potentially worth it. Depends how big the data structure ever gets. If the data structure is only ever 64 nodes, none of this really matters. The performance is going to be reasonable, kind of, no matter what you do. But since I allow off 5,000 node expansion, then keeping that data structure small definitely helps. The link list is of what object? Of the pathfinding nodes, the nodes that have been explored in the pathfinding. All right. So I just removed the assertions. So let's check that in. And we have been going for 243. So I'm not even going to take a break. I'm just going to take a minute to get a drink. I'll be right back. Will this pathfind work going through a tunnel on the map? Yes, it will. When I was doing this yesterday, I had it pathfind a thing that was four units tall and was showing how it could go under this, but couldn't go under this. But, and I actually went down into a pit. But let's go ahead and demonstrate just because it's worth doing. So my pathfind is, let's make it a little easier to get down here, my pathfinder is pathfinding for a thing that is two units tall. So let's, I don't even think I've ever made a tunnel that goes all the way through, just because it's nice to see that kind of stuff. That's why I did a demo of going down into the pit. The other day was just to show that kind of stuff. All right, so, all right, so if we're here and we pathfind here to, where's this pit? So if we pathfind from there to here, it might go through. It might not, I don't know, or it didn't pathfind at all. Was this one not, was it, was I in midair when I did this? I was in midair when I did this, okay. So now, it path found through the tunnel. There you go. And then let's get it to pathfind not through the tunnel, say to here, it should, I think, go around, yeah. So here it didn't go through the tunnel. It just went over the land, right? There you go, woohoo. Then we go back to this side, say to here. Oh, same thing, I'm standing over the edge. So pathfind fails. Get back, get down. There's no path. It explored all the way and then didn't do our path. That's very odd. How does that happen? It didn't actually go to the right place. It's some kind of bug, it's actually, the height is wrong here, if I go down. Yeah, it's under the train. So there's some kind of stale data bug again. Yeah, these lights are wrong. Yep, it's just shifted off. It's an old pathfind that's shifted by a little bit. When having multiple AIs, do they share the pathfinding info? It's what we're about to do, except I just found this bug. What they'll do is they'll call this to generate the path and then they'll copy the path out and keep their own private path solution. That's really sad that I found a bug. Okay, how do you get stale data? I memset that whole thing to zero. There are no globals anymore. How do you get stale data? Well, I don't set the array to zero. So if you somehow got, no, I do set the array because the array is inside the data structure and I memset the whole data structure to zero. So how could you get stale data? Okay, I'm going to just have to stop the low, low action of tracing the path of the player between two possible positions. Yeah, this is, I'm not gonna answer that. That's getting too much restating what's already been done. So, and I need to focus on this bug. Okay, so that's pretty clearly what happened. I don't need to look at the data. I don't need to break point there. So memset PC zero size of star PC. That should be all of PC data. So how could you get stale data? But if someone else wanted to explain that to try to answer those questions, they should do so. How could you get stale data here? It's memsetting. It is memsetting. Every call memsets the whole structure to zero. So how could you get stale data? The data we're displaying is this. We're just storing a pointer to the data inside the structure. That's what we're displaying. But all that data got overwritten. How did the pathline fail is also a question, but or I could just move on and have a lurking bug that seems kind of bad. We memset the whole thing. How does that happen? It doesn't make any sense. I guess I should see if I can reproduce a different example of it. See if I can get more data about what kind of thing is going wrong. Don't know what I did to do it. I was making that tunnel, but that tunnel had anything to do with it. It was a very long path, well, relatively long path, so I can do that to try to replicate. And it was weird because it showed all the nodes but didn't show a path. So maybe if the path failed, but the nodes were bad, how would the path failing? And I don't know why the path failed either. Well, you gotta replicate. If you don't replicate, I can't fix the bug. So I totally have to dig another tunnel to make it happen. No, the paths fail in mid-air. I test that at the very beginning of the function if the point is in mid-air, if either end point is in mid-air. So it won't ever explore anything. Oh, won't ever explore anything. And it's before I memset. That's why. So that would result in still data. Although the still data should have still been relative to the, maybe not. I'm not sure why I didn't always see that, but that's possible. Debug does plus slash plus. Yeah, okay, that was why. You're right. Okay, so nothing to worry about. So we can move on to pathfiling. All right, good. Okay, so to do pathing, we need some creatures in the world or some things. Well, we need some blocks that do things. And we have some blocks. We have the test and the balance. But let's go ahead and make an O-type critter just for the heck of it. And we just have to go to world and here I have an O-type critter shape. And let's make it the same size as the player. And we need to create them, which is in UI where we throw things and code T, the row thing is even its name. And we do this and let's change it to create a critter and then let us change this to move it, take its current position as velocity, moving by a second, store that to its position, zero its velocity. Okay, so it'll jump out in the direction we're facing. It'll be in midair. It doesn't have any physics. So let's see, or maybe it's getting by default the inanimate physics, I don't know. Okay, yeah, it's getting the inanimate physics. Okay, so we can create critters. They don't run into each other. They don't, you can't see them very well. We should probably fix that. Okay, so then in world where we do the physics, we're going to want to do them a different kind of physics. Object physics, object physics, so that's for type on ground bouncy. So that says inanimate. So we're going to do a switch of type, case type test inanimate physics. Case of type critter is going to do AI tick, and then, and I don't know what that physics is going to do yet. Okay, AI, here it is. What do we do for our AI? Well, if we're not on the ground, we flail helplessly and do nothing. So now if we're on the ground, what do we do? What we want to do is we want to have an AI pointer or index, but we'll make it a pointer. And we're going to look at that. So brain B. So if B has target, then that means we're pathfinding, we're pathfollowing, rather. And we do stuff with that. So now let's declare that type. So where's our object? Here's our object. Type def, brain, and we can't have a brain. Yeah, we can't call them both brain. Okay, so brain state. What goes in our brain state? Well, we have a, what was it called? Has target. And we have the target if we'd have a target. And then we have a path. Let's say we'll have 64, find max brains. So how many critters can we have in the world? Say 4,000. Then that's going to be 12 bytes times 64 nodes 64 nodes times 4,000, which is ballpark two to the four times two to the six times two to the 12, which is two to the 22, which is about four megabytes. So we'd be storing four megabytes of path data. Most of it is never used. That's why I wanted to see how much it is. Most it won't ever be in use. That's probably okay. Ant path position. Ant, if we have a target, we look at our position, let's say b arrow path minus one. If b arrow path position equals b arrow path length minus one. We're in the last node of our path. Then we wanna check if we're at the destination. So we check if we're at the destination. So if we are, then we got to our destination. So we set has target to false and we return. And we set our velocity to zero. If we're earlier in the path, which is always true, then we check, look at the current node that we're at and we see if that we currently are. And if it is, we don't have to do anything because we're currently processing it. If it's not, it's changed. This is the crucial one. Let's see, how is this gonna work? When we, oh, we still have to do this case, sorry. This is not gonna work. This is going to happen after we get a new path in the current one. If the dot x does not equal zero or z does not equal zero, then we don't have to do anything because we're already moving. If we're stopped, that means there's something that I'm not gonna explain. We need to get moving to the next node. So if our position is not to that, then we have to advance it, plus it must be in our path position. And if we advance it and it becomes that, that case actually happens here. If we advanced it and we got to the end of the path, then we do this stuff. You could store path as relative from the start pause then keep a much smaller footprint. Yeah, I mean, it would be half the footprint. And then I'd have to pepper that relativization into all this code everywhere. So not sure it's worth it because the cache properties aren't the same because this is gonna be accessed really infrequently, sparsely. You'll only ever access one or two path nodes at a time. And you're only accessing it once per frame as opposed to the path finder where it's iterating over all of them constantly. So I'm not sure it actually would help. It would save memory, but I don't think it would help performance and it would be more complicated code, more buggy. So I think I'm gonna stick with it being the way it is. If I were wrong about that, then yeah, it would make sense to use the exact same kind of relative coordinates that are already house. And it could even just return the relative coordinates without converting them back to global coordinates. Is that vegetable juice? Yeah, it's V8. So the idea is that there's a fixed length path in the brain and when you find a path and it's like 200 long and the brain only can store 64, you just store the first 64 and when you get to the end of the 64, you'll refine the path. It's not perfect, but it avoids memory management from storing variable eight length paths. And often if you're pathing like to another creature or something, you'll want to refine the path periodically anyway, so it actually doesn't hurt to do that, which is why I went with that design in the first place. Okay, so you get to here and these are like, okay, I need to move to the next thing. So if the arrow path position plus one is outside of the array, so we have a problem. But we already handled one of those cases here. So I don't think that case happens, but so I want the inverse of that, which is that. But we'll just do nothing. If we hit that case, if you can't look at the next node, then we'll do this. So, oh wait, we already incremented here. Right, yeah, that's right. Okay, so we want to know what our next target is to make three I. Next is the arrow path of the arrow path position plus one and we want a delta vector between them. So we take next x, the center point of next x plus or minus our current position and we don't go to the center of this one. We go to size or type type. Of zero of two, that's a negative number. So we want to subtract it and add a little epsilon, just in case, and then these need to be x, y, z. All right, so that's the direction we want to go. So then we say vector norm, are those in main? I think, because I haven't split them out yet. Yep, okay, vector norm, then we can set our velocity to delta x times critter speed. So that should get us moving in that direction, assuming physics will make us move along our velocity. So, all right, so now let's critter speed and move pretty slow. So we need to define the physics. Physics move animate, move animate, go to physics, animate, make physics move animate. And what this is gonna do is, if not on ground, return physics, move animate. So it will fall, when it's not on the ground, it will fall. So that starting, I can launch them in the air and they'll fall to the ground because the pathfinding only works if they're on the ground, so I need to make sure they're on the ground. Else, we just move them, pause equals back, add, scale, pause, velocity, dt. And we always say they're on the ground. So when they move, if they jump down, they're just gonna slide down at this fixed speed. They're not gonna go through physics because we talked about how making them go through physics is messy, but we'll maybe switch that over at some point. Okay, so that's how they move. So then we just need to initialize their pathfind here, which was in, I guess it's in World. AI tick, yeah, okay, here we are. So AI pathfind here and then we're gonna have the UI be able to pathfind, trigger the pathfind. So let's put that into the header until I actually pass it in, Vic3i, target. When we research, we just use our existing target. And then what do we do? We do pathfind, we need a path behavior. Let's go to UI to get the existing path behavior code and copy that out. And this can all be critter dependent eventually. And we do need a Vic3i start. So start.x equals oh, oh, it's this code right here. So pathfind takes the path behavior, start the destination and a max path. So Vic3i, you know, 10 or make it a thousand. I don't think, I guess if you made a zigzaggy tunnel, it could be a thousand because it's not allowed to go very far from the origin anyway. So pathfind into full path, max path, land. That returns the length. If land equals zero, then we say brain arrow as target is false. And we record the target anyway just in case. Maybe we need to refine the path at some point when it becomes available. And if it is this, then we say, oh, arrow brain arrow path length equals. And then how did I define that? That was called max path. So this is max short path is sdb smaller of min and min and short path line, max short path, a smaller of those two, right? Yes. And then copy, oh, full path times size of, so we copy our path out and then we say position equals zero. So what else do we have in here? We have has target, path, we just updated path length reset, path position reset, path target reset. So we just have to say has target equals true. Okay, is there anything else? Now, once I run a single thing, if I make all of them go to the same target, then once they're all the same target and I try to send them somewhere else, they'll just go to the same thing. So in terms of giving them targets, I need to actually give them different targets. So I'm not sure how to do that. I guess I'll, I can sort of scatter and gather them. Okay, so do I have anything else I have to write? For that.h is there. Of startitian with a renamed pause. All right, that's compiling now. So now I just need to trigger it. Let's run it first before I even try to trigger it and just make sure that they fall to the ground. Oh, they don't have brains yet. I need to give them brains. Now I need to give my AI some brains. So the whole point of that, where is our object array? What we create objects if type equals o type critter, then we need to allocate them a brain. So the whole point is that these brains, max brains, brain state, 4,000. So where's the allocate player stuff? Cause I should put it there. It's an object. So let's put this in object brain. Okay, now we have a brain allocator. So now we get allocator brains back in world brain equals brain. Do I not have object destruction yet? I think I don't have object destruction yet. So I don't have to deallocate the brain because I don't have any way to deallocate objects. And that's not reporting it, turning a pointer. Right, I need to change that to return a pointer. Cause I'm not using IDs for the brains. I'm just using pointers for the brains because I decided to be lazy and not size efficient on that. So that fixes that. And then the object, that's a brain is not public brain data. Brain data, isn't it called brain data? It's in, this is funcs and funcs includes data and data includes brain data, right? Or did I not put it? Oh, and it's brain state, brain data. Dun, dun, dun, dun, dun, dun, dun, dun, dun, dun, dun. Okay, what did I forget to do? I don't know what I forgot to do. So we throw a guy, hits the ground, turns into a brain. Okay, when we render those, where do we render those? I think it's in main, render objects. If O arrow type equals O type critter. Let's give them a different color. All right, so there we go. And they're being drawn three units high, I'd say. So let's make sure they're, let's make sure the path find request says that they're three tall. And we just need to call it. Okay, now path find. Okay, so go to UI, trigger path find, I equal, how do I loop over objects again? Okay, I'll pick that. Type equals O type critter. Um, excuse me, then we want to call it the path find. I have path find dot O, target spread. Then target equals, let's do that differently. Target equals, um, of player ID, dot position, dot X. We'll go to the player. And then if spread, target dot X, we need to make sure it's on the ground. It's a pain. Target dot X equals, we're gonna path find and fit. Can't stand, we need can stand. But you do flanks over in UI, so we don't have access to that path behavior. I can stand. So if it can stand there, then we're done. If it can't stand, we try a higher Z. And I should search up and down from the current Z, it'd be faster, but this is good enough. Okay, so if we ask for non-spread, they all go to the player. If we ask for spread, they pick a random distance around the player and find a Z value that works. And then they path find to it. Sounds good. I need to implement and I can stand, and then you call trigger path find. Okay, so we'll use this and I have to write that function. AI can stand. It's just gonna be a world. That's where the AI is currently. We'll move it into an AI function at some point. We're in AI init, behavior, let's not call it set behavior. Path behavior, pb, object, astro, pb.becomes pbero, call AI set behavior. Now we can make the function full AI can stand and it's going to path behavior. pb, behavior, path behavior. Zero offset from, looks like we're going to pass it in. Target, target. Okay, I'm sure I've forgotten something, but we'll try it and see what happens. So we throw a couple of guys up there and have them path find and nothing happened. All right, so let's break point on AI path find. See if that seems to be called. Okay, it has been called. Tried to do a path find. Len was zero, okay? So let's start creating it 79 and target. It's where the player is, 2375. Let's step in. Okay, it claims it can't stand at the start point. So it fell down to the ground and now it's claiming it can't stand in the position where it fell down to. And it's supposed to have a little epsilon to make sure it doesn't go under the ground. So does it have an epsilon? 2509. So it's actually, it doesn't need the epsilon because it's actually above the ground because I'm not accounting for where it's bottom is. But that should be correct. It's at 79.2509. So it's 79 should be standable because 78 should have a block and 79 should not have a block because it fell to the ground. So let's step in there again. Let's step in here. See what happens. We're entering over the whole size. All three things fit. Then we're checking for ground. So this is the crucial one. It's looking at 78. Claims that's 81.92. 14, 16, 384. So it's claiming it's one too high to touch the ground. We go over and look at it. It depends where its center point is. It's possible that its center point, no, it looks pretty, this one clearly, this one clearly should be valid. Now it's not valid, but I just wanted to check. So what's, how would this not be valid? Why can't I step in this? Oh, cause it's hard running. Okay, try to see if it can stand. Nope, okay. Did not get any ground as not flying. I could turn on one flying. I could just make them be flying. And then it would just let us focus on this and not worry about this other problem. So the pathfinding would be all, that's right. Oh, the pathfinding would be all. Right, the pathfinding has weird other codes for flying, but, and then the other one, let me get here. It's trying 76, it's 12, it's 4,096, it's off by two. Right, that'll be 4,096, and it only extends up to 1024 is the highest one. So it's got a gap of two. That doesn't make any sense. Is the start point wrong? How are we computing the start point? Start.zee, they look consistent. How is that happening? How is it that the pathfinding claim that there isn't a place to stand? They are, the measuring from the bottom or they are that's top. It's measuring from the bottom. It's exactly the same as the player. I copied the player shape, so it's ground based. I'm just using the exact same code I used for the player that I did. Like if I go look at the UI, if you look at the old testing code that I was doing, which is player based, test path, let's see what did it do. It, and didn't actually floor this, but it did offset by this, but this is negative 0.25. So when you floor that, it's not gonna actually make any difference. I could copy it in, but it's not gonna be the issue because it is only negative 0.25 and I'm looking at it right here. That'll bring that down from 77.2502 down to 77.0002. And then when you round it, it'll still be 77. The question is why the ground there is at 74 or 75 or whatever. I mean, and visually you can see it's right. So I could look at the physics and try to see what the physics is doing, but that doesn't make a sense because you can see it's right. Yeah, I always measure from the feet, basically. I'm not sure why I added that extra 0.25 at the bottom, but it's just a standard thing in 2D and 3D. The gravity axis measure from the feet and the other axis measure from the center just makes everything in your life easier. You know, I mean, I'll copy this over, but it ain't gonna matter. All right, start point is in the world. I'm sure this isn't gonna matter. Well, that one's now able to go. It's going in the wrong direction. Don't know what it's doing. Like I just set the velocity in the wrong direction. Next, from the current. Center of the next. Subtract that to raise it up in the air properly. Minus the current position. Sure seems right to me. Now there is an issue if whenever you do that, there is a case that it's moved into something when we never move straight up and down. So let's not test that. Just we'll simplify things a little bit. It's, the question is whether we've advanced path nodes here, but we don't check that we're actually in the node that we just advanced to. I'm just assuming this other code will do that. If it's not in the thing that it path to, then the next time through, it'll check that again and it'll still not match and it'll advance to the next node. So it'll just wind through all the nodes. So there is this issue that when you move from one to the next, it really should be checking that you reached the one you were trying to get to, not checked that you got out of the one that you're currently in, but that's not going to fix. And we can only do that, be our path position, plus one is less than the be our path length, which is of course the same test we have down here. If that is the case, we can test it. If that's not the case, for example, we're moving into the very last node. Then the question is, what's the correct trigger for this then now? So we advanced the position. Now it should be the one that we're in because we just checked that we weren't in it right here. So we weren't in it or our velocity was zero. So if our velocity was zero, we don't want to advance it. We want to recompute the thing down here. If the position is, is that, that should always trigger this case actually. We should never have that case. So sort, that's the same as moving the minus one to this side. And if it's not less, it would be because it's equal and that's the test there. So that should be. Seem to go in the opposite direction, is there an issue with the velocity? So that's the very first thing I checked. I walked through this and made sure I was looking at the right values. But they, they seem signed correct. They don't seem like the wrong sign. They seem like the correct sign. So where you're trying to get minus where you currently are. So it might be instead that the path wound differently. It could be that the path find was totally wrong. Let's run it with only one thing so that we can see the visualization of the path find. Oh, here it comes. And there he goes. So he will like jump straight to the end of the path it looked like. Oh, and then that assert field that I just added. It looked like he went to the wrong path node because he moved it directly to the thing. He didn't go through the intervening steps. He just moved directly to the end point. As if, am I not resetting the error path position? No, I set to zero. And then how do you get to this case? The error path position 14. So it's one less. So somewhere we increment it. When we increment it, if it's one length less, we either turn off HasTarget and return or we trigger a new path find. But the new path find could fail, in which case it says HasTarget to false, but we don't return. Okay, so I think that's how you hit the thing. It shouldn't be doing that at this point, but that would make it hit that spot. So why did it jump all the way to the end? I think we just have to break point in here and see what's going on. All right, so it's not in the same thing as the curve. It's not in the target, but it's not in the target. It is not in the next step, but it's velocity is zero as velocity is not to return, but it's velocity is zero. So we step down here, which makes us go to here. Which makes us look at the next spot, pick a delta, it goes in the right direction. We know it goes in the right direction at first. We don't increment path position because we haven't gotten there yet. Okay, but it picked a weird direction. I need to step through that and look at what it was. The arrow path here, path position. Oh, the path comes in backwards. That's why. I made the path come in backwards so I didn't have to deal with reversing it in the other place. So I need to make the path get reversed here. Boom, boom, boom, boom, boom. Here, we copy it out. Okay, did a weird zigzag, but didn't seem right, but and then it didn't stop when it got to the end. Did I put in stop when it gets to the end? When it increments, if it what it incremented to was the end, then it checks whether that's correct. Oh, it dived into the ground there. That just ain't right. Why is it in underground like that? Because it does need to be up. Wait, I want it to be up extra, not down. Okay. I want it to hover a little bit just to keep things simple. It's still down. Delta Z, path position plus 0.5. What is the thing I'm subtracting? Oh, it is from the top. You're right. Wow. Why is it from the top? Oh, because it's from the eye position. I always do it from the feet, but for this thing, because this all was hacked around from a camera, it's all relative to the camera, which I should fix. Okay, so now it's hovering. Now let's get rid of the hover. So where was the hover? Where was this? All right, and it stopped when it got to the end point. All right, here we go. All converge on it. Let's do L. Let's have them spread. See if that works. I guess so. I'd be welcome to be again. Oh wait, that's still going. Poor guy. Okay, I'm gonna have them all come to me. Like I didn't work. Like I didn't work, nobody worked. Nobody was able to come to me. I mean, I was in midair. Okay, spread it out. And I'll get up on here and let's watch them come up here. I can't get to here. I'm standing on a, is it because my head height thing doesn't work? No, because I put in the fixed code. Why can't they find me now? And why don't I see the debugging? Shouldn't I still be seeing the pathline debugging? I haven't seen that. I should show the most recent pathline, shouldn't it? Where'd the third guy go? Third guy has disappeared. Is he under the ground somewhere? Where'd he go? He's lost. He's way over there. He moved so far he fell out of the pathfinding data and he fell out of the world. Yeah, because there was no physics so he fell or something. Where he's still going? I think he's still going. Yeah, he had some kind of bug and just kept going. All right. Many of them are coming. Most of them. One got stuck. Oh, because he's stuck off the edge of the thing so he can't go. He's not allowed to pathfind from there. Gotta do something about that. These guys are all converging on that spot. Good. Let's spread them from here. Welcome here. And this guy got himself stuck, which makes sense because I'm playing fast and loose with the continuous versus discrete, they're allowed to go through the terrain and then they get stuck and it's all fine. It's expected. All right, so it's mostly working. Now a bunch of them come over here. Let's get up on this. Oops. And let's spread them. And then from where they are currently, tell them to come to me. That didn't seem to override their pathfinds. When I triggered a new one, it seems to not be updating. That's interesting. I think it breaks it. If I run a pathfind while they're already moving, I think it breaks it. Which could be what happened to the first guy who got lost. So when we trigger a pathfind, let's set their velocity to zero because that's what we're checking to decide whether to update it. So what was happening was it was seeing that the path was already non-zero, the velocity was already non-zero and not changing it. It was assuming it was already on the correct trajectory, but it's not when you re-trigger it. It needs to stop and restart. So I think that works. Modulo, the fact that they go through the train and get stuck and all that stuff. So cleaning that up will be the next thing to do. So let's see if we can get them to go up on that thing again. Okay, here they come. All right, and now let's have them spread out. Then let's have them come back up here. Now they should come up by different routes. Oh, by some of them got stuck. How did you get up there? Oh, he just got stuck, he's fine. So sometimes they get too far from their current thing. I don't know how that happens, but we can check for that. So they aren't there, and they are moving. And what we wanna do is check if they're too far from the path. If policy.x, if apps policy.x is greater than four or apps policy.x is greater than one probably. Yeah, we'll just do greater than one. Then, so even though I did this really trivial path while they still somehow managed to get off the path. Go to refine. And they might not be on the ground, so they might just stop dead when this happens. So we should just always set the velocity as there at this point. In fact, that might already be what went wrong here. And then let's make the spread smaller so they don't get so far away from me. And let's run a whole bunch. I always forget that when I do this. So let's see if we can get them to get up on here. They don't want a path find here. Oh, some of them do. Oh, a lot of them are stuck. All right. Oh, no. So some of them could path find elsewhere, but couldn't make that path find work. That's very odd. So there you go. You also kind of create variables with nothings and then need a second one perhaps as a temp and you get nothings too. No. All right, so I think that's good enough. I think I'm gonna stop there for our stream. So the obvious thing you could do here at this point though is just have them idle and randomly pick a place a couple squares away and move there and idle and do that. And that would give you something like Minecraft Sheep. And then you can have them path find to the player. Let's do that. Let's have them automatically repath find to the player every couple ticks. Oh, that gets stuck. I need to have them not get stuck before that'll work. But we can try it, but I think they're just gonna get stuck and it won't work that well. But I can just keep hitting O every so often. It's very hard to steer and press O. I need a key that's closer. Yeah, they just converge too much. So there you go. You are sort of friend, you are just in time for me to stop the stream. So questions and then I'll stop the stream. Pushing it as we speak. Oh, the frame rate. Yeah, yeah. So now we wanted to test the, I should test what the performance difference is with the two pathfinders. So what we can do now is can I get enough that it'll even be noticeable? I don't know. Let's switch back to the old one and see if it's noticeable. See if we can see a difference. So or I can see a difference. You may not be able to see a difference. I think there might be some bugs in that though. No, because the bugs would have been fixed. Okay, yeah. So we'll just make a whole bunch of guys. I need them to not get stuck. Just put them all in the same place. That's fine. Oh, I need to know how many I make though. So it's consistent. I think that's 32. Might not be enough. Let's see. So then CPU tick time. I guess that's where that'll show up. Let's go all the way up to here. Let's go onto the top of this hill. This might be too far. You met this pathfinder and I might not be able to find here. Okay, CPU tick. Well, I saw it hesitate. So I think they all failed to pathfind. So yeah, there's a momentary pause here as they explore 5,000 nodes, I guess. You can't see it in the performance numbers, but you can see the pause. 20 frames per second, you might not be able to see the pause as clearly as I can because I'm running a 60. But the Twitch stream is at 20. Now it's not that noticeable. Oh, because they find it. It's only because they didn't find it that it was happening. All right, so now I'll see how slow that pauses. I mean, I can insert profiling code and actually measure it, but it's 32, all right. Yeah, I can do that kind of stuff. I said I talked about doing that earlier. I'm just not doing it currently. I just gotta do one thing at a time here. Whereas it was the top of this hill, I think, we're gonna go all the way up to here, I guess, to get to the top. I don't see any hesitation in the tick now. Literally none. So it's definitely faster. But of course they're not coming to me this time. They might not be, oh, they think they're off the edge. I think they're off the edge so they can't pathfind it all. Yep, yep, I gotta redo it. They're just shortcutting out of that. That's 32. Check that they're actually standing. Okay, good. Down, down, down, down, down, down. All right, now here's a more reasonable test. That's what we'll actually see. What the hesitation is like. Still, I see it jizzing all the frame time, but the mount is near instantaneous. I suppose the other one where I could see a frame rate hitch and here they come. All right, so I'm guessing it went from like 20 milliseconds to one millisecond or something like that. But let's get a little more accurate result now that we can see that we can see it. Okay, more than 32. Why don't I just write some code to do it instead of doing that? Not really consistent. What is that? Throw thing, throw thing. All right, so if you take time now it shows a little bit of something. And they were off the edge. All right, that looks good. I guess I could actually insert profiling code to 1,001, 1,002, 1,002 seconds, kind of. And then we go to the old path find or the new path find, that looks good. All right, here we go. Ready, 1,001, so here we go. 1,001, so it's twice as fast, it looks like. With the very fuzzy reporting here of timings. So about twice as fast. So maybe not as big a deal as I had hoped, but hey. It is going in a straight line, which is, oh no, right, because this is the non-reach case, so it's probably exploring your time. I never did figure out why it's not rendering anymore. Did I want to figure that out? Maybe I want to figure that out. I didn't disable the rendering, right? Debug node alloc reaches into that data structure. The data structure is global. So the last one that ran should show. Why is it not showing them? I don't know, I can't think, I can't think of anything that would change. The last one that you run leaves the debug nodes and debug node alloc, unless it's just always ending up with one that's, no, because if I just test with one, I had one testing with one working and it's not showing any debug. Why is it not showing debug right now? Bug node alloc, 53. Oh, because it's using last pause. It's not got the right coordinate set, so it's drawing them way down here. And then the path itself is in world coordinates, but half length, that one's not getting updated. Okay, so all right, that all makes sense and I'm not going to fix it. All right, so there were no questions, so we're done. I don't remember why it changed. So I'll see you guys next time and gals. So what did I do? Simple critters follow. All right, later.