shithub: sce

Download patch

ref: 05c937e347bbfc1f2758261c15ae0e5a73ada669
parent: a806c098ae69c06691066449ae09fb129b19576a
author: qwx <qwx@sciops.net>
date: Tue Mar 1 18:36:47 EST 2022

decouple a∗, jps-b, movement and pathing state machines, pending rewrite

--- /dev/null
+++ b/a∗.c
@@ -1,0 +1,156 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include "dat.h"
+#include "fns.h"
+
+static Pairheap *queue;
+static Node *nearest;
+
+static void
+clearpath(void)
+{
+	nukequeue(&queue);
+	memset(map, 0, mapwidth * mapheight * sizeof *map);
+	nearest = nil;
+}
+
+static Node *
+a∗nearestfree(Node*, Node*, Node *nearest)
+{
+	return nearest;
+}
+
+void
+a∗backtrack(Mobj *mo, Node *b, Node *a)
+{
+	Path *pp;
+
+	pp = &mo->path;
+	assert(b != a && b->step > 0);
+	pp->dist = b->len;
+	clearvec(&pp->moves);
+	for(; b!=a; b=b->from){
+		assert(b != nil);
+		dprint("%M a∗: backtracking: %#p %P dist %f from %#p\n",
+			mo, b, b->Point, b->h, b->from);
+		pushvec(&pp->moves, &b->Point);
+	}
+	pp->step = (Point *)pp->moves.p + pp->moves.n - 1;
+}
+
+static Node **
+successors(Mobj *mo, Node *n, Node*)
+{
+	static Node *dir[8+1];
+	static dtab[2*(nelem(dir)-1)]={
+		-1,-1, 0,-1, 1,-1,
+		-1,0, 1,0,
+		-1,1, 0,1, 1,1,
+	};
+	int i;
+	Node *s, **p;
+
+	memset(dir, 0, sizeof dir);
+	for(i=0, p=dir; i<nelem(dtab); i+=2){
+		s = n + dtab[i+1] * mapwidth + dtab[i];
+		if(s >= map && s < map + mapwidth * mapheight){
+			s->Point = addpt(n->Point, Pt(dtab[i], dtab[i+1]));
+			if(isblocked(s->Point, mo->o))
+				continue;
+			s->Δg = 1;
+			s->Δlen = dtab[i] != 0 && dtab[i+1] != 0 ? SQRT2 : 1;
+			// UGHHHHh
+			s->x = (s - map) % mapwidth;
+			s->y = (s - map) / mapwidth;
+			*p++ = s;
+		}
+	}
+	return dir;
+}
+
+Node *
+a∗(Mobj *mo, Node *a, Node *b, Node**(*successorsfn)(Mobj*,Node*,Node*))
+{
+	double g, Δg;
+	Node *x, *n, **dp;
+	Pairheap *pn;
+
+	if(a == b){
+		werrstr("a∗: moving in place");
+		return nil;
+	}
+	clearpath();
+	a->Point = mo->Point;
+	b->Point = Pt((b-map)%mapwidth, (b-map)/mapwidth);
+	x = a;
+	a->h = octdist(a->Point, b->Point);
+	pushqueue(a, &queue);
+	while((pn = popqueue(&queue)) != nil){
+		x = pn->n;
+		free(pn);
+		if(x == b)
+			break;
+		x->closed = 1;
+		dp = successorsfn(mo, x, b);
+		for(n=*dp++; n!=nil; n=*dp++){
+			if(n->closed)
+				continue;
+			if(isblocked(n->Point, mo->o))
+				continue;
+			g = x->g + n->Δg;
+			Δg = n->g - g;
+			if(!n->open){
+				n->from = x;
+				n->open = 1;
+				n->step = x->step + 1;
+				n->h = octdist(n->Point, b->Point);
+				n->len = x->len + n->Δlen;
+				n->g = g;
+				pushqueue(n, &queue);
+			}else if(Δg > 0){
+				n->from = x;
+				n->step = x->step + 1;
+				n->len = x->len + n->Δlen;
+				n->g -= Δg;
+				decreasekey(n->p, Δg, &queue);
+				assert(n->g >= 0);
+			}
+			if(nearest == nil || n->h < nearest->h){
+				nearest = n;
+				dprint("%M a∗: nearest node now %#p %P dist %f\n",
+					mo, n, n->Point, n->h);
+			}
+		}
+	}
+	return x;
+}
+
+Node *
+a∗findpath(Mobj *mo, Node *a, Node *b)
+{
+	Node *n, *m;
+
+	if(a∗(mo, a, b, jpsbsuccessors) == b){
+		dprint("%M a∗path: successfully found %#p at %P dist %f\n", mo, b, b->Point, b->h);
+		return b;
+	}
+	dprint("%M a∗findpath: goal unreachable\n", mo);
+	n = nearest;
+	if(n == a || n == nil){
+		werrstr("a∗findpath: can't move");
+		return nil;
+	}
+	dprint("%M findpath: nearest jump is %#p %P dist %f\n", mo, n, n->Point, n->h);
+	m = jpsbnearestnonjump(mo, n, b);
+	if(nearest == nil || nearest == n){
+		dprint("%M a∗findpath: failed to find nearer non-jump point\n", mo);
+		nearest = n;
+	}
+	if(m == b){
+		dprint("%M a∗findpath: jps pathfinding failed but plain a∗ found the goal\n", mo);
+		nearest = b;
+	}
+	m = a∗(mo, a, nearest, successors);
+	return m == b ? b : nearest;
+}
--- a/bmap.c
+++ b/bmap.c
@@ -15,6 +15,36 @@
 static uchar ffstab[256];
 
 int
+isblocked(Point p, Obj *o)
+{
+	u64int *row;
+
+	if(o->f & Fair)
+		return 0;
+	row = bload(p, Pt(o->w, o->h), ZP, 0, 0);
+	return (*row & 1ULL << 63) != 0;
+}
+
+void
+markmobj(Mobj *mo, int set)
+{
+	Point sz;
+
+	if(mo->o->f & Fair)
+		return;
+	sz = Pt(mo->o->w, mo->o->h);
+/*
+	if((mo->sub.x & Submask) != 0 && mo->x != ((mo->sub.x>>Pixelshift) + 1) / Nodesz)
+		sz.x++;
+	if((mo->sub.y & Submask) != 0 && mo->y != ((mo->sub.y>>Pixelshift) + 1) / Nodesz)
+		sz.y++;
+*/
+	sz.x += (mo->sub.x & Submask) != 0 && mo->x != mo->sub.x + (1<<Pixelshift) >> Subshift;
+	sz.y += (mo->sub.y & Submask) != 0 && mo->y != mo->sub.y + (1<<Pixelshift) >> Subshift;
+	bset(mo->Point, sz, set);
+}
+
+int
 lsb(uvlong v)
 {
 	int c;
--- a/dat.h
+++ b/dat.h
@@ -38,6 +38,8 @@
 	Pixelshift = 16 - 3,
 };
 
+#define SQRT2 1.4142135623730951
+
 struct Vector{
 	void *p;
 	ulong n;
--- a/drw.c
+++ b/drw.c
@@ -494,7 +494,7 @@
 {
 	Drawlist *dl;
 
-	if(initdraw(nil, nil, "path") < 0)
+	if(initdraw(nil, nil, progname) < 0)
 		sysfatal("initdraw: %r");
 	newvec(&vis, 32, sizeof(Mobj*));
 	for(dl=drawlist; dl<drawlist+DLend; dl++){
--- a/fns.h
+++ b/fns.h
@@ -53,6 +53,11 @@
 int	isblocked(Point, Obj*);
 void	markmobj(Mobj*, int);
 int	isnextto(Mobj*, Mobj*);
+Node**	jpsbsuccessors(Mobj*, Node*, Node*);
+Node*	jpsbnearestnonjump(Mobj*, Node*, Node*);
+void	a∗backtrack(Mobj*, Node*, Node*);
+Node*	a∗(Mobj*, Node*, Node*, Node**(*)(Mobj*,Node*,Node*));
+Node*	a∗findpath(Mobj*, Node*, Node*);
 int	findpath(Mobj*, Point);
 void	drawnodemap(Rectangle, Mobj*);
 Mobj*	mapspawn(Obj*, Point);
--- /dev/null
+++ b/jpsb.c
@@ -1,0 +1,312 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include "dat.h"
+#include "fns.h"
+
+/* jump point search with block-based symmetry breaking (JPS(B): 2014, harabor and
+ * grastien), using pairing heaps for priority queues and a bitmap representing the
+ * entire map.
+ * no preprocessing since we'd have to repair the database each time anything moves,
+ * which is a pain.
+ * no pruning of intermediate nodes (JPS(B+P)) as of yet, until other options are
+ * assessed.
+ * the pruning rules adhere to (2012, harabor and grastien) to disallow corner cutting
+ * in diagonal movement, and movement code elsewhere reflects that.
+ * if there is no path to the target, the unit still has to move to the nearest
+ * accessible node.  if there is such a node, we first attempt to find a nearer
+ * non-jump point in a cardinal direction, and if successful, the point is added at
+ * the end of the path.  unlike plain a∗, we cannot rely on the path backtracked from
+ * the nearest node, since it is no longer guaranteed to be optimal, and will in fact
+ * go all over the place.  unless jump points can be connected to all other visible
+ * jump points so as to perform a search on this reduced graph without rediscovering
+ * the map, we're forced to re-do pathfinding to this nearest node.  the search should
+ * be much quicker since this new node is accessible.
+ * pathfinding is not limited to an area, so entire map may be scanned, which is too
+ * slow.  simple approaches don't seem to work well, it would perhaps be better to
+ * only consider a sub-grid of the map, but the data structures currently used do not
+ * allow it.  since the pathfinding algorithm will probably change, the current
+ * implementation disregards the issue.
+ * pathfinding is limited by number of moves (the cost function).  this prevents the
+ * search to look at the entire map, but also means potentially non-optimal paths and
+ * more pathfinding when crossing the boundaries.
+ * since units are bigger than the pathfinding grid, the grid is "compressed" when
+ * scanned by using a sliding window the size of the unit, so the rest of the algorithm
+ * still operates on 3x3 neighbor grids, with each bit checking as many nodes as needed
+ * for impassibility.  such an approach has apparently not been discussed in regards
+ * to JPS(B), possibly since JPS(B) is a particular optimization of the original
+ * algorithm and this snag may rarely be hit in practice.
+ * map dimensions are assumed to be multiples of 16 tiles.
+ * the code is currently horrendously ugly, though short, and ultimately wrong.
+ * movement should occur at any angle (rather than in 8 directions) and unit sizes
+ * do not have a common denominator higher than 1 pixel. */
+
+enum{
+	θ∅ = 0,
+	θN,
+	θE,
+	θS,
+	θW,
+	θNE,
+	θSE,
+	θSW,
+	θNW,
+};
+
+/* FIXME: horrendous. use fucking tables you moron */
+static Node *
+jumpeast(int x, int y, int w, int h, Node *b, int *ofs, int left, int rot)
+{
+	int nbits, steps, stop, end, *u, *v, ss, Δu, Δug, Δug2, Δvg;
+	u64int bs, *row;
+	Node *n;
+
+	if(rot){
+		u = &y;
+		v = &x;
+		Δug = b->y - y;
+		Δvg = b->x - x;
+	}else{
+		u = &x;
+		v = &y;
+		Δug = b->x - x;
+		Δvg = b->y - y;
+	}
+	steps = 0;
+	nbits = 64 - w + 1;
+	ss = left ? -1 : 1;
+	(*v)--;
+	for(;;){
+		row = bload(Pt(x, y), Pt(w, h), Pt(0, 2), left, rot);
+		bs = row[1];
+		if(left){
+			bs |= row[0] << 1 & ~row[0];
+			bs |= row[2] << 1 & ~row[2];
+		}else{
+			bs |= row[0] >> 1 & ~row[0];
+			bs |= row[2] >> 1 & ~row[2];
+		}
+		if(bs)
+			break;
+		(*u) += ss * nbits;
+		steps += nbits;
+	}
+	if(left){
+		stop = lsb(bs);
+		Δu = stop;
+	}else{
+		stop = msb(bs);
+		Δu = 63 - stop;
+	}
+	end = (row[1] & 1ULL << stop) != 0;
+	(*u) += ss * Δu;
+	(*v)++;
+	steps += Δu;
+	Δug2 = rot ? b->y - y : b->x - x;
+	if(ofs != nil)
+		*ofs = steps;
+	if(end && Δug2 == 0)
+		return nil;
+	if(Δvg == 0 && (Δug == 0 || (Δug < 0) ^ (Δug2 < 0))){
+		b->Δg = steps - abs(Δug2);
+		b->Δlen = b->Δg;
+		return b;
+	}
+	if(end)
+		return nil;
+	assert(x < mapwidth && y < mapheight);
+	n = map + y * mapwidth + x;
+	n->x = x;
+	n->y = y;
+	n->Δg = steps;
+	n->Δlen = steps;
+	return n;
+}
+
+static Node *
+jumpdiag(int x, int y, int w, int h, Node *b, int dir)
+{
+	int left1, ofs1, left2, ofs2, Δx, Δy, steps;
+	Node *n;
+
+	steps = 0;
+	left1 = left2 = Δx = Δy = 0;
+	switch(dir){
+	case θNE: left1 = 1; left2 = 0; Δx = 1; Δy = -1; break;
+	case θSW: left1 = 0; left2 = 1; Δx = -1; Δy = 1; break;
+	case θNW: left1 = 1; left2 = 1; Δx = -1; Δy = -1; break;
+	case θSE: left1 = 0; left2 = 0; Δx = 1; Δy = 1; break;
+	}
+	for(;;){
+		steps++;
+		x += Δx;
+		y += Δy;
+		if(*bload(Pt(x, y), Pt(w, h), ZP, 0, 0) & 1ULL << 63)
+			return nil;
+		if(jumpeast(x, y, w, h, b, &ofs1, left1, 1) != nil
+		|| jumpeast(x, y, w, h, b, &ofs2, left2, 0) != nil)
+			break;
+		if(ofs1 == 0 || ofs2 == 0)
+			return nil;
+	}
+	assert(x < mapwidth && y < mapheight);
+	n = map + y * mapwidth + x;
+	n->x = x;
+	n->y = y;
+	n->Δg = steps;
+	n->Δlen = steps * SQRT2;
+	return n;
+}
+
+static Node *
+jump(int x, int y, int w, int h, Node *b, int dir)
+{
+	Node *n;
+
+	switch(dir){
+	case θE: n = jumpeast(x, y, w, h, b, nil, 0, 0); break;
+	case θW: n = jumpeast(x, y, w, h, b, nil, 1, 0); break;
+	case θS: n = jumpeast(x, y, w, h, b, nil, 0, 1); break;
+	case θN: n = jumpeast(x, y, w, h, b, nil, 1, 1); break;
+	default: n = jumpdiag(x, y, w, h, b, dir); break;
+	}
+	return n;
+}
+
+/* 2012, harabor and grastien: disabling corner cutting implies that only moves in
+ * a cardinal direction may produce forced neighbors */
+static int
+forced(int n, int dir)
+{
+	int m;
+
+	m = 0;
+	switch(dir){
+	case θN:
+		if((n & (1<<8 | 1<<5)) == 1<<8) m |= 1<<5 | 1<<2;
+		if((n & (1<<6 | 1<<3)) == 1<<6) m |= 1<<3 | 1<<0;
+		break;
+	case θE:
+		if((n & (1<<2 | 1<<1)) == 1<<2) m |= 1<<1 | 1<<0;
+		if((n & (1<<8 | 1<<7)) == 1<<8) m |= 1<<7 | 1<<6;
+		break;
+	case θS:
+		if((n & (1<<2 | 1<<5)) == 1<<2) m |= 1<<5 | 1<<8;
+		if((n & (1<<0 | 1<<3)) == 1<<0) m |= 1<<3 | 1<<6;
+		break;
+	case θW:
+		if((n & (1<<0 | 1<<1)) == 1<<0) m |= 1<<1 | 1<<2;
+		if((n & (1<<6 | 1<<7)) == 1<<6) m |= 1<<7 | 1<<8;
+		break;
+	}
+	return m;
+}
+
+static int
+natural(int n, int dir)
+{
+	int m;
+
+	switch(dir){
+	/* disallow corner coasting on the very first move */
+	default:
+		if((n & (1<<1 | 1<<3)) != 0)
+			n |= 1<<0;
+		if((n & (1<<7 | 1<<3)) != 0)
+			n |= 1<<6;
+		if((n & (1<<7 | 1<<5)) != 0)
+			n |= 1<<8;
+		if((n & (1<<1 | 1<<5)) != 0)
+			n |= 1<<2;
+		return n;
+	case θN: return n | ~(1<<1);
+	case θE: return n | ~(1<<3);
+	case θS: return n | ~(1<<7);
+	case θW: return n | ~(1<<5);
+	case θNE: m = 1<<1 | 1<<3; return (n & m) == 0 ? n | ~(1<<0 | m) : n | 1<<0;
+	case θSE: m = 1<<7 | 1<<3; return (n & m) == 0 ? n | ~(1<<6 | m) : n | 1<<6;
+	case θSW: m = 1<<7 | 1<<5; return (n & m) == 0 ? n | ~(1<<8 | m) : n | 1<<8;
+	case θNW: m = 1<<1 | 1<<5; return (n & m) == 0 ? n | ~(1<<2 | m) : n | 1<<2;
+	}
+}
+
+static int
+prune(int n, int dir)
+{
+	return natural(n, dir) & ~forced(n, dir);
+}
+
+static int
+neighbors(int x, int y, int w, int h)
+{
+	u64int *row;
+
+	row = bload(Pt(x-1,y-1), Pt(w,h), Pt(2,2), 1, 0);
+	return (row[2] & 7) << 6 | (row[1] & 7) << 3 | row[0] & 7;
+}
+
+Node **
+jpsbsuccessors(Mobj *mo, Node *n, Node *goal)
+{
+	static Node *dir[8+1];
+	static dtab[2*(nelem(dir)-1)]={
+		1<<1, θN, 1<<3, θE, 1<<7, θS, 1<<5, θW,
+		1<<0, θNE, 1<<6, θSE, 1<<8, θSW, 1<<2, θNW
+	};
+	int i, ns;
+	Node *s, **p;
+
+	ns = neighbors(n->x, n->y, mo->o->w, mo->o->h);
+	ns = prune(ns, n->dir);
+	memset(dir, 0, sizeof dir);
+	for(i=0, p=dir; i<nelem(dtab); i+=2){
+		if(ns & dtab[i])
+			continue;
+		if((s = jump(n->x, n->y, mo->o->w, mo->o->h, goal, dtab[i+1])) != nil){
+			s->dir = dtab[i+1];
+			*p++ = s;
+		}
+	}
+	return dir;
+}
+
+/* FIXME: clean all this garbage out once map reimplemented */
+static Node *nearest;
+static Node **
+nearestsuccessors(Mobj *mo, Node *n, Node *goal)
+{
+	static Node *dir[8+1];
+	static dtab[2*(nelem(dir)-1)]={
+		-1,-1, 0,-1, 1,-1,
+		-1,0, 1,0,
+		-1,1, 0,1, 1,1,
+	};
+	int i;
+	double d;
+	Node *s, **p;
+
+	d = octdist(nearest->Point, goal->Point);
+	memset(dir, 0, sizeof dir);
+	for(i=0, p=dir; i<nelem(dtab); i+=2){
+		s = n + dtab[i+1] * mapwidth + dtab[i];
+		if(s >= map && s < map + mapwidth * mapheight){
+			s->Point = addpt(n->Point, Pt(dtab[i], dtab[i+1]));
+			if(octdist(s->Point, goal->Point) > d || isblocked(s->Point, mo->o))
+				continue;
+			s->Δg = 1;
+			s->Δlen = dtab[i] != 0 && dtab[i+1] != 0 ? SQRT2 : 1;
+			// UGHHHHh
+			s->x = (s - map) % mapwidth;
+			s->y = (s - map) / mapwidth;
+			*p++ = s;
+		}
+	}
+	return dir;
+}
+
+Node *
+jpsbnearestnonjump(Mobj *mo, Node *nearestjump, Node *goal)
+{
+	nearest = nearestjump;
+	return a∗(mo, nearestjump, goal, nearestsuccessors);
+}
--- a/map.c
+++ b/map.c
@@ -34,6 +34,45 @@
 	markmobj(mo, 1);
 }
 
+int
+isnextto(Mobj *mo, Mobj *tgt)
+{
+	Rectangle r1, r2;
+
+	if(tgt == nil)
+		return 0;
+	r1.min = mo->Point;
+	r1.max = addpt(r1.min, Pt(mo->o->w, mo->o->h));
+	r2.min = tgt->Point;
+	r2.max = addpt(r2.min, Pt(tgt->o->w, tgt->o->h));
+	return rectXrect(insetrect(r1, -1), r2);
+}
+
+Mobj *
+unitat(Point p)
+{
+	Point mp;
+	Rectangle r, mr;
+	Tile *t;
+	Mobjl *ml;
+	Mobj *mo;
+
+	mp = divpt(p, Node2Tile);
+	r = Rpt(subpt(mp, Pt(4, 4)), mp);
+	for(; mp.y>=r.min.y; mp.y--){
+		mp.x = r.max.x;
+		t = tilemap + mp.y * tilemapwidth + mp.x;
+		for(; mp.x>=r.min.x; mp.x--, t--)
+			for(ml=t->ml.l; ml!=&t->ml; ml=ml->l){
+				mo = ml->mo;
+				mr = Rect(mo->x, mo->y, mo->x+mo->o->w, mo->y+mo->o->h);
+				if(ptinrect(p, mr))
+					return mo;
+			}
+	}
+	return nil;
+}
+
 Tile *
 tilepos(Point p)
 {
--- a/mkfile
+++ b/mkfile
@@ -2,10 +2,12 @@
 BIN=$home/bin/$objtype
 TARG=sce
 OFILES=\
+	a∗.$O\
 	bmap.$O\
 	com.$O\
 	drw.$O\
 	fs.$O\
+	jpsb.$O\
 	map.$O\
 	net.$O\
 	path.$O\
--- a/path.c
+++ b/path.c
@@ -4,60 +4,6 @@
 #include "dat.h"
 #include "fns.h"
 
-/* jump point search with block-based symmetry breaking (JPS(B): 2014, harabor and
- * grastien), using pairing heaps for priority queues and a bitmap representing the
- * entire map.
- * no preprocessing since we'd have to repair the database each time anything moves,
- * which is a pain.
- * no pruning of intermediate nodes (JPS(B+P)) as of yet, until other options are
- * assessed.
- * the pruning rules adhere to (2012, harabor and grastien) to disallow corner cutting
- * in diagonal movement, and movement code elsewhere reflects that.
- * if there is no path to the target, the unit still has to move to the nearest
- * accessible node.  if there is such a node, we first attempt to find a nearer
- * non-jump point in a cardinal direction, and if successful, the point is added at
- * the end of the path.  unlike plain a∗, we cannot rely on the path backtracked from
- * the nearest node, since it is no longer guaranteed to be optimal, and will in fact
- * go all over the place.  unless jump points can be connected to all other visible
- * jump points so as to perform a search on this reduced graph without rediscovering
- * the map, we're forced to re-do pathfinding to this nearest node.  the search should
- * be much quicker since this new node is accessible.
- * pathfinding is not limited to an area, so entire map may be scanned, which is too
- * slow.  simple approaches don't seem to work well, it would perhaps be better to
- * only consider a sub-grid of the map, but the data structures currently used do not
- * allow it.  since the pathfinding algorithm will probably change, the current
- * implementation disregards the issue.
- * pathfinding is limited by number of moves (the cost function).  this prevents the
- * search to look at the entire map, but also means potentially non-optimal paths and
- * more pathfinding when crossing the boundaries.
- * since units are bigger than the pathfinding grid, the grid is "compressed" when
- * scanned by using a sliding window the size of the unit, so the rest of the algorithm
- * still operates on 3x3 neighbor grids, with each bit checking as many nodes as needed
- * for impassibility.  such an approach has apparently not been discussed in regards
- * to JPS(B), possibly since JPS(B) is a particular optimization of the original
- * algorithm and this snag may rarely be hit in practice.
- * map dimensions are assumed to be multiples of 16 tiles.
- * the code is currently horrendously ugly, though short, and ultimately wrong.
- * movement should occur at any angle (rather than in 8 directions) and unit sizes
- * do not have a common denominator higher than 1 pixel. */
-
-enum{
-	θ∅ = 0,
-	θN,
-	θE,
-	θS,
-	θW,
-	θNE,
-	θSE,
-	θSW,
-	θNW,
-};
-
-#define SQRT2 1.4142135623730951
-
-static Pairheap *queue;
-static Node *nearest;
-
 void
 drawnodemap(Rectangle r, Mobj *sel)
 {
@@ -96,69 +42,6 @@
 	}
 }
 
-static void
-clearpath(void)
-{
-	nukequeue(&queue);
-	memset(map, 0, mapwidth * mapheight * sizeof *map);
-	nearest = nil;
-}
-
-int
-isblocked(Point p, Obj *o)
-{
-	u64int *row;
-
-	if(o->f & Fair)
-		return 0;
-	row = bload(p, Pt(o->w, o->h), ZP, 0, 0);
-	return (*row & 1ULL << 63) != 0;
-}
-
-Mobj *
-unitat(Point p)
-{
-	Point mp;
-	Rectangle r, mr;
-	Tile *t;
-	Mobjl *ml;
-	Mobj *mo;
-
-	mp = divpt(p, Node2Tile);
-	r = Rpt(subpt(mp, Pt(4, 4)), mp);
-	for(; mp.y>=r.min.y; mp.y--){
-		mp.x = r.max.x;
-		t = tilemap + mp.y * tilemapwidth + mp.x;
-		for(; mp.x>=r.min.x; mp.x--, t--)
-			for(ml=t->ml.l; ml!=&t->ml; ml=ml->l){
-				mo = ml->mo;
-				mr = Rect(mo->x, mo->y, mo->x+mo->o->w, mo->y+mo->o->h);
-				if(ptinrect(p, mr))
-					return mo;
-			}
-	}
-	return nil;
-}
-
-void
-markmobj(Mobj *mo, int set)
-{
-	Point sz;
-
-	if(mo->o->f & Fair)
-		return;
-	sz = Pt(mo->o->w, mo->o->h);
-/*
-	if((mo->sub.x & Submask) != 0 && mo->x != ((mo->sub.x>>Pixelshift) + 1) / Nodesz)
-		sz.x++;
-	if((mo->sub.y & Submask) != 0 && mo->y != ((mo->sub.y>>Pixelshift) + 1) / Nodesz)
-		sz.y++;
-*/
-	sz.x += (mo->sub.x & Submask) != 0 && mo->x != mo->sub.x + (1<<Pixelshift) >> Subshift;
-	sz.y += (mo->sub.y & Submask) != 0 && mo->y != mo->sub.y + (1<<Pixelshift) >> Subshift;
-	bset(mo->Point, sz, set);
-}
-
 double
 eucdist(Point a, Point b)
 {
@@ -179,299 +62,7 @@
 	return 1 * (dx + dy) + min(dx, dy) * (SQRT2 - 2 * 1);
 }
 
-/* FIXME: horrendous. use fucking tables you moron */
-static Node *
-jumpeast(int x, int y, int w, int h, Node *b, int *ofs, int left, int rot)
-{
-	int nbits, steps, stop, end, *u, *v, ss, Δu, Δug, Δug2, Δvg;
-	u64int bs, *row;
-	Node *n;
 
-	if(rot){
-		u = &y;
-		v = &x;
-		Δug = b->y - y;
-		Δvg = b->x - x;
-	}else{
-		u = &x;
-		v = &y;
-		Δug = b->x - x;
-		Δvg = b->y - y;
-	}
-	steps = 0;
-	nbits = 64 - w + 1;
-	ss = left ? -1 : 1;
-	(*v)--;
-	for(;;){
-		row = bload(Pt(x, y), Pt(w, h), Pt(0, 2), left, rot);
-		bs = row[1];
-		if(left){
-			bs |= row[0] << 1 & ~row[0];
-			bs |= row[2] << 1 & ~row[2];
-		}else{
-			bs |= row[0] >> 1 & ~row[0];
-			bs |= row[2] >> 1 & ~row[2];
-		}
-		if(bs)
-			break;
-		(*u) += ss * nbits;
-		steps += nbits;
-	}
-	if(left){
-		stop = lsb(bs);
-		Δu = stop;
-	}else{
-		stop = msb(bs);
-		Δu = 63 - stop;
-	}
-	end = (row[1] & 1ULL << stop) != 0;
-	(*u) += ss * Δu;
-	(*v)++;
-	steps += Δu;
-	Δug2 = rot ? b->y - y : b->x - x;
-	if(ofs != nil)
-		*ofs = steps;
-	if(end && Δug2 == 0)
-		return nil;
-	if(Δvg == 0 && (Δug == 0 || (Δug < 0) ^ (Δug2 < 0))){
-		b->Δg = steps - abs(Δug2);
-		b->Δlen = b->Δg;
-		return b;
-	}
-	if(end)
-		return nil;
-	assert(x < mapwidth && y < mapheight);
-	n = map + y * mapwidth + x;
-	n->x = x;
-	n->y = y;
-	n->Δg = steps;
-	n->Δlen = steps;
-	return n;
-}
-
-static Node *
-jumpdiag(int x, int y, int w, int h, Node *b, int dir)
-{
-	int left1, ofs1, left2, ofs2, Δx, Δy, steps;
-	Node *n;
-
-	steps = 0;
-	left1 = left2 = Δx = Δy = 0;
-	switch(dir){
-	case θNE: left1 = 1; left2 = 0; Δx = 1; Δy = -1; break;
-	case θSW: left1 = 0; left2 = 1; Δx = -1; Δy = 1; break;
-	case θNW: left1 = 1; left2 = 1; Δx = -1; Δy = -1; break;
-	case θSE: left1 = 0; left2 = 0; Δx = 1; Δy = 1; break;
-	}
-	for(;;){
-		steps++;
-		x += Δx;
-		y += Δy;
-		if(*bload(Pt(x, y), Pt(w, h), ZP, 0, 0) & 1ULL << 63)
-			return nil;
-		if(jumpeast(x, y, w, h, b, &ofs1, left1, 1) != nil
-		|| jumpeast(x, y, w, h, b, &ofs2, left2, 0) != nil)
-			break;
-		if(ofs1 == 0 || ofs2 == 0)
-			return nil;
-	}
-	assert(x < mapwidth && y < mapheight);
-	n = map + y * mapwidth + x;
-	n->x = x;
-	n->y = y;
-	n->Δg = steps;
-	n->Δlen = steps * SQRT2;
-	return n;
-}
-
-static Node *
-jump(int x, int y, int w, int h, Node *b, int dir)
-{
-	Node *n;
-
-	switch(dir){
-	case θE: n = jumpeast(x, y, w, h, b, nil, 0, 0); break;
-	case θW: n = jumpeast(x, y, w, h, b, nil, 1, 0); break;
-	case θS: n = jumpeast(x, y, w, h, b, nil, 0, 1); break;
-	case θN: n = jumpeast(x, y, w, h, b, nil, 1, 1); break;
-	default: n = jumpdiag(x, y, w, h, b, dir); break;
-	}
-	return n;
-}
-
-/* 2012, harabor and grastien: disabling corner cutting implies that only moves in
- * a cardinal direction may produce forced neighbors */
-static int
-forced(int n, int dir)
-{
-	int m;
-
-	m = 0;
-	switch(dir){
-	case θN:
-		if((n & (1<<8 | 1<<5)) == 1<<8) m |= 1<<5 | 1<<2;
-		if((n & (1<<6 | 1<<3)) == 1<<6) m |= 1<<3 | 1<<0;
-		break;
-	case θE:
-		if((n & (1<<2 | 1<<1)) == 1<<2) m |= 1<<1 | 1<<0;
-		if((n & (1<<8 | 1<<7)) == 1<<8) m |= 1<<7 | 1<<6;
-		break;
-	case θS:
-		if((n & (1<<2 | 1<<5)) == 1<<2) m |= 1<<5 | 1<<8;
-		if((n & (1<<0 | 1<<3)) == 1<<0) m |= 1<<3 | 1<<6;
-		break;
-	case θW:
-		if((n & (1<<0 | 1<<1)) == 1<<0) m |= 1<<1 | 1<<2;
-		if((n & (1<<6 | 1<<7)) == 1<<6) m |= 1<<7 | 1<<8;
-		break;
-	}
-	return m;
-}
-
-static int
-natural(int n, int dir)
-{
-	int m;
-
-	switch(dir){
-	/* disallow corner coasting on the very first move */
-	default:
-		if((n & (1<<1 | 1<<3)) != 0)
-			n |= 1<<0;
-		if((n & (1<<7 | 1<<3)) != 0)
-			n |= 1<<6;
-		if((n & (1<<7 | 1<<5)) != 0)
-			n |= 1<<8;
-		if((n & (1<<1 | 1<<5)) != 0)
-			n |= 1<<2;
-		return n;
-	case θN: return n | ~(1<<1);
-	case θE: return n | ~(1<<3);
-	case θS: return n | ~(1<<7);
-	case θW: return n | ~(1<<5);
-	case θNE: m = 1<<1 | 1<<3; return (n & m) == 0 ? n | ~(1<<0 | m) : n | 1<<0;
-	case θSE: m = 1<<7 | 1<<3; return (n & m) == 0 ? n | ~(1<<6 | m) : n | 1<<6;
-	case θSW: m = 1<<7 | 1<<5; return (n & m) == 0 ? n | ~(1<<8 | m) : n | 1<<8;
-	case θNW: m = 1<<1 | 1<<5; return (n & m) == 0 ? n | ~(1<<2 | m) : n | 1<<2;
-	}
-}
-
-static int
-prune(int n, int dir)
-{
-	return natural(n, dir) & ~forced(n, dir);
-}
-
-static int
-neighbors(int x, int y, int w, int h)
-{
-	u64int *row;
-
-	row = bload(Pt(x-1,y-1), Pt(w,h), Pt(2,2), 1, 0);
-	return (row[2] & 7) << 6 | (row[1] & 7) << 3 | row[0] & 7;
-}
-
-static Node **
-jpssuccessors(Node *n, Size sz, Node *b)
-{
-	static Node *dir[8+1];
-	static dtab[2*(nelem(dir)-1)]={
-		1<<1, θN, 1<<3, θE, 1<<7, θS, 1<<5, θW,
-		1<<0, θNE, 1<<6, θSE, 1<<8, θSW, 1<<2, θNW
-	};
-	int i, ns;
-	Node *s, **p;
-
-	ns = neighbors(n->x, n->y, sz.w, sz.h);
-	ns = prune(ns, n->dir);
-	memset(dir, 0, sizeof dir);
-	for(i=0, p=dir; i<nelem(dtab); i+=2){
-		if(ns & dtab[i])
-			continue;
-		if((s = jump(n->x, n->y, sz.w, sz.h, b, dtab[i+1])) != nil){
-			s->dir = dtab[i+1];
-			*p++ = s;
-		}
-	}
-	return dir;
-}
-
-static Node **
-successors(Node *n, Size, Node *)
-{
-	static Node *dir[8+1];
-	static dtab[2*(nelem(dir)-1)]={
-		-1,-1, 0,-1, 1,-1,
-		-1,0, 1,0,
-		-1,1, 0,1, 1,1,
-	};
-	int i;
-	Node *s, **p;
-
-	memset(dir, 0, sizeof dir);
-	for(i=0, p=dir; i<nelem(dtab); i+=2){
-		s = n + dtab[i+1] * mapwidth + dtab[i];
-		if(s >= map && s < map + mapwidth * mapheight){
-			s->Point = addpt(n->Point, Pt(dtab[i], dtab[i+1]));
-			s->Δg = 1;
-			s->Δlen = dtab[i] != 0 && dtab[i+1] != 0 ? SQRT2 : 1;
-			*p++ = s;
-		}
-	}
-	return dir;
-}
-
-static Node *
-a∗(Mobj *mo, Node *a, Node *b)
-{
-	double g, Δg;
-	Node *x, *n, **dp;
-	Pairheap *pn;
-
-	if(a == b){
-		werrstr("a∗: moving in place");
-		return nil;
-	}
-	x = a;
-	a->h = octdist(a->Point, b->Point);
-	pushqueue(a, &queue);
-	while((pn = popqueue(&queue)) != nil){
-		x = pn->n;
-		free(pn);
-		if(x == b)
-			break;
-		x->closed = 1;
-		dp = successors(x, mo->o->Size, b);
-		for(n=*dp++; n!=nil; n=*dp++){
-			if(n->closed)
-				continue;
-			if(isblocked(n->Point, mo->o))
-				continue;
-			g = x->g + n->Δg;
-			Δg = n->g - g;
-			if(!n->open){
-				n->from = x;
-				n->open = 1;
-				n->step = x->step + 1;
-				n->h = octdist(n->Point, b->Point);
-				n->len = x->len + n->Δlen;
-				n->g = g;
-				pushqueue(n, &queue);
-			}else if(Δg > 0){
-				n->from = x;
-				n->step = x->step + 1;
-				n->len = x->len + n->Δlen;
-				n->g -= Δg;
-				decreasekey(n->p, Δg, &queue);
-				assert(n->g >= 0);
-			}
-			if(nearest == nil || n->h < nearest->h)
-				nearest = n;
-		}
-	}
-	return x;
-}
-
 static void
 directpath(Mobj *mo, Node *a, Node *g)
 {
@@ -484,70 +75,10 @@
 	pp->step = (Point *)pp->moves.p + pp->moves.n - 1;
 }
 
-static void
-backtrack(Mobj *mo, Node *n, Node *a)
-{
-	Path *pp;
-
-	pp = &mo->path;
-	assert(n != a && n->step > 0);
-	pp->dist = n->len;
-	clearvec(&pp->moves);
-	for(; n!=a; n=n->from)
-		pushvec(&pp->moves, &n->Point);
-	pp->step = (Point *)pp->moves.p + pp->moves.n - 1;
-}
-
-int
-isnextto(Mobj *mo, Mobj *tgt)
-{
-	Rectangle r1, r2;
-
-	if(tgt == nil)
-		return 0;
-	r1.min = mo->Point;
-	r1.max = addpt(r1.min, Pt(mo->o->w, mo->o->h));
-	r2.min = tgt->Point;
-	r2.max = addpt(r2.min, Pt(tgt->o->w, tgt->o->h));
-	return rectXrect(insetrect(r1, -1), r2);
-}
-
+/* FIXME: use center of mobj if any instead of upper left corner
+ * no need for any of this crap: if target is a mobj, set goal to
+ * its center, done */
 /* FIXME: completely broken */
-static Node *
-nearestnonjump(Mobj *mo, Node *n, Node *b)
-{
-	static Point dirtab[] = {
-		{0,-1},
-		{1,0},
-		{0,1},
-		{-1,0},
-	};
-	int i;
-	Point p;
-	Node *m, *min;
-
-	min = n;
-	for(i=0; i<nelem(dirtab); i++){
-		p = addpt(n->Point, dirtab[i]);
-		while(!isblocked(p, mo->o)){
-			m = map + p.y * mapwidth + p.x;
-			m->Point = p;
-			m->h = octdist(m->Point, b->Point);
-			if(min->h < m->h)
-				break;
-			min = m;
-			p = addpt(p, dirtab[i]);
-		}
-	}
-	if(min != n){
-		min->from = n;
-		min->open = 1;
-		min->step = n->step + 1;
-	}
-	return min;
-}
-
-/* FIXME: completely broken */
 void
 setgoal(Mobj *mo, Point *gp, Mobj *block)
 {
@@ -604,6 +135,7 @@
 	*gp = g;
 }
 
+/* FIXME: fmt for Nodes or w/e */
 int
 findpath(Mobj *mo, Point p)
 {
@@ -613,50 +145,24 @@
 		werrstr("not moving to itself");
 		return -1;
 	}
-	clearpath();
 	a = map + mo->y * mapwidth + mo->x;
 	a->Point = mo->Point;
 	b = map + p.y * mapwidth + p.x;
 	b->Point = p;
-	dprint("%M findpath from %P to %P dist %f\n", mo, a->Point, b->Point, octdist(a->Point, b->Point));
+	dprint("%M findpath from %P to %P dist %f\n",
+		mo, a->Point, b->Point, octdist(a->Point, b->Point));
 	if(mo->o->f & Fair){
 		directpath(mo, a, b);
 		return 0;
 	}
 	markmobj(mo, 0);
-	n = a∗(mo, a, b);
-	if(n != b){
-		dprint("%M findpath: goal unreachable\n", mo);
-		if((n = nearest) == a || n == nil || a->h < n->h){
-			werrstr("a∗: can't move");
-			markmobj(mo, 1);
-			return -1;
-		}
-		dprint("%M findpath: nearest is %#p %P dist %f\n", mo, n, n->Point, n->h);
-		n = nearest;
-		if(n == a){
-			werrstr("a∗: really can't move");
-			markmobj(mo, 1);
-			return -1;
-		}
-		/*
-		b = nearestnonjump(mo, n, b);
-		if(b == a){
-			werrstr("a∗: really can't move");
-			markmobj(mo, 1);
-			return -1;
-		}
-		clearpath();
-		a->Point = mo->Point;
-		b->Point = Pt((b - map) % mapwidth, (b - map) / mapwidth);
-		if((n = a∗(mo, a, b)) != b){
-			werrstr("bug: failed to find path to nearest non-jump point");
-			return -1;
-		}
-		*/
-	}
-	dprint("%M found %#p at %P dist %f\n", mo, n, n->Point, n->h);
+	n = a∗findpath(mo, a, b);
 	markmobj(mo, 1);
-	backtrack(mo, n, a);
+	if(n == nil){
+		dprint("%M findpath: failed\n", mo);
+		return -1;
+	}
+	dprint("%M findpath: setting path to goal %P dist %f\n", mo, n->Point, n->h);
+	a∗backtrack(mo, n, a);
 	return 0;
 }