shithub: sce

Download patch

ref: abe39da4407afac58b57173b8322936051c7bfa5
parent: 94e08d831823da42d1700faf4145dd795e0daff5
author: qwx <[email protected]>
date: Mon Aug 10 18:16:17 EDT 2020

reimplement pathfinding and movement

- sce.db: count unit size in path nodes (8px)
- fs: bind() can now return negative non-error values
- fs: separate terrain and map into separate grids for drawing
- drw: path visualization (debugmap)
- drw: add visible unit bitmap for selection the size of the screen
- drw: bound drawing to visible area, separate layers
- drw: draw fixes, some legibility improvements
- map: spawn units on top of spawner object, finding free spawning spot
- path: add jps(B) on top of a∗ with pairing heaps for priority queues and bitmap for map
- path: disallow moving unit on top of itself
- path: attempt movement towards target if inaccessible, given jps limitations
- path: move pathfinding goal if target is within a block
- sim: redo tracking units on the map
- sim: redo movement increments, disallow corner coasting
- sim: rework pathfinding error conditions, restarting, aborting

pathfinding now performs very well even on largest 256x256 map,
but has several unresolved issues, listed in path.c.
the code is horrible and unreadable; many improvements can be made.
movement also needs refinement.
the worst: unit sizes are not multiples of 8px at all; the
benefits of compressing the map to 8x8 blocks now gone; other
approaches to be considered?
obviously, no multi-agent pathfinding yet.
drawing order still not right, probably have to build an ordered
list of things to draw in order.
loading all the sprites takes forever.
there's a non reproducible bug in the shitty net code which can
cause a hang on exit (or even start up?).

--- a/ai.c
+++ /dev/null
@@ -1,271 +1,0 @@
-#include <u.h>
-#include <libc.h>
-#include <draw.h>
-#include "dat.h"
-#include "fns.h"
-
-Path *path;
-int pathwidth, pathheight;
-
-typedef struct Queue Queue;
-struct Queue{
-	Path *pp;
-	Queue *q;
-	Queue *p;
-};
-static Queue q1 = {.q = &q1, .p = &q1}, *popen = &q1;
-
-int
-isblocked(Point *p, Mobj *mo)
-{
-	int x, y;
-	Path *pp, *e;
-
-	if(mo->o->f & Fair)
-		return 0;
-	pp = path + p->y * pathwidth + p->x;
-	x = mo->o->w * (Tlwidth / Tlsubwidth);
-	y = mo->o->h * (Tlheight / Tlsubheight);
-	while(y-- > 0){
-		for(e=pp+x; pp<e; pp++)
-			if(pp->blk != nil && pp->blk != mo)
-				return 1;
-		pp += pathwidth - x;
-	}
-	return 0;
-}
-
-void
-setblk(Mobj *mo, int clr)
-{
-	int x, y;
-	Path *pp, *e;
-	Lobj *lo;
-
-	pp = path + mo->y * pathwidth + mo->x;
-	x = mo->o->w * (Tlwidth / Tlsubwidth);
-	y = mo->o->h * (Tlheight / Tlsubheight);
-	lo = mo->blk;
-	if(mo->x + x > pathwidth)
-		x = pathwidth - mo->x;
-	if(mo->y + y > pathheight)
-		y = pathheight - mo->y;
-	while(y-- > 0){
-		for(e=pp+x; pp<e; pp++)
-			if(clr){
-				if(pp->blk == mo)
-					pp->blk = nil;
-				lunlink(lo++);
-			}else{
-				if((mo->o->f & Fair) == 0)
-					pp->blk = mo;
-				llink(lo++, &pp->lo);
-			}
-		pp += pathwidth - x;
-	}
-}
-
-static Path *
-poppath(Queue *r)
-{
-	Path *p;
-	Queue *q;
-
-	q = r->q;
-	if(q == r)
-		return nil;
-	p = q->pp;
-	r->q = q->q;
-	free(q);
-	return p;
-}
-
-static void
-pushpath(Queue *r, Path *pp)
-{
-	Queue *q, *qp, *p;
-
-	for(qp=r, q=qp->q; q!=r && pp->g+pp->h > q->pp->g+q->pp->h; qp=q, q=q->q)
-		;
-	p = emalloc(sizeof *p);
-	p->pp = pp;
-	p->q = q;
-	qp->q = p;
-}
-
-static void
-clearpath(void)
-{
-	Queue *p, *q;
-	Path *pp;
-
-	/* FIXME: separate table(s)? */
-	for(pp=path; pp<path+pathwidth*pathheight; pp++){
-		pp->g = 0x7fffffff;
-		pp->h = 0x7fffffff;
-		pp->from = nil;
-		pp->closed = 0;
-		pp->open = 0;
-	}
-	for(p=popen->q; p!=popen; p=q){
-		q = p->q;
-		free(p);
-	}
-	popen->p = popen->q = popen;
-}
-
-static void
-setpath(Path *e, Mobj *mo)
-{
-	int n;
-	Point *p, *pe, x, p0, p1;
-
-	/* FIXME: probably better to just replace it with a static buffer;
-	 * except for static buildings and air units, units are going to move
-	 * and use pathfinding almost always */
-	pe = emalloc(Npath * sizeof *pe);
-	mo->path = pe;
-	p = pe + Npath - 1;
-	n = 0;
-	/* FIXME: drawing: spawn needs to center unit on path node, and
-	 * drawing needs to offset sprite from path center */
-	for(; e!=nil; e=e->from){
-		x.x = ((e - path) % pathwidth) * Tlsubwidth + (Tlsubwidth >> 1);
-		x.y = ((e - path) / pathwidth) * Tlsubheight + (Tlsubheight >> 1);
-		if(n == 0){
-			x.x -= Tlsubwidth >> 1;
-			x.y -= Tlsubheight >> 1;
-			*p-- = x;
-			p0 = x;
-			n++;
-		}else if(n == 1){
-			p1 = x;
-			n++;
-		}else{
-			if(atan2(x.y - p0.y, x.x - p0.x)
-			!= atan2(p1.y - p0.y, p1.x - p0.x)){
-				if(p < pe){
-					memmove(pe+1, pe, (Npath - 1) * sizeof *pe);
-					*pe = p1;
-				}else
-					*p-- = p1;
-				p0 = p1;
-				n = 2;
-			}
-			p1 = x;
-		}
-	}
-	mo->pathp = p + 1;
-	mo->pathe = pe + Npath;
-}
-
-static double
-dist(Path *p, Path *q)
-{
-	int dx, dy;
-
-	dx = abs(p->x - q->x);
-	dy = abs(p->y - q->y);
-	return dx + dy;
-	//return 1 * (dx + dy) + (1 - 2 * 1) * (dx < dy ? dx : dy);
-	//return sqrt(dx * dx + dy * dy);
-}
-
-/* FIXME: air: ignore pathfinding? */
-static Path **
-trywalk(Path *pp, Mobj *mo)
-{
-	int x, y;
-	Point p, *d, diff[] = {
-		{1,0}, {0,-1}, {-1,0}, {0,1},
-		{1,1}, {-1,1}, {-1,-1}, {1,-1},
-	};
-	Path **lp;
-	static Path *l[nelem(diff) + 1];
-
-	x = (pp - path) % pathwidth;
-	y = (pp - path) / pathwidth;
-	for(d=diff, lp=l; d<diff+nelem(diff); d++){
-		p.x = x + d->x;
-		p.y = y + d->y;
-		if(p.x < 0 || p.x > pathwidth
-		|| p.y < 0 || p.y > pathheight)
-			continue;
-		if(!isblocked(&p, mo))
-			*lp++ = path + p.y * pathwidth + p.x;
-	}
-	*lp = nil;
-	return l;
-}
-
-static int
-a∗(Mobj *mo, Point *p2)
-{
-	double g;
-	Path *s, *pp, *e, *q, **l;
-
-	clearpath();
-	s = path + pathwidth * mo->y + mo->x;
-	e = path + pathwidth * (p2->y / Tlsubheight) + p2->x / Tlsubwidth;
-	/* FIXME: don't return an error, just an empty path, or the same
-	 * block as the source */
-	if(s == e){
-		werrstr("no.");
-		return -1;
-	}
-	s->g = 0;
-	s->h = dist(s, e);
-	pushpath(popen, s);
-	while((pp = poppath(popen)) != nil){
-		if(pp == e)
-			break;
-		pp->closed = 1;
-		pp->open = 0;
-		l = trywalk(pp, mo);
-		for(q=*l; q!=nil; q=*l++)
-			if(!q->closed){
-				g = pp->g + dist(pp, q);
-				if(!q->open){
-					q->from = pp;
-					q->g = g;
-					q->h = dist(q, e);
-					q->open = 1;
-					pushpath(popen, q);
-				}else if(g < q->g){
-					q->from = pp;
-					q->g = g;
-				}
-			}
-	}
-	/* FIXME: move towards target anyway */
-	if(e->from == nil && pp != e){
-		werrstr("NOPE");
-		return -1;
-	}
-	setpath(e, mo);
-	return 0;
-}
-
-int
-findpath(Mobj *mo, Point *p2)
-{
-	return a∗(mo, p2);
-}
-
-void
-initpath(void)
-{
-	int n, x, y;
-	Path *pp;
-
-	pathwidth = mapwidth * (Tlwidth / Tlsubwidth);
-	pathheight = mapheight * (Tlheight / Tlsubheight);
-	n = pathwidth * pathheight;
-	path = emalloc(n * sizeof *path);
-	for(y=0, pp=path; y<pathheight; y++)
-		for(x=0; x<pathwidth; x++, pp++){
-			pp->x = x;
-			pp->y = y;
-			pp->lo.lo = pp->lo.lp = &pp->lo;
-		}
-}
--- /dev/null
+++ b/bmap.c
@@ -1,0 +1,216 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include "dat.h"
+#include "fns.h"
+
+enum{
+	Nmaxsize = 4,
+	Npad = 1,
+};
+
+static u64int *bmap, *rbmap;
+static int bmapwidth, bmapheight, rbmapwidth, rbmapheight;
+
+static uchar ffstab[256];
+
+int
+lsb(uvlong v)
+{
+	int c;
+
+	c = 0;
+	if((v & 0xffffffff) == 0){
+		v >>= 32;
+		c += 32;
+	}
+	if((v & 0xffff) == 0){
+		v >>= 16;
+		c += 16;
+	}
+	if((v & 0xff) == 0){
+		v >>= 8;
+		c += 8;
+	}
+	if((v & 0xf) == 0){
+		v >>= 4;
+		c += 4;
+	}
+	if((v & 3) == 0){
+		v >>= 2;
+		c += 2;
+	}
+	if((v & 1) == 0)
+		c++;
+	return c;
+}
+
+int
+msb(uvlong v)
+{
+	int n;
+
+	if(n = v >> 56)
+		return 56 + ffstab[n];
+	else if(n = v >> 48)
+		return 48 + ffstab[n];
+	else if(n = v >> 40)
+		return 40 + ffstab[n];
+	else if(n = v >> 32)
+		return 32 + ffstab[n];
+	else if(n = v >> 24)
+		return 24 + ffstab[n];
+	else if(n = v >> 16)
+		return 16 + ffstab[n];
+	else if(n = v >> 8)
+		return 8 + ffstab[n];
+	else
+		return ffstab[v];
+}
+
+u64int *
+baddr(int x, int y)
+{
+	x >>= Bshift;
+	x += Npad;
+	y += Npad;
+	return bmap + y * bmapwidth + x;
+}
+
+u64int *
+rbaddr(int y, int x)
+{
+	x >>= Bshift;
+	x += Npad;
+	y += Npad;
+	return rbmap + y * rbmapwidth + x;
+}
+
+static u64int *
+breduce(u64int *p, int Δp, int ofs, int w, int h, int Δw, int Δh, int left)
+{
+	static u64int row[Nmaxsize+2];
+	int i, j;
+	u64int u, m;
+
+	m = (1 << w - 1) - 1;
+	if(left){
+		ofs = 64 - w - Δw - ofs;
+		m <<= 63 - w + 1;
+	}
+	m = ~m;
+	for(i=0; i<h+Δh; i++, p+=Δp){
+		u = p[0];
+		if(ofs > 0){
+			if(left){
+				u >>= ofs;
+				u |= p[-1] << 64 - ofs;
+			}else{
+				u <<= ofs;
+				u |= p[1] >> 64 - ofs;
+			}
+		}
+		if(left)
+			switch(w){
+			case 4: u |= u >> 1 | u >> 2 | u >> 3; break;
+			case 2: u |= u >> 1; break;
+			}
+		else
+			switch(w){
+			case 4: u |= u << 1 | u << 2 | u << 3; break;
+			case 2: u |= u << 1; break;
+			}
+		u &= m;
+		row[i] = u;
+		for(j=max(i-h+1, 0); j<i; j++)
+			row[j] |= u;
+	}
+	return row;
+}
+
+u64int *
+bload(int x, int y, int w, int h, int Δw, int Δh, int left, int rot)
+{
+	int ofs, Δp;
+	u64int *p;
+
+	if(rot){
+		p = rbaddr(x, y);
+		Δp = rbmapwidth;
+		ofs = y & Bmask;
+	}else{
+		p = baddr(x, y);
+		Δp = bmapwidth;
+		ofs = x & Bmask;
+	}
+	return breduce(p, Δp, ofs, w, h, Δw, Δh, left);
+}
+
+void
+bset(int x, int y, int w, int h, int set)
+{
+	int i, Δ, n;
+	u64int *p, m, m´;
+
+	p = baddr(x, y);
+	n = x & Bmask;
+	m = (1ULL << w) - 1 << 64 - w;
+	m >>= n;
+	Δ = n + w - 64;
+	m´ = (1ULL << Δ) - 1 << 64 - Δ;
+	for(i=0; i<h; i++, p+=bmapwidth){
+		p[0] = set ? p[0] | m : p[0] & ~m;
+		if(Δ > 0)
+			p[1] = set ? p[1] | m´ : p[1] & ~m´;
+	}
+	p = rbaddr(x, y);
+	n = y & Bmask;
+	m = (1ULL << h) - 1 << 64 - h;
+	m >>= n;
+	Δ = n + h - 64;
+	m´ = (1ULL << Δ) - 1 << 64 - Δ;
+	for(i=0; i<w; i++, p+=rbmapwidth){
+		p[0] = set ? p[0] | m : p[0] & ~m;
+		if(Δ > 0)
+			p[1] = set ? p[1] | m´ : p[1] & ~m´;
+	}
+}
+
+static void
+initffs(void)
+{
+	int i;
+
+	ffstab[0] = 0;
+	ffstab[1] = 0;
+	for(i=2; i<nelem(ffstab); i++)
+		ffstab[i] = 1 + ffstab[i/2];
+}
+
+void
+initbmap(void)
+{
+	int i;
+
+	bmapwidth = (mapwidth >> Bshift) + 2 * Npad;
+	bmapheight = mapheight + 2 * Npad;
+	rbmapwidth = (mapheight >> Bshift) + 2 * Npad;
+	rbmapheight = mapwidth + 2 * Npad;
+	bmap = emalloc(bmapwidth * bmapheight * sizeof *bmap);
+	rbmap = emalloc(rbmapwidth * rbmapheight * sizeof *rbmap);
+	for(i=0; i<Npad; i++){
+		memset(bmap + i * mapwidth, 0xff, bmapwidth * sizeof *bmap);
+		memset(bmap + (bmapheight - i - 1) * bmapwidth, 0xff,
+			bmapwidth * sizeof *bmap);
+		memset(rbmap + i * rbmapwidth, 0xff, rbmapwidth * sizeof *rbmap);
+		memset(rbmap + (rbmapheight - i - 1) * rbmapwidth, 0xff,
+			rbmapwidth * sizeof *rbmap);
+	}
+	for(i=Npad; i<bmapheight-Npad; i++){
+		memset(bmap + i * bmapwidth, 0xff, Npad * sizeof *bmap);
+		memset(bmap + (i+1) * bmapwidth - Npad, 0xff, Npad * sizeof *bmap);
+		memset(rbmap + i * rbmapwidth, 0xff, Npad * sizeof *rbmap);
+		memset(rbmap + (i+1) * rbmapwidth - Npad, 0xff, Npad * sizeof *rbmap);
+	}
+	initffs();
+}
--- a/dat.h
+++ b/dat.h
@@ -1,14 +1,16 @@
+typedef struct Node Node;
+typedef struct Pairheap Pairheap;
 typedef struct Attack Attack;
 typedef struct Pic Pic;
 typedef struct Pics Pics;
 typedef struct Obj Obj;
+typedef struct Path Path;
 typedef struct Mobj Mobj;
-typedef struct Lobj Lobj;
+typedef struct Mobjl Mobjl;
 typedef struct Terrain Terrain;
 typedef struct Map Map;
 typedef struct Resource Resource;
 typedef struct Team Team;
-typedef struct Path Path;
 
 enum{
 	Nresource = 3,
@@ -15,17 +17,44 @@
 	Nteam = 8,
 	Nselect = 12,
 	Nrot = 32,
-	Npath = 64,
 	Tlwidth = 32,
 	Tlheight = Tlwidth,
-	Tlshift = 16,
-	Tlmask = ((1 << Tlshift) - 1) << Tlshift,
 	Tlsubshift = 2,
 	Tlsubwidth = Tlwidth >> Tlsubshift,
 	Tlsubheight = Tlheight >> Tlsubshift,
-	Tlsubmask = Tlsubwidth - 1,
+	Tlnsub = Tlwidth / Tlsubwidth,
+	Subpxshift = 16,
+	Subpxmask = (1 << Subpxshift) - 1,
 };
 
+enum{
+	Bshift = 6,
+	Bmask = (1 << Bshift) - 1,
+};
+
+struct Pairheap{
+	double sum;
+	Node *n;
+	Pairheap *parent;
+	Pairheap *left;
+	Pairheap *right;
+};
+
+struct Node{
+	int x;
+	int y;
+	int closed;
+	int open;
+	double g;
+	double Δg;
+	double h;
+	int step;
+	int dir;
+	Node *from;
+	Pairheap *p;
+};
+extern Node *node;
+
 struct Attack{
 	char *name;
 	int dmg;
@@ -63,10 +92,10 @@
 	Pics pidle;
 	Pics pmove;
 	int nr;
-	Attack *atk[2];
-	int f;
 	int w;
 	int h;
+	int f;
+	Attack *atk[2];
 	int hp;
 	int def;
 	int speed;
@@ -76,47 +105,50 @@
 	Obj **spawn;
 	int nspawn;
 };
-
+struct Path{
+	Point target;
+	int goalblocked;
+	int npatherr;
+	int npathbuf;
+	Point *paths;
+	Point *pathp;
+	Point *pathe;
+};
 struct Mobj{
 	Obj *o;
 	Pics *pics;
-	Point;
-	Point p;
-	Point subp;
 	int θ;
-	double vx;
-	double vy;
-	double vv;
+	Point;
+	int px;
+	int py;
+	int subpx;
+	int subpy;
+	Path;
 	int Δθ;
-	Point *path;
-	Point *pathp;
-	Point *pathe;
+	double u;
+	double v;
+	double speed;
+	Mobjl *movingp;
+	Mobjl *mapp;
 	int f;
 	int team;
 	int hp;
 	int xp;
-	Lobj *blk;
-	Lobj *zl;
-	Lobj *vl;
 };
-
-struct Lobj{
+struct Mobjl{
 	Mobj *mo;
-	Lobj *lo;
-	Lobj *lp;
+	Mobjl *l;
+	Mobjl *lp;
 };
-extern Lobj zlist;
 
 struct Terrain{
 	Pic *p;
 };
+extern Terrain **terrain;
+extern terwidth, terheight;
 
 struct Map{
-	Point;
-	int tx;
-	int ty;
-	Terrain *t;
-	Lobj lo;
+	Mobjl ml;
 };
 extern Map *map;
 extern int mapwidth, mapheight;
@@ -135,19 +167,6 @@
 extern Team team[Nteam], *curteam;
 extern int nteam;
 
-struct Path{
-	Point;
-	Lobj lo;
-	Mobj *blk;
-	int closed;
-	int open;
-	Path *from;
-	double g;
-	double h;
-};
-extern Path *path;
-extern int pathwidth, pathheight;
-
 extern int lport;
 extern char *netmtpt;
 
@@ -160,3 +179,5 @@
 extern char *progname, *prefix, *dbname, *mapname;
 extern int clon;
 extern vlong tc;
+extern int pause, debugmap;
+extern int debug;
--- a/drw.c
+++ b/drw.c
@@ -7,25 +7,15 @@
 int scale = 1;
 Point p0, pan;
 
-static int fbsz, fbh, fbw;
-static u32int *fb, *fbe;
+static int fbsz, fbh, fbw, fbws;
+static u32int *fb, *fbvis;
 static Image *fbi;
 static Rectangle selr;
 static Point panmax;
 static Mobj *selected[Nselect];
+static Mobj **visbuf;
+static int nvisbuf, nvis;
 
-static int
-max(int a, int b)
-{
-	return a > b ? a : b;
-}
-
-static int
-min(int a, int b)
-{
-	return a < b ? a : b;
-}
-
 void
 dopan(int dx, int dy)
 {
@@ -41,38 +31,73 @@
 		pan.y = panmax.y;
 }
 
-static Mobj *
-mapselect(Point *pp)
-{
-	Path *p;
-
-	p = path + pathwidth * (pp->y / Tlsubheight) + pp->x / Tlsubwidth;
-	return p->lo.lo->mo;
-}
-
 void
 select(Point p, int b)
 {
+	int i;
+	Point vp;
+	Mobj *mo;
+
 	if(!ptinrect(p, selr))
 		return;
-	p = divpt(addpt(subpt(p, selr.min), pan), scale);
-	/* FIXME: multiple selections */
-	/* FIXME: selection map based on sprite dimensions and offset */
-	if(b & 1)
-		selected[0] = mapselect(&p);
-	else if(b & 4){
+	if(b & 1){
+		p = divpt(subpt(p, selr.min), scale);
+		i = fbvis[p.y * fbw + p.x];
+		selected[0] = i == -1 ? nil : visbuf[i];
+	}else if(b & 4){
 		if(selected[0] == nil)
 			return;
-		/* FIXME: this implements move for any unit of any team,
-		 * including buildings, but not attack or anything else */
-		/* FIXME: attack, attackobj, moveobj (follow obj), etc. */
-		/* FIXME: offset sprite size */
-		//move(p.x & ~Tlsubmask, p.y & ~Tlsubmask, selected);
-		move(p.x, p.y, selected);
+		vp = divpt(subpt(p, selr.min), scale);
+		i = fbvis[vp.y * fbw + vp.x];
+		mo = i == -1 ? nil : visbuf[i];
+		if(mo == selected[0]){
+			dprint("select: %#p not moving to itself\n", visbuf[i]);
+			return;
+		}
+		p = divpt(addpt(subpt(p, selr.min), pan), scale);
+		p.x /= Tlsubwidth;
+		p.y /= Tlsubheight;
+		moveone(p, selected[0], mo);
 	}
 }
 
+static void
+drawhud(void)
+{
+	char s[256];
+	Mobj *mo;
+
+	draw(screen, Rpt(p0, screen->r.max), display->black, nil, ZP);
+	mo = selected[0];
+	if(mo == nil)
+		return;
+	snprint(s, sizeof s, "%s %d/%d", mo->o->name, mo->hp, mo->o->hp);
+	string(screen, p0, display->white, ZP, font, s);
+}
+
 static int
+addvis(Mobj *mo)
+{
+	int i;
+
+	if((i = nvis++) >= nvisbuf){
+		visbuf = erealloc(visbuf, (nvisbuf + 16) * sizeof *visbuf,
+			nvisbuf * sizeof *visbuf);
+		nvisbuf += 16;
+	}
+	visbuf[i] = mo;
+	return i;
+}
+
+static void
+clearvis(void)
+{
+	if(visbuf != nil)
+		memset(visbuf, 0, nvisbuf * sizeof *visbuf);
+	nvis = 0;
+}
+
+static int
 boundpic(Rectangle *r, u32int **q)
 {
 	int w;
@@ -79,7 +104,7 @@
 
 	r->min.x -= pan.x / scale;
 	r->min.y -= pan.y / scale;
-	if(r->min.x + r->max.x < 0 || r->min.x >= fbw / scale
+	if(r->min.x + r->max.x < 0 || r->min.x >= fbw
 	|| r->min.y + r->max.y < 0 || r->min.y >= fbh)
 		return -1;
 	w = r->max.x;
@@ -89,8 +114,8 @@
 		r->max.x += r->min.x;
 		r->min.x = 0;
 	}
-	if(r->min.x + r->max.x > fbw / scale)
-		r->max.x -= r->min.x + r->max.x - fbw / scale;
+	if(r->min.x + r->max.x > fbw)
+		r->max.x -= r->min.x + r->max.x - fbw;
 	if(r->min.y < 0){
 		if(q != nil)
 			*q -= w * r->min.y;
@@ -100,14 +125,15 @@
 	if(r->min.y + r->max.y > fbh)
 		r->max.y -= r->min.y + r->max.y - fbh;
 	r->min.x *= scale;
+	r->max.x *= scale;
 	return 0;
 }
 
 static void
-drawpic(int x, int y, Pic *pic)
+drawpic(int x, int y, Pic *pic, int ivis)
 {
-	int n, w, Δp, Δq;
-	u32int v, *p, *q;
+	int n, Δp, Δsp, Δq;
+	u32int v, *p, *e, *sp, *q;
 	Rectangle r;
 
 	if(pic->p == nil)
@@ -116,12 +142,14 @@
 	r = Rect(x + pic->dx, y + pic->dy, pic->w, pic->h);
 	if(boundpic(&r, &q) < 0)
 		return;
-	Δq = pic->w - r.max.x;
-	p = fb + r.min.y * fbw + r.min.x;
-	Δp = fbw - r.max.x * scale;
+	Δq = pic->w - r.max.x / scale;
+	p = fb + r.min.y * fbws + r.min.x;
+	Δp = fbws - r.max.x;
+	sp = fbvis + r.min.y * fbw + r.min.x / scale;
+	Δsp = fbw - r.max.x / scale;
 	while(r.max.y-- > 0){
-		w = r.max.x;
-		while(w-- > 0){
+		e = p + r.max.x;
+		while(p < e){
 			v = *q++;
 			if(v & 0xff << 24){
 				for(n=0; n<scale; n++)
@@ -128,9 +156,11 @@
 					*p++ = v;
 			}else
 				p += scale;
+			*sp++ = ivis;
 		}
 		q += Δq;
 		p += Δp;
+		sp += Δsp;
 	}
 }
 
@@ -137,8 +167,8 @@
 static void
 drawshadow(int x, int y, Pic *pic)
 {
-	int n, w, Δp, Δq;
-	u32int v, *p, *q;
+	int n, Δp, Δq;
+	u32int v, *p, *e, *q;
 	Rectangle r;
 
 	q = pic->p;
@@ -145,12 +175,12 @@
 	r = Rect(x + pic->dx, y + pic->dy, pic->w, pic->h);
 	if(boundpic(&r, &q) < 0)
 		return;
-	Δq = pic->w - r.max.x;
-	p = fb + r.min.y * fbw + r.min.x;
-	Δp = fbw - r.max.x * scale;
+	Δq = pic->w - r.max.x / scale;
+	p = fb + r.min.y * fbws + r.min.x;
+	Δp = fbws - r.max.x;
 	while(r.max.y-- > 0){
-		w = r.max.x;
-		while(w-- > 0){
+		e = p + r.max.x;
+		while(p < e){
 			v = *q++;
 			if(v & 0xff << 24){
 				v = p[0];
@@ -167,21 +197,21 @@
 	}
 }
 
-static void
-compose(Path *pp, u32int c)
+void
+compose(int x, int y, u32int c)
 {
-	int n, w, Δp;
-	u32int v, *p;
+	int n, Δp;
+	u32int v, *p, *e;
 	Rectangle r;
 
-	r = Rect(pp->x * Tlsubwidth, pp->y * Tlsubheight, Tlsubwidth, Tlsubheight);
+	r = Rect(x * Tlsubwidth, y * Tlsubheight, Tlsubwidth, Tlsubheight);
 	if(boundpic(&r, nil) < 0)
 		return;
-	p = fb + r.min.y * fbw + r.min.x;
-	Δp = fbw - r.max.x * scale;
+	p = fb + r.min.y * fbws + r.min.x;
+	Δp = fbws - r.max.x;
 	while(r.max.y-- > 0){
-		w = r.max.x;
-		while(w-- > 0){
+		e = p + r.max.x;
+		while(p < e){
 			v = *p;
 			v = (v & 0xff0000) + (c & 0xff0000) >> 1 & 0xff0000
 				| (v & 0xff00) + (c & 0xff00) >> 1 & 0xff00
@@ -193,6 +223,41 @@
 	}
 }
 
+static void
+drawmap(Rectangle *r)
+{
+	int x, y;
+	u64int *row, v, m;
+	Node *n;
+	Mobj *mo;
+	Point *p;
+
+	for(y=r->min.y, n=node+y*mapwidth+r->min.x; y<r->max.y; y++){
+		x = r->min.x;
+		row = baddr(x, y);
+		v = *row++;
+		m = 1ULL << 63 - (x & Bmask);
+		for(; x<r->max.x; x++, n++, m>>=1){
+			if(m == 0){
+				v = *row++;
+				m = 1ULL << 63;
+			}
+			if(v & m)
+				compose(x, y, 0xff0000);
+			if(n->closed)
+				compose(x, y, 0x0000ff);
+			else if(n->open)
+				compose(x, y, 0xffff00);
+		}
+		n += mapwidth - (r->max.x - r->min.x);
+	}
+	if((mo = selected[0]) != nil && mo->pathp != nil){
+		for(p=mo->paths; p<mo->pathe; p++)
+			compose(p->x, p->y, 0x00ff00);
+		compose(mo->target.x, mo->target.y, 0x00ffff);
+	}
+}
+
 static Pic *
 frm(Mobj *mo, int notshadow)
 {
@@ -212,64 +277,90 @@
 void
 redraw(void)
 {
-	char s[256];
-	Point p;
+	int x, y;
+	Rectangle mr, tr;
+	Terrain **t;
 	Map *m;
 	Mobj *mo;
-	Lobj *lo;
-	//Path *pp;
+	Mobjl *ml;
 
-	/* FIXME: only process visible parts of the screen and adjacent tiles */
-	for(m=map; m<map+mapwidth*mapheight; m++)
-		drawpic(m->x, m->y, m->t->p);
-	/* FIXME: draw overlay (creep) */
-	/* FIXME: draw corpses */
-	for(m=map; m<map+mapwidth*mapheight; m++){
-		for(lo=m->lo.lo; lo!=&m->lo; lo=lo->lo){
-			mo = lo->mo;
-			drawshadow(mo->p.x, mo->p.y, frm(mo, 0));
+	clearvis();
+	tr.min.x = pan.x / scale / Tlwidth;
+	tr.min.y = pan.y / scale / Tlheight;
+	tr.max.x = min(tr.min.x + fbw / Tlwidth + scale, terwidth);
+	tr.max.y = min(tr.min.y + fbh / Tlheight + scale, terheight);
+	mr.min.x = max(tr.min.x - 3, 0) * Tlnsub;
+	mr.min.y = max(tr.min.y - 3, 0) * Tlnsub;
+	mr.max.x = tr.max.x * Tlnsub;
+	mr.max.y = tr.max.y * Tlnsub;
+	for(y=tr.min.y, t=terrain+y*terwidth+tr.min.x; y<tr.max.y; y++){
+		for(x=tr.min.x; x<tr.max.x; x++, t++)
+			drawpic(x*Tlwidth, y*Tlheight, (*t)->p, -1);
+		t += terwidth - (tr.max.x - tr.min.x);
+	}
+	for(y=mr.min.y, m=map+y*mapwidth+mr.min.x; y<mr.max.y; y++){
+		for(x=mr.min.x; x<mr.max.x; x++, m++){
+			for(ml=m->ml.l; ml!=&m->ml; ml=ml->l){
+				mo = ml->mo;
+				if(mo->o->f & Fair)
+					break;
+				drawshadow(mo->px, mo->py, frm(mo, 0));
+			}
+			for(ml=m->ml.l; ml!=&m->ml; ml=ml->l){
+				mo = ml->mo;
+				if(mo->o->f & Fair)
+					break;
+				drawpic(mo->px, mo->py, frm(mo, 1), addvis(mo));
+			}
 		}
-		for(lo=m->lo.lo; lo!=&m->lo; lo=lo->lo){
-			mo = lo->mo;
-			drawpic(mo->p.x, mo->p.y, frm(mo, 1));
+		m += mapwidth - (mr.max.x - mr.min.x);
+	}
+	for(y=mr.min.y, m=map+y*mapwidth+mr.min.x; y<mr.max.y; y++){
+		for(x=mr.min.x; x<mr.max.x; x++, m++){
+			for(ml=m->ml.l; ml!=&m->ml; ml=ml->l){
+				mo = ml->mo;
+				if((mo->o->f & Fair) == 0)
+					continue;
+				drawshadow(mo->px, mo->py, frm(mo, 0));
+			}
+			for(ml=m->ml.l; ml!=&m->ml; ml=ml->l){
+				mo = ml->mo;
+				if((mo->o->f & Fair) == 0)
+					continue;
+				drawpic(mo->px, mo->py, frm(mo, 1), addvis(mo));
+			}
 		}
+		m += mapwidth - (mr.max.x - mr.min.x);
 	}
-	//for(pp=path; pp<path+pathwidth*pathheight; pp++)
-	//	if(pp->blk != nil)
-	//		compose(pp, 0xff00ff);
-	/* FIXME: draw hud */
-	draw(screen, Rpt(p0, screen->r.max), display->black, nil, ZP);
-	mo = selected[0];
-	if(mo == nil)
-		return;
-	/* FIXME: selected should be its own layer, not mapped to Map,
-	 * since the coordinates won't match */
-	p = p0;
-	snprint(s, sizeof s, "%s %d/%d", mo->o->name, mo->hp, mo->o->hp);
-	string(screen, p, display->white, ZP, font, s);
+	if(debugmap)
+		drawmap(&mr);
+	drawhud();
 }
 
 void
 resetfb(void)
 {
-	fbw = min(mapwidth * Tlwidth * scale, Dx(screen->r));
-	fbh = min(mapheight * Tlheight * scale, Dy(screen->r));
-	selr = Rpt(screen->r.min, addpt(screen->r.min, Pt(fbw, fbh)));
+	fbws = min(mapwidth * Tlsubwidth * scale, Dx(screen->r));
+	fbh = min(mapheight * Tlsubheight * scale, Dy(screen->r));
+	selr = Rpt(screen->r.min, addpt(screen->r.min, Pt(fbws, fbh)));
 	p0 = Pt(screen->r.min.x + 8, screen->r.max.y - 3 * font->height);
-	panmax.x = max(Tlwidth * mapwidth * scale - Dx(screen->r), 0);
-	panmax.y = max(Tlheight * mapheight * scale - Dy(screen->r), 0);
+	panmax.x = max(Tlsubwidth * mapwidth * scale - Dx(screen->r), 0);
+	panmax.y = max(Tlsubheight * mapheight * scale - Dy(screen->r), 0);
 	if(p0.y < selr.max.y){
 		panmax.y += selr.max.y - p0.y;
+		fbh -= selr.max.y - p0.y;
 		selr.max.y = p0.y;
 	}
+	fbw = fbws / scale;
 	fbh /= scale;
-	fbsz = fbw * fbh * sizeof *fb;
+	fbsz = fbws * fbh * sizeof *fb;
 	free(fb);
+	free(fbvis);
 	freeimage(fbi);
 	fb = emalloc(fbsz);
-	fbe = fb + fbsz / sizeof *fb;
+	fbvis = emalloc(fbw * fbh * sizeof *fb);
 	if((fbi = allocimage(display,
-		Rect(0,0,fbw,scale==1 ? fbh : 1),
+		Rect(0,0,fbws,scale==1 ? fbh : 1),
 		XRGB32, scale>1, 0)) == nil)
 		sysfatal("allocimage: %r");
 	draw(screen, screen->r, display->black, nil, ZP);
@@ -282,8 +373,6 @@
 	Rectangle r, r2;
 
 	r = selr;
-	if(r.max.y > p0.y)
-		r.max.y = p0.y;
 	p = (uchar *)fb;
 	if(scale == 1){
 		loadimage(fbi, fbi->r, p, fbsz);
--- a/fns.h
+++ b/fns.h
@@ -1,7 +1,7 @@
 void	stepsnd(void);
 void	initsnd(void);
-void	llink(Lobj*, Lobj*);
-void	lunlink(Lobj*);
+void	linktomap(Mobj*);
+int	moveone(Point, Mobj*, Mobj*);
 void	stepsim(void);
 void	initsv(void);
 void	flushcl(void);
@@ -10,18 +10,36 @@
 void	joinnet(char*);
 void	listennet(void);
 void	dopan(int, int);
+void	compose(int, int, u32int);
 void	redraw(void);
 void	resetfb(void);
 void	drawfb(void);
 void	initimg(void);
 void	init(void);
-int	isblocked(Point*, Mobj*);
-void	setblk(Mobj*, int);
-int	findpath(Mobj*, Point*);
-void	initpath(void);
-int	move(int, int, Mobj**);
-int	spawn(Map*, Obj*, int);
-void	inittab(void);
+void	setgoal(Point*, Mobj*, Mobj*);
+int	isblocked(int, int, Obj*);
+void	markmobj(Mobj*, int);
+int	findpath(Point, Mobj*);
+Mobj*	mapspawn(int, int, Obj*);
+void	initmap(void);
+int	spawn(int, int, Obj*, int);
+void	nukequeue(Pairheap**);
+Pairheap*	popqueue(Pairheap**);
+void	decreasekey(Pairheap*, double, Pairheap**);
+void	pushqueue(Node*, Pairheap**);
+int	lsb(uvlong);
+int	msb(uvlong);
+u64int*	baddr(int, int);
+u64int*	rbaddr(int, int);
+u64int*	bload(int, int, int, int, int, int, int, int);
+void	bset(int, int, int, int, int);
+void	initbmap(void);
+void	dprint(char *, ...);
+int	max(int, int);
+int	min(int, int);
 char*	estrdup(char*);
+void*	erealloc(void*, ulong, ulong);
 void*	emalloc(ulong);
 vlong	flen(int);
+
+#pragma	varargck	argpos	dprint	1
--- a/fs.c
+++ b/fs.c
@@ -6,11 +6,7 @@
 #include "dat.h"
 #include "fns.h"
 
-/* FIXME: building sight range, set to 1 for now */
-
 Resource resource[Nresource];
-Map *map;
-int mapwidth, mapheight;
 
 typedef struct Table Table;
 typedef struct Objp Objp;
@@ -43,7 +39,7 @@
 	Terrain *t;
 	Terrainl *l;
 };
-static Terrainl terrain0 = {.l = &terrain0}, *terrain = &terrain0;
+static Terrainl terrainl0 = {.l = &terrainl0}, *terrainl = &terrainl0;
 static Picl pic0 = {.l = &pic0}, *pic = &pic0;
 static Objp *objp;
 static Attack *attack;
@@ -158,16 +154,16 @@
 {
 	Terrainl *tl;
 
-	for(tl=terrain->l; tl!=terrain; tl=tl->l)
+	for(tl=terrainl->l; tl!=terrainl; tl=tl->l)
 		if(tl->id == id)
 			break;
-	if(tl == terrain){
+	if(tl == terrainl){
 		tl = emalloc(sizeof *tl);
 		tl->id = id;
 		tl->t = emalloc(sizeof *tl->t);
 		tl->t->p = pushpic(",", id, PFterrain, 1);
-		tl->l = terrain->l;
-		terrain->l = tl;
+		tl->l = terrainl->l;
+		terrainl->l = tl;
 	}
 	return tl->t;
 }
@@ -275,23 +271,16 @@
 readmap(char **fld, int n, Table *tab)
 {
 	int x;
-	Map *m;
+	Terrain **t;
 
 	if(tab->row == 0){
 		tab->ncol = n;
-		mapwidth = n;
-		map = emalloc(mapheight * mapwidth * sizeof *map);
+		terwidth = n;
+		terrain = emalloc(terheight * terwidth * sizeof *terrain);
 	}
-	m = map + tab->row * mapwidth;
-	for(x=0; x<n; x++, m++){
-		unpack(fld++, "t", &m->t);
-		/* FIXME: get rid of these */
-		m->tx = x;
-		m->ty = tab->row;
-		m->x = m->tx * Tlwidth;
-		m->y = m->ty * Tlheight;
-		m->lo.lo = m->lo.lp = &m->lo;
-	}
+	t = terrain + tab->row * terwidth;
+	for(x=0; x<n; x++, t++)
+		unpack(fld++, "t", t);
 }
 
 static void
@@ -346,6 +335,8 @@
 		&o->hp, &o->def, &o->speed, &o->vis,
 		o->cost, o->cost+1, o->cost+2, &o->time,
 		o->atk, o->atk+1);
+	if(o->w < 1 || o->h < 1)
+		sysfatal("readobj: %s invalid dimensions %d,%d", o->name, o->w, o->h);
 }
 
 static void
@@ -386,7 +377,7 @@
 	{"resource", readresource, 2, &nresource},
 	{"spawn", readspawn, -1, nil},
 	{"tileset", readtileset, 1, nil},
-	{"map", readmap, -1, &mapheight},
+	{"map", readmap, -1, &terheight},
 	{"spr", readspr, -1, nil},
 };
 
@@ -467,13 +458,10 @@
 initmapobj(void)
 {
 	Objp *op;
-	Map *m;
 
-	for(op=objp; op<objp+nobjp; op++){
-		m = map + mapwidth * op->y + op->x;
-		if(spawn(m, op->o, op->team) < 0)
+	for(op=objp; op<objp+nobjp; op++)
+		if(spawn(op->x * Tlnsub, op->y * Tlnsub, op->o, op->team) < 0)
 			sysfatal("initmapobj: %s team %d: %r", op->o->name, op->team);
-	}
 	free(objp);
 }
 
@@ -482,8 +470,8 @@
 {
 	Terrainl *tl;
 
-	for(tl=terrain->l; tl!=terrain; tl=terrain->l){
-		terrain->l = tl->l;
+	for(tl=terrainl->l; tl!=terrainl; tl=terrainl->l){
+		terrainl->l = tl->l;
 		free(tl);
 	}
 }
@@ -495,8 +483,9 @@
 		sysfatal("checkdb: no tileset defined");
 	if(nresource != Nresource)
 		sysfatal("checkdb: incomplete resource specification");
-	if(mapwidth < 8 || mapheight < 8)
-		sysfatal("checkdb: map too small %d,%d", mapwidth, mapheight);
+	if(terwidth % 16 != 0 || terheight % 16 != 0 || terwidth * terheight == 0)
+		sysfatal("checkdb: map size %d,%d not in multiples of 16",
+			terwidth, terheight);
 	if(nteam < 2)
 		sysfatal("checkdb: not enough teams");
 }
@@ -504,9 +493,9 @@
 static void
 initdb(void)
 {
-	initpath();
-	initmapobj();
 	checkdb();
+	initmap();
+	initmapobj();
 	cleanup();
 }
 
@@ -513,7 +502,7 @@
 void
 init(void)
 {
-	if(bind(".", prefix, MBEFORE|MCREATE) < 0 || chdir(prefix) < 0)
+	if(bind(".", prefix, MBEFORE|MCREATE) == -1 || chdir(prefix) < 0)
 		fprint(2, "init: %r\n");
 	loaddb(dbname);
 	loaddb(mapname);
--- /dev/null
+++ b/map.c
@@ -1,0 +1,124 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include "dat.h"
+#include "fns.h"
+
+Terrain **terrain;
+int terwidth, terheight;
+Map *map;
+int mapwidth, mapheight;
+Node *node;
+
+static void
+updatemap(Mobj *mo)
+{
+	linktomap(mo);
+	markmobj(mo, 1);
+}
+
+static int
+findspawn(int *nx, int *ny, int ofs, Obj *o, Obj *spawn)
+{
+	int sz, x, y, minx, miny, maxx, maxy;
+
+	sz = ofs * o->w;
+	minx = *nx - sz;
+	miny = *ny - sz;
+	maxx = *nx + spawn->w + sz;
+	maxy = *ny + spawn->h + sz;
+	for(x=minx+sz, y=maxy; x<maxx; x++)
+		if(!isblocked(x, y, o))
+			goto found;
+	for(x=maxx, y=maxy; y>miny; y--)
+		if(!isblocked(x, y, o))
+			goto found;
+	for(x=maxx, y=miny; x>minx; x--)
+		if(!isblocked(x, y, o))
+			goto found;
+	for(x=minx, y=miny; y<=maxy; y++)
+		if(!isblocked(x, y, o))
+			goto found;
+	return -1;
+found:
+	*nx = x;
+	*ny = y;
+	return 0;
+}
+
+static int
+getspawn(int *nx, int *ny, Obj *o)
+{
+	int n, x, y;
+	Mobj *mo;
+	Map *m;
+
+	x = *nx;
+	y = *ny;
+	if(o->f & Fbuild){
+		if(isblocked(x, y, o)){
+			werrstr("getspawn: building placement at %d,%d blocked", x, y);
+			return -1;
+		}
+	}else if((o->f & Fair) == 0){
+		m = map + y * mapwidth + x;
+		if(m->ml.l == &m->ml || m->ml.l->mo == nil){
+			werrstr("getspawn: no spawn object at %d,%d", x, y);
+			return -1;
+		}
+		mo = m->ml.l->mo;
+		for(n=0; n<3; n+=o->w)
+			if(findspawn(&x, &y, n / o->w, o, mo->o) >= 0)
+				break;
+		if(n == 3){
+			werrstr("getspawn: no free spot for %s at %d,%d\n",
+				o->name, x, y);
+			return -1;
+		}
+	}
+	*nx = x;
+	*ny = y;
+	return 0;
+}
+
+Mobj *
+mapspawn(int x, int y, Obj *o)
+{
+	Mobj *mo;
+
+	if(o->f & Fbuild && (x & Tlnsub-1 || y & Tlnsub-1)){
+		werrstr("mapspawn: building spawn %d,%d not aligned to terrain map", x, y);
+		return nil;
+	}
+	if(getspawn(&x, &y, o) < 0)
+		return nil;
+	mo = emalloc(sizeof *mo);
+	mo->x = x;
+	mo->y = y;
+	mo->px = x * Tlsubwidth;
+	mo->py = y * Tlsubheight;
+	mo->subpx = mo->px << Subpxshift;
+	mo->subpy = mo->py << Subpxshift;
+	mo->o = o;
+	mo->f = o->f;
+	mo->hp = o->hp;
+	mo->θ = nrand(Nrot);
+	updatemap(mo);
+	return mo;
+}
+
+void
+initmap(void)
+{
+	int n;
+	Map *m;
+
+	mapwidth = terwidth * Tlnsub;
+	mapheight = terheight * Tlnsub;
+	n = mapwidth * mapheight;
+	map = emalloc(n * sizeof *map);
+	node = emalloc(n * sizeof *node);
+	for(m=map; m<map+n; m++)
+		m->ml.l = m->ml.lp = &m->ml;
+	initbmap();
+}
--- a/mkfile
+++ b/mkfile
@@ -2,13 +2,17 @@
 BIN=$home/bin/$objtype
 TARG=sce
 OFILES=\
-	ai.$O\
+	bmap.$O\
 	drw.$O\
 	fs.$O\
+	map.$O\
 	net.$O\
+	path.$O\
+	pheap.$O\
 	sce.$O\
 	sim.$O\
 	snd.$O\
+	util.$O\
 
 HFILES=dat.h fns.h
 </sys/src/cmd/mkone
--- /dev/null
+++ b/path.c
@@ -1,0 +1,525 @@
+#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,
+};
+
+#define SQRT2 1.4142135623730951
+
+static Pairheap *queue;
+static Node *nearest;
+
+static void
+clearpath(void)
+{
+	nukequeue(&queue);
+	memset(node, 0, mapwidth * mapheight * sizeof *node);
+	nearest = nil;
+}
+
+int
+isblocked(int x, int y, Obj *o)
+{
+	u64int *row;
+
+	row = bload(x, y, o->w, o->h, 0, 0, 0, 0);
+	return (*row & 1ULL << 63) != 0;
+}
+
+void
+markmobj(Mobj *mo, int set)
+{
+	int w, h;
+
+	w = mo->o->w;
+	if((mo->subpx & Subpxmask) != 0 && mo->x != (mo->px + 1) / Tlsubwidth)
+		w++;
+	h = mo->o->h;
+	if((mo->subpy & Subpxmask) != 0 && mo->y != (mo->py + 1) / Tlsubwidth)
+		h++;
+	bset(mo->x, mo->y, w, h, set);
+}
+
+static double
+octdist(Node *a, Node *b)
+{
+	int dx, dy;
+
+	dx = abs(a->x - b->x);
+	dy = abs(a->y - b->y);
+	return 1 * (dx + dy) + (SQRT2 - 2 * 1) * min(dx, dy);
+}
+
+/* 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(x, y, w, h, 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) ^ (Δug2 < 0)){
+		b->Δg = steps - abs(Δug2);
+		return b;
+	}
+	if(end)
+		return nil;
+	assert(x < mapwidth && y < mapheight);
+	n = node + y * mapwidth + x;
+	n->x = x;
+	n->y = y;
+	n->Δg = 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(x, y, w, h, 0, 0, 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 = node + y * mapwidth + x;
+	n->x = x;
+	n->y = y;
+	n->Δg = steps;
+	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(x-1, y-1, w, h, 2, 2, 1, 0);
+	return (row[2] & 7) << 6 | (row[1] & 7) << 3 | row[0] & 7;
+}
+
+static Node **
+successors(Node *n, int w, int h, 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, w, 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, w, h, b, dtab[i+1])) != nil){
+			s->dir = dtab[i+1];
+			*p++ = s;
+		}
+	}
+	return dir;
+}
+
+static Node *
+a∗(Node *a, Node *b, Mobj *mo)
+{
+	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, b);
+	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->w, mo->o->h, b);
+		for(n=*dp++; n!=nil; n=*dp++){
+			if(n->closed)
+				continue;
+			g = x->g + n->Δg;
+			Δg = n->g - g;
+			if(!n->open){
+				n->from = x;
+				n->g = g;
+				n->h = octdist(n, b);
+				n->open = 1;
+				n->step = x->step + 1;
+				pushqueue(n, &queue);
+			}else if(Δg > 0){
+				n->from = x;
+				n->step = x->step + 1;
+				n->g -= Δg;
+				decreasekey(n->p, Δg, &queue);
+			}
+			if(nearest == nil || n->h < nearest->h)
+				nearest = n;
+		}
+	}
+	return x;
+}
+
+static void
+backtrack(Node *n, Node *a, Mobj *mo)
+{
+	int x, y;
+	Point *p;
+
+	assert(n != a && n->step > 0);
+	if(mo->npathbuf < n->step){
+		mo->paths = erealloc(mo->paths,
+			n->step * sizeof mo->paths,
+			mo->npathbuf * sizeof mo->paths);
+		mo->npathbuf = n->step;
+	}
+	p = mo->paths + n->step;
+	mo->pathe = p--;
+	for(; n!=a; n=n->from){
+		x = n->x;
+		y = n->y;
+		*p-- = (Point){x, y};
+	}
+	assert(p == mo->paths - 1);
+}
+
+static Node *
+nearestnonjump(Node *n, Node *b, Mobj *mo)
+{
+	static Point dirtab[] = {
+		{0,-1},
+		{1,0},
+		{0,1},
+		{-1,0},
+	};
+	int i, x, y;
+	Node *m, *min;
+
+	min = n;
+	for(i=0; i<nelem(dirtab); i++){
+		x = n->x + dirtab[i].x;
+		y = n->y + dirtab[i].y;
+		while(!isblocked(x, y, mo->o)){
+			m = node + y * mapwidth + x;
+			m->x = x;
+			m->y = y;
+			m->h = octdist(m, b);
+			if(min->h < m->h)
+				break;
+			min = m;
+			x += dirtab[i].x;
+			y += dirtab[i].y;
+		}
+	}
+	if(min != n){
+		min->from = n;
+		min->open = 1;
+		min->step = n->step + 1;
+	}
+	return min;
+}
+
+void
+setgoal(Point *p, Mobj *mo, Mobj *block)
+{
+	int x, y, e;
+	double Δ, Δ´;
+	Node *n1, *n2, *pm;
+
+	if(block == nil){
+		mo->goalblocked = 0;
+		return;
+	}
+	mo->goalblocked = 1;
+	dprint("setgoal: moving goal %d,%d in block %#p ", p->x, p->y, block);
+	pm = node + p->y * mapwidth + p->x;
+	pm->x = p->x;
+	pm->y = p->y;
+	Δ = 0x7ffffff;
+	x = block->x;
+	y = block->y;
+	n1 = node + y * mapwidth + x;
+	n2 = n1 + (block->o->h - 1) * mapwidth;
+	for(e=x+block->o->w; x<e; x++, n1++, n2++){
+		n1->x = x;
+		n1->y = y;
+		Δ´ = octdist(pm, n1);
+		if(Δ´ < Δ){
+			Δ = Δ´;
+			p->x = x;
+			p->y = y;
+		}
+		n2->x = x;
+		n2->y = y + block->o->h - 1;
+		Δ´ = octdist(pm, n2);
+		if(Δ´ < Δ){
+			Δ = Δ´;
+			p->x = x;
+			p->y = y + block->o->h - 1;
+		}
+	}
+	x = block->x;
+	y = block->y + 1;
+	n1 = node + y * mapwidth + x;
+	n2 = n1 + block->o->w - 1;
+	for(e=y+block->o->h-2; y<e; y++, n1+=mapwidth, n2+=mapwidth){
+		n1->x = x;
+		n1->y = y;
+		Δ´ = octdist(pm, n1);
+		if(Δ´ < Δ){
+			Δ = Δ´;
+			p->x = x;
+			p->y = y;
+		}
+		n2->x = x + block->o->w - 1;
+		n2->y = y;
+		Δ´ = octdist(pm, n2);
+		if(Δ´ < Δ){
+			Δ = Δ´;
+			p->x = x + block->o->w - 1;
+			p->y = y;
+		}
+	}
+	dprint("to %d,%d\n", p->x, p->y);
+}
+
+int
+findpath(Point p, Mobj *mo)
+{
+	Node *a, *b, *n;
+
+	dprint("findpath %d,%d → %d,%d\n", mo->x, mo->y, p.x, p.y);
+	clearpath();
+	a = node + mo->y * mapwidth + mo->x;
+	a->x = mo->x;
+	a->y = mo->y;
+	b = node + p.y * mapwidth + p.x;
+	b->x = p.x;
+	b->y = p.y;
+	markmobj(mo, 0);
+	n = a∗(a, b, mo);
+	if(n != b){
+		dprint("findpath: goal unreachable\n");
+		if((n = nearest) == a || n == nil || a->h < n->h){
+			werrstr("a∗: can't move");
+			markmobj(mo, 1);
+			return -1;
+		}
+		dprint("nearest: %#p %d,%d dist %f\n", n, n->x, n->y, n->h);
+		b = nearestnonjump(n, b, mo);
+		if(b == a){
+			werrstr("a∗: really can't move");
+			markmobj(mo, 1);
+			return -1;
+		}
+		clearpath();
+		a->x = mo->x;
+		a->y = mo->y;
+		b->x = (b - node) % mapwidth;
+		b->y = (b - node) / mapwidth;
+		if((n = a∗(a, b, mo)) == nil)
+			sysfatal("findpath: phase error");
+	}
+	markmobj(mo, 1);
+	backtrack(n, a, mo);
+	return 0;
+}
--- /dev/null
+++ b/pheap.c
@@ -1,0 +1,86 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include "dat.h"
+#include "fns.h"
+
+static Pairheap *
+mergequeue(Pairheap *a, Pairheap *b)
+{
+	if(b == nil)
+		return a;
+	else if(a->sum < b->sum){
+		b->right = a->left;
+		a->left = b;
+		b->parent = a;
+		return a;
+	}else{
+		a->right = b->left;
+		b->left = a;
+		a->parent = b;
+		return b;
+	}
+}
+
+static Pairheap *
+mergepairs(Pairheap *a)
+{
+	Pairheap *b, *c;
+
+	if(a == nil)
+		return nil;
+	a->parent = nil;
+	b = a->right;
+	if(b == nil)
+		return a;
+	a->right = nil;
+	b->parent = nil;
+	c = b->right;
+	b->right = nil;
+	return mergequeue(mergequeue(a, b), mergepairs(c));
+}
+
+void
+nukequeue(Pairheap **queue)
+{
+	Pairheap *p;
+
+	while((p = popqueue(queue)) != nil)
+		free(p);
+}
+
+Pairheap *
+popqueue(Pairheap **queue)
+{
+	Pairheap *p;
+
+	p = *queue;
+	if(p == nil)
+		return nil;
+	*queue = mergepairs(p->left);
+	return p;
+}
+
+void
+decreasekey(Pairheap *p, double Δ, Pairheap **queue)
+{
+	p->sum -= Δ;
+	p->n->g -= Δ;
+	if(p->parent != nil && p->sum < p->parent->sum){
+		p->parent->left = nil;
+		p->parent = nil;
+		*queue = mergequeue(p, *queue);
+	}
+}
+
+void
+pushqueue(Node *n, Pairheap **queue)
+{
+	Pairheap *p;
+
+	p = emalloc(sizeof *p);
+	p->n = n;
+	p->sum = n->h + n->g;
+	n->p = p;
+	*queue = mergequeue(p, *queue);
+}
--- a/sce.c
+++ b/sce.c
@@ -16,6 +16,7 @@
 char *progname = "sce", *dbname, *prefix, *mapname = "map1.db";
 int clon;
 vlong tc;
+int pause, debugmap;
 
 typedef struct Kev Kev;
 typedef struct Mev Mev;
@@ -37,42 +38,8 @@
 };
 static int tv = Tfast, tdiv;
 static vlong Δtc;
-static int pause;
 static Channel *reszc, *kc, *mc;
 
-char *
-estrdup(char *s)
-{
-	if((s = strdup(s)) == nil)
-		sysfatal("estrdup: %r");
-	setmalloctag(s, getcallerpc(&s));
-	return s;
-}
-
-void *
-emalloc(ulong n)
-{
-	void *p;
-
-	if((p = mallocz(n, 1)) == nil)
-		sysfatal("emalloc: %r");
-	setmalloctag(p, getcallerpc(&n));
-	return p;
-}
-
-vlong
-flen(int fd)
-{
-	vlong l;
-	Dir *d;
-
-	if((d = dirfstat(fd)) == nil) 
-		sysfatal("flen: %r");
-	l = d->length;
-	free(d);
-	return l;
-}
-
 static void
 mproc(void *)
 {
@@ -179,9 +146,8 @@
 		if(!ke.down)
 			continue;
 		switch(ke.r){
-		case ' ':
-			pause ^= 1;
-			break;
+		case KF|1: debugmap ^= 1; pause ^= 1; break;
+		case ' ': pause ^= 1; break;
 		case Kdel: quit(); break;
 		}
 	}
@@ -229,7 +195,7 @@
 static void
 usage(void)
 {
-	fprint(2, "usage: %s [-l port] [-m map] [-n name] [-s scale] [-t speed] [-x netmtpt] [sys]\n", argv0);
+	fprint(2, "usage: %s [-D] [-l port] [-m map] [-n name] [-s scale] [-t speed] [-x netmtpt] [sys]\n", argv0);
 	threadexits("usage");
 }
 
@@ -239,6 +205,7 @@
 	vlong t, t0, dt;
 
 	ARGBEGIN{
+	case 'D': debug = 1; break;
 	case 'l': lport = strtol(EARGF(usage()), nil, 0); break;
 	case 'm': mapname = EARGF(usage()); break;
 	case 'n': progname = EARGF(usage()); break;
--- a/sce/sce.db
+++ b/sce/sce.db
@@ -3,10 +3,10 @@
 resource,vespene gas,0
 attack,fusion cutter,5,1,15
 attack,spines,5,1,22
-obj,scv,32,3,1,1,60,0,5,7,1,50,0,20,fusion cutter,
-obj,drone,32,1,1,1,40,0,5,7,1,50,0,20,spines,
-obj,control,1,8,4,3,1500,1,0,1,10,400,0,1800,,
-obj,hatchery,1,8,4,3,1250,1,0,1,10,300,0,1800,,
+obj,scv,32,3,4,4,60,0,5,7,1,50,0,20,fusion cutter,
+obj,drone,32,1,4,4,40,0,5,7,1,50,0,20,spines,
+obj,control,1,8,16,12,1500,1,0,1,10,400,0,1800,,
+obj,hatchery,1,8,16,12,1250,1,0,1,10,300,0,1800,,
 spawn,control,scv
 spr,scv,1,0
 spr,scv,0x8001,0
--- a/sim.c
+++ b/sim.c
@@ -9,202 +9,133 @@
 int nteam;
 int initres[Nresource], foodcap;
 
-static Lobj vlist = {.lo = &vlist, .lp = &vlist };
+static Mobjl moving0 = {.l = &moving0, .lp = &moving0}, *moving = &moving0;
 
-/* FIXME: networking: recvbuf and sendbuf for accumulating messages to flush
- * to all clients */
-/* FIXME: acceleration, deceleration, turning speed (360°) */
-/* FIXME: minerals: 4 spaces in every direction forbidding cc placement */
-/* FIXME: resource tiles: take 2x1 tiles, drawn on top of rest
- *	-> actual (immutable) object, not terrain (remove resource= from
- *	   terrain shit, move to objects)
- *	-> minerals: min0?.grp, three types depending on abundance, with
- *	   several variants each
- *	-> Geyser.grp (terrain pal?) */
-/* FIXME: rip mineral sprites and implement terrain objects layer */
-/* FIXME: buildings are aligned to map grid, while units are aligned to
- * path grid -> building placement uses Map, not Path */
-/* FIXME: verify that path node centering does in fact work correctly, esp
- * viz blockmap and .subp; if it does, do centering at spawn */
-/* FIXME: fix land spawning to always work if adjacent space can be found */
-/* FIXME: once spawning is fixed, remove race-specific mentions and units from
- * map spec, having only starts instead, resolving to a race's cc and spawning
- * 4 workers by default (marked in db) */
-
-/* FIXME:
- - networking
- 	. server: spawn server + local client (by default)
- 	. client: connect to server
- 	. both initialize a simulation, but only the server modifies state
- - command line: choose whether or not to spawn a server and/or a client
-   (default: both)
- - always spawn a server, always initialize loopback channel to it; client
-   should do the same amount of work but the server always has the last word
-   	. don't systematically announce a port
- - client: join observers on connect
- - program a lobby for both client and server
- 	. server: kick/ban ip's, rate limit, set teams and rules
- 	. master client -> server priviledges
- 	. master client == local client
- 	. if spawning a server only, local client just implements the lobby
- 	  and a console interface, same as lobby, for controlling the server
- 	. client: choose slot
- */
-/* FIXME: db: build tree specification */
-
-static Lobj *
-lalloc(Mobj *mo, ulong sz)
+static Mobjl *
+linkmobj(Mobjl *l, Mobj *mo, Mobjl *p)
 {
-	Lobj *lo, *l;
-
-	lo = emalloc(sz * sizeof *lo);
-	for(l=lo; sz>0; sz--, l++)
-		l->mo = mo;
-	return lo;
+	if(p == nil)
+		p = emalloc(sizeof *p);
+	p->mo = mo;
+	p->l = l->l;
+	p->lp = l;
+	l->l->lp = p;
+	l->l = p;
+	return p;
 }
 
-void
-llink(Lobj *lo, Lobj *lp)
+static void
+unlinkmobj(Mobjl *ml)
 {
-	lo->lo = lp->lo;
-	lo->lp = lp;
-	lp->lo->lp = lo;
-	lp->lo = lo;
+	if(ml == nil || ml->l == nil || ml->lp == nil)
+		return;
+	ml->lp->l = ml->l;
+	ml->l->lp = ml->lp;
+	ml->lp = ml->l = nil;
 }
 
 void
-lunlink(Lobj *lo)
+linktomap(Mobj *mo)
 {
-	lo->lp->lo = lo->lo;
-	lo->lo->lp = lo->lp;
-}
+	Map *m;
 
-static void
-lfree(Lobj *lo)
-{
-	lunlink(lo);
-	free(lo);
+	m = map + mo->y * mapwidth + mo->x;
+	mo->mapp = linkmobj(mo->f & Fair ? m->ml.lp : &m->ml, mo, mo->mapp);
 }
 
 static void
-freepath(Mobj *mo)
+resetcoords(Mobj *mo)
 {
-	if(mo->path == nil)
-		return;
-	free(mo->path);
-	mo->path = mo->pathp = mo->pathe = nil;
-	lunlink(mo->vl);
+	markmobj(mo, 0);
+	mo->subpx = mo->px << Subpxshift;
+	mo->subpy = mo->py << Subpxshift;
+	markmobj(mo, 1);
 }
 
-static void
-nextpath(Mobj *mo)
+static int
+facemobj(Point p, Mobj *mo)
 {
-	int Δθ, vx, vy;
-	double θ, l;
-	Point p;
+	int dx, dy;
+	double vx, vy, θ, d;
 
-	p = *mo->pathp++;
-	vx = p.x - mo->p.x;
-	vy = p.y - mo->p.y;
-	l = sqrt(vx * vx + vy * vy);
-	mo->vx = vx / l;
-	mo->vy = vy / l;
-	mo->vv = mo->o->speed;
-	θ = atan2(mo->vy, mo->vx) + PI / 2;
+	dx = p.x - mo->x;
+	dy = p.y - mo->y;
+	d = sqrt(dx * dx + dy * dy);
+	vx = dx / d;
+	vy = dy / d;
+	mo->u = vx;
+	mo->v = vy;
+	θ = atan2(vy, vx) + PI / 2;
 	if(θ < 0)
 		θ += 2 * PI;
 	else if(θ >= 2 * PI)
 		θ -= 2 * PI;
-	Δθ = (θ / (2*PI) * 360) / (90. / (Nrot/4)) - mo->θ;
+	return (θ / (2 * PI) * 360) / (90. / (Nrot / 4));
+}
+
+static void
+freemove(Mobj *mo)
+{
+	unlinkmobj(mo->movingp);
+	mo->pathp = nil;
+	mo->pics = &mo->o->pidle;
+	resetcoords(mo);
+}
+
+static void
+nextmove(Mobj *mo)
+{
+	int Δθ;
+
+	resetcoords(mo);
+	Δθ = facemobj(*mo->pathp, mo) - mo->θ;
 	if(Δθ <= -Nrot / 2)
 		Δθ += Nrot;
 	else if(Δθ >= Nrot / 2)
 		Δθ -= Nrot;
 	mo->Δθ = Δθ;
+	mo->speed = mo->o->speed;
 }
 
 static int
-repath(Mobj *mo, Point *p)
+repath(Point p, Mobj *mo)
 {
-	freepath(mo);
-	if(findpath(mo, p) < 0){
-		fprint(2, "repath: move to %d,%d: %r\n", p->x, p->y);
+	freemove(mo);
+	mo->target = p;
+	if(findpath(p, mo) < 0){
+		mo->θ = facemobj(p, mo);
 		return -1;
 	}
-	if(mo->vl == nil)
-		mo->vl = lalloc(mo, 1);
-	llink(mo->vl, vlist.lp);
+	mo->movingp = linkmobj(moving, mo, mo->movingp);
+	mo->pathp = mo->paths;
 	mo->pics = mo->o->pmove.p != nil ? &mo->o->pmove : &mo->o->pidle;
-	nextpath(mo);
+	nextmove(mo);
 	return 0;
 }
 
-static int
-canmove(int x, int y, Mobj **op)
-{
-	/* FIXME: attempt to move in the given direction even if impassible */
-	USED(x, y, op);
-	return 1;
-}
-
 int
-move(int x, int y, Mobj **op)
+moveone(Point p, Mobj *mo, Mobj *block)
 {
-	Mobj *mo, **mp;
-	Point p;
-
-	if(!canmove(x, y, op))
+	if(mo->o->speed == 0){
+		dprint("move: obj %s can't move\n", mo->o->name);
 		return -1;
-	for(mp=op; (mo=*mp)!=nil; mp++){
-		/* FIXME: offset sprite size */
-		p = (Point){x & ~Tlsubmask, y & ~Tlsubmask};
-		if(mo->o->speed != 0 && repath(mo, &p) < 0)
-			continue;
 	}
+	setgoal(&p, mo, block);
+	if(repath(p, mo) < 0){
+		dprint("move to %d,%d: %r\n", p.x, p.y);
+		return -1;
+	}
 	return 0;
 }
 
-/* FIXME: we're not actually spawning a unit at a location, are we? it's a
- * unit or structure that spawns a unit, the placement is determined based on
- * available space -> the spawning mechanism works the same; initial units are
- * always the same and are spawned in the same manner */
-static int
-canspawn(Map *m, Mobj *mo)
-{
-	Point p;
-
-	/* FIXME: find space, unless not a unit */
-	p.x = m->tx * (Tlwidth / Tlsubwidth);
-	p.y = m->ty * (Tlheight / Tlsubheight);
-	if(isblocked(&p, mo)){
-		werrstr("no available space");
-		return 0;
-	}
-	return 1;
-}
-
 int
-spawn(Map *m, Obj *o, int n)
+spawn(int x, int y, Obj *o, int n)
 {
 	Mobj *mo;
 
-	mo = emalloc(sizeof *mo);
-	mo->o = o;
-	mo->p.x = m->x;
-	mo->p.y = m->y;
-	if(!canspawn(m, mo)){
-		free(mo);
+	if((mo = mapspawn(x, y, o)) == nil)
 		return -1;
-	}
-	mo->x = m->x / Tlsubwidth;
-	mo->y = m->y / Tlsubheight;
 	mo->team = n;
-	mo->f = o->f;
-	mo->hp = o->hp;
-	mo->zl = lalloc(mo, 1);
-	llink(mo->zl, &m->lo);
-	mo->blk = lalloc(mo, mo->o->w * (Tlwidth / Tlsubwidth) * mo->o->h * (Tlheight / Tlsubheight));
-	setblk(mo, 0);
 	mo->pics = &mo->o->pidle;
 	if(mo->f & Fbuild)
 		team[n].nbuild++;
@@ -218,7 +149,10 @@
 {
 	int Δθ;
 
-	Δθ = mo->Δθ < 0 ? -1 : 1;
+	if(mo->Δθ < 0)
+		Δθ = mo->Δθ < -4 ? -4 : mo->Δθ;
+	else
+		Δθ = mo->Δθ > 4 ? 4 : mo->Δθ;
 	mo->θ = mo->θ + Δθ & Nrot - 1;
 	mo->Δθ -= Δθ;
 }
@@ -226,111 +160,132 @@
 static int
 trymove(Mobj *mo)
 {
-	int Δr, sx, sy;
-	Point subΔr, subp, p, pp;
+	int x, y, sx, sy, Δx, Δy, Δu, Δv, Δrx, Δry, Δpx, Δpy;
 
-	subΔr.x = mo->vx * mo->vv * (1 << Tlshift);
-	subΔr.y = mo->vy * mo->vv * (1 << Tlshift);
-	sx = subΔr.x < 0 ? -1 : 1;
-	sy = subΔr.y < 0 ? -1 : 1;
-	subΔr.x *= sx;
-	subΔr.y *= sy;
-	Δr = sx * (mo->pathp[-1].x - mo->p.x << Tlshift) - mo->subp.x;
-	if(subΔr.x > Δr)
-		subΔr.x = Δr;
-	Δr = sy * (mo->pathp[-1].y - mo->p.y << Tlshift) - mo->subp.y;
-	if(subΔr.y > Δr)
-		subΔr.y = Δr;
-	while(subΔr.x > 0 || subΔr.y > 0){
-		p = mo->p;
-		subp = mo->subp;
-		if(subΔr.x > 0){
-			Δr = (Tlsubwidth << Tlshift) - subp.x;
-			if(Δr <= subΔr.x){
-				p.x += sx * Tlsubwidth;
-				subp.x = 0;
-			}else{
-				subp.x += subΔr.x;
-				p.x += sx * (subp.x >> Tlshift);
-				subp.x &= (1 << Tlshift) - 1;
-			}
-			subΔr.x -= Δr;
+	markmobj(mo, 0);
+	sx = mo->subpx;
+	sy = mo->subpy;
+	Δu = mo->u * (1 << Subpxshift);
+	Δv = mo->v * (1 << Subpxshift);
+	Δx = abs(Δu);
+	Δy = abs(Δv);
+	Δrx = Δx * mo->speed;
+	Δry = Δy * mo->speed;
+	Δpx = abs((mo->pathp->x * Tlsubwidth << Subpxshift) - sx);
+	Δpy = abs((mo->pathp->y * Tlsubwidth << Subpxshift) - sy);
+	if(Δpx < Δrx)
+		Δrx = Δpx;
+	if(Δpy < Δry)
+		Δry = Δpy;
+	while(Δrx > 0 || Δry > 0){
+		x = mo->x;
+		y = mo->y;
+		if(Δrx > 0){
+			sx += Δu;
+			Δrx -= Δx;
+			if(Δrx < 0)
+				sx += mo->u < 0 ? -Δrx : Δrx;
+			x = (sx >> Subpxshift) + ((sx & Subpxmask) != 0);
+			x /= Tlsubwidth;
 		}
-		if(subΔr.y > 0){
-			Δr = (Tlsubheight << Tlshift) - subp.y;
-			if(Δr <= subΔr.y){
-				p.y += sy * Tlsubheight;
-				subp.y = 0;
-			}else{
-				subp.y += subΔr.y;
-				p.y += sy * (subp.y >> Tlshift);
-				subp.y &= (1 << Tlshift) - 1;
-			}
-			subΔr.y -= Δr;
+		if(Δry > 0){
+			sy += Δv;
+			Δry -= Δy;
+			if(Δry < 0)
+				sy += mo->v < 0 ? -Δry : Δry;
+			y = (sy >> Subpxshift) + ((sy & Subpxmask) != 0);
+			y /= Tlsubwidth;
 		}
-		pp.x = p.x / Tlsubwidth;
-		pp.y = p.y / Tlsubheight;
-		if(mo->x != pp.x || mo->y != pp.y){
-			if(isblocked(&pp, mo))
-				return -1;
-			setblk(mo, 1);
-			mo->x = pp.x;
-			mo->y = pp.y;
-			setblk(mo, 0);
+		if(isblocked(x, y, mo->o))
+			goto end;
+		/* disallow corner coasting */
+		if(x != mo->x && y != mo->y
+		&& (isblocked(x, mo->y, mo->o) || isblocked(mo->x, y, mo->o))){
+			dprint("detected corner coasting %d,%d vs %d,%d\n",
+				x, y, mo->x, mo->y);
+			goto end;
 		}
-		mo->subp = subp;
-		mo->p = p;
+		mo->subpx = sx;
+		mo->subpy = sy;
+		mo->px = sx >> Subpxshift;
+		mo->py = sy >> Subpxshift;
+		mo->x = mo->px / Tlsubwidth;
+		mo->y = mo->py / Tlsubheight;
 	}
+	markmobj(mo, 1);
 	return 0;
+end:
+	werrstr("trymove: can't move to %d,%d", x, y);
+	mo->subpx = mo->px << Subpxshift;
+	mo->subpy = mo->py << Subpxshift;
+	markmobj(mo, 1);
+	return -1;
 }
 
 static void
 stepmove(Mobj *mo)
 {
-	Point pp;
-	Map *m, *m´;
+	int r, n;
 
-	/* FIXME: constant turn speed for now, but is it always so? */
-	/* FIXME: too slow */
+	n = 0;
+restart:
+	n++;
 	if(mo->Δθ != 0){
 		tryturn(mo);
 		return;
 	}
-	m = map + mo->y / (Tlheight / Tlsubheight) * mapwidth
-		+ mo->x / (Tlwidth / Tlsubwidth);
-	/* FIXME: update speed (based on range from src and to target?) */
-	/* FIXME: update speed: accel, decel, turning speed */
-	if(trymove(mo) < 0){
-		mo->vv = 0.0;
-		pp = mo->pathe[-1];
-		fprint(2, "mo %#p blocked at %d,%d goal %d,%d\n", mo, mo->x * Tlsubwidth, mo->y * Tlsubheight, mo->pathp[-1].x, mo->pathp[-1].y);
-		/* FIXME: if path now blocked, move towards target anyway */
-		repath(mo, &pp);
-		return;
+	unlinkmobj(mo->mapp);
+	r = trymove(mo);
+	linktomap(mo);
+	if(r < 0){
+		if(n > 1){
+			fprint(2, "stepmove: %s %#p bug inducing infinite loop!\n",
+				mo->o->name, mo);
+			return;
+		}
+		dprint("stepmove: failed to move: %r\n");
+		if(repath(mo->target, mo) < 0){
+			dprint("stepmove: %s %#p moving towards target: %r\n",
+				mo->o->name, mo);
+			return;
+		}
+		goto restart;
 	}
-	if(mo->p.x == mo->pathp[-1].x && mo->p.y == mo->pathp[-1].y){
-		if(mo->pathp < mo->pathe)
-			nextpath(mo);
-		else{
-			freepath(mo);
-			mo->pics = &mo->o->pidle;
+	if(mo->x == mo->pathp->x && mo->y == mo->pathp->y){
+		mo->pathp++;
+		if(mo->pathp < mo->pathe){
+			nextmove(mo);
+			return;
+		}else if(mo->x == mo->target.x && mo->y == mo->target.y){
+			mo->npatherr = 0;
+			freemove(mo);
+			return;
 		}
+		dprint("stepmove: %s %#p reached final node, but not target\n",
+			mo->o->name, mo);
+		if(mo->goalblocked && isblocked(mo->target.x, mo->target.y, mo->o)){
+			dprint("stepmove: %s %#p goal still blocked, stopping\n",
+				mo->o->name, mo);
+			freemove(mo);
+			return;
+		}
+		if(mo->npatherr++ > 1
+		|| repath(mo->target, mo) < 0){
+			dprint("stepmove: %s %#p trying to find target: %r\n",
+				mo->o->name, mo);
+			mo->npatherr = 0;
+			freemove(mo);
+		}
 	}
-	m´ = map + mo->y / (Tlheight / Tlsubheight) * mapwidth
-		+ mo->x / (Tlwidth / Tlsubwidth);
-	if(m != m´){
-		lunlink(mo->zl);
-		llink(mo->zl, &m´->lo);
-	}
 }
 
 void
 stepsim(void)
 {
-	Lobj *lo;
+	Mobjl *ml, *oml;
 
-	for(lo=vlist.lo; lo!=&vlist; lo=lo->lo)
-		stepmove(lo->mo);
+	for(oml=moving->l, ml=oml->l; oml!=moving; oml=ml, ml=ml->l)
+		stepmove(oml->mo);
 }
 
 static void
--- /dev/null
+++ b/util.c
@@ -1,0 +1,76 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include "dat.h"
+#include "fns.h"
+
+int debug;
+
+int
+max(int a, int b)
+{
+	return a > b ? a : b;
+}
+
+int
+min(int a, int b)
+{
+	return a < b ? a : b;
+}
+
+void
+dprint(char *fmt, ...)
+{
+	char s[256];
+	va_list arg;
+
+	if(!debug)
+		return;
+	va_start(arg, fmt);
+	vseprint(s, s+sizeof s, fmt, arg);
+	va_end(arg);
+	fprint(2, "%s\n", s);
+}
+
+char *
+estrdup(char *s)
+{
+	if((s = strdup(s)) == nil)
+		sysfatal("estrdup: %r");
+	setmalloctag(s, getcallerpc(&s));
+	return s;
+}
+
+void *
+erealloc(void *p, ulong n, ulong oldn)
+{
+	if((p = realloc(p, n)) == nil)
+		sysfatal("realloc: %r");
+	setrealloctag(p, getcallerpc(&p));
+	memset((uchar *)p + oldn, 0, n - oldn);
+	return p;
+}
+
+void *
+emalloc(ulong n)
+{
+	void *p;
+
+	if((p = mallocz(n, 1)) == nil)
+		sysfatal("emalloc: %r");
+	setmalloctag(p, getcallerpc(&n));
+	return p;
+}
+
+vlong
+flen(int fd)
+{
+	vlong l;
+	Dir *d;
+
+	if((d = dirfstat(fd)) == nil) 
+		sysfatal("flen: %r");
+	l = d->length;
+	free(d);
+	return l;
+}