"use strict";

var DEFINE_SPRITE_MOVE_RESULT_FLAGS = {
	"EJECTED_H" 	: 1, // ejected by a wall while moving horizontally
	"EJECTED_V" 	: 2, // ejected by a wall while moving vertically
	"WRAPPED_H" 	: 4, // x coordinate wrapped to the opposite side of the screen when moving horizontally
	"WRAPPED_V" 	: 8, // y coordinate wrapped to the opposite side of the screen when moving vertically
	"CAPPED_H" 		: 16, // ejected by the edge of the room when moving horizontally
	"CAPPED_V" 		: 32, // ejected by the edge of the room when moving vertically
	"OFF_LEFT"		: 64, // sprite is mechanically off screen left (x+box_right<0)
	"OFF_RIGHT"		: 128, // sprite is mechanically off screen right (x+box_left>=room.pixel_width)
	"OFF_TOP"		: 256, // sprite is mechanically off screen top (y+box_bottom<0)
	"OFF_BOTTOM"	: 512, // sprite is mechanically off screenbottom (y_box_top>=room.pixel_height)
	"EJECTED_SH"	: 1024, // ejected from at least one solid sprite while moving horizontally
	"EJECTED_SV"	: 2048, // ejected from at least one solid sprite while moving vertically
	"CRUSHED"		: 4096, // would would have been ejected from a solid sprite but there was no room
	"COLLIDING_S"	: 8192 // currently colliding with at least one non-solid sprite
};
var DEFINE_SPRITE_MOVE_RESULT_EJECTED_H = (
		DEFINE_SPRITE_MOVE_RESULT_FLAGS.EJECTED_H | 
		DEFINE_SPRITE_MOVE_RESULT_FLAGS.EJECTED_SH |
		DEFINE_SPRITE_MOVE_RESULT_FLAGS.CAPPED_H);
var DEFINE_SPRITE_MOVE_RESULT_EJECTED_V = (
		DEFINE_SPRITE_MOVE_RESULT_FLAGS.EJECTED_V | 
		DEFINE_SPRITE_MOVE_RESULT_FLAGS.EJECTED_SV |
		DEFINE_SPRITE_MOVE_RESULT_FLAGS.CAPPED_V);
var DEFINE_SPRITE_MOVE_RESULT_EJECTED_S = (
		DEFINE_SPRITE_MOVE_RESULT_FLAGS.EJECTED_SH |
		DEFINE_SPRITE_MOVE_RESULT_FLAGS.EJECTED_SV);

var DEFINE_SPRITE_MOVE_CONSTRAINT = {
	"NONE"	: 0,
	"WRAP"	: 1,
	"CAP"	: 2
};

var DEFINE_SPRITE_COLLISION_FLAGS = {
	"GROUND"			: 1, // this sprite can collide with room walls when it moves
	"WATER"				: 2, // this sprite can collide with room water when it moves
	"AIR"				: 4, // this sprite can collide with room air when it moves
	"SPRITES_ACTIVE"	: 8, // this sprite can collide with other sprites when it moves (if they are flagged SPRITES_PASSIVE)
	"SPRITES_PASSIVE"	: 16, // other sprites can collide with this sprite when they move (if they are flagged SPRITES_ACTIVE)
	"SOLID"				: 32  // iff this sprite is also flagged SPRITES_PASSIVE, other SPRITES_ACTIVE sprites moving into it will be ejected
};
var DEFINE_SPRITE_COLLIDES_TERRAIN = (DEFINE_SPRITE_COLLISION_FLAGS.GROUND | DEFINE_SPRITE_COLLISION_FLAGS.WATER | DEFINE_SPRITE_COLLISION_FLAGS.AIR);

/* support class for encapsulating the results of moving a sprite */
var SpriteMoveResult = function(flags, ejectedByH, ejectedByV, ejectedBy, collidingWith){
	this.flags = 0;
	this.ejections = [];
	this.ejections_h = [];
	this.ejections_v = [];
	this.collisions = [];

	this._i=0; // not worth a full _heap

	this.init(flags, ejectedByH, ejectedByV, ejectedBy, collidingWith);
}
SpriteMoveResult.prototype.init = function(flags, ejectedByH, ejectedByV, ejectedBy, collidingWith){
	this.ejections.length=0;
	this.ejections_h.length=0;
	this.ejections_v.length=0;
	this.collisions.length=0;

	// an | of DEFINE_SPRITE_MOVE_RESULT_FLAGS describing any events encountered when moving the sprite
	this.flags = flags ? flags : 0;

	// array of sprites which this sprite was ejected by, if any
	//this.ejections = ejectedBy;
	for (this._i=0; ejectedBy && this._i<ejectedBy.length; ++this._i){
		this.ejections[this._i]=ejectedBy[this._i];
	}
	//this.ejections_h = ejectedByH;
	for (this._i=0; ejectedByH && this._i<ejectedByH.length; ++this._i){
		this.ejections_h[this._i]=ejectedByH[this._i];
	}
	//this.ejections_v = ejectedByV;
	for (this._i=0; ejectedByV && this._i<ejectedByV.length; ++this._i){
		this.ejections_v[this._i]=ejectedByV[this._i];
	}

	// array of sprites which this sprite is colliding with, if any
	//this.collisions = collidingWith;
	for (this._i=0; collidingWith && this._i<collidingWith.length; ++this._i){
		this.collisions[this._i]=collidingWith[this._i];
	}
}

/*
* baseline container class for any arbitrary game sprite
*
* tilesheet: the sheet containing this sprite's frame images.  May be null for invisible sprites.
* name: convenient value to check a sprite's identity for itneractions.  May be null.
*
* After creating a sprite, 
* you MUST set its map_room in order for its move() and collision resolution to work.
* its max_step (for slope traversal) and collision bounding box will be given intelligent defaults
*	but it is recommended to override them.
*	max_step may be set directly, the boundig box should be set with setBoundingBox()
* its collision_flags and constraints.x/constraints.y have legal defaults, but are intended to be set manually.
* feel free to set its frame and name directly, although this is not requred.
* Setting its x and y directly is valid, but will not invoke collision detection.
* 
*/
var Sprite = function(tilesheet,name){
	this.x=0;
	this.y=0;
	this.frame=0;
	this.tile_sheet=tilesheet;
	this.name=name;
	this.visible=true;

	this.max_step = 1; // how many pixels perpendicular to an attempted movement will be auto-traversed without ejection?

	this.collision_flags = DEFINE_SPRITE_COLLISION_FLAGS.GROUND | DEFINE_SPRITE_COLLISION_FLAGS.SPRITES_ACTIVE;
	this.constraints = {};
	this.constraints.x = DEFINE_SPRITE_MOVE_CONSTRAINT.WRAP;
	this.constraints.y = DEFINE_SPRITE_MOVE_CONSTRAINT.WRAP;

	this.map_room = null;
	this.game_object = null;

	// structure preallocation
	this._heap = {};
	this._heap.getCollisions = {};
	this._heap.getCollisions.penetration = {x:0, y:0};

	this._heap.move = {};
	this._heap.move.penetration = {x:0, y:0};
	this._heap.move.influenced = [];
	this._heap.move.reSprites = [];
	this._heap.move.collidingSprites = [];
	this._heap.move.ejectingSprites = [];
	this._heap.move.ejectingSpritesH = [];
	this._heap.move.ejectingSpritesV = [];

	this._heap._traverseTerrain = {};

	this.top_left={x:0, y:0};
	this.top_right={x:0, y:0};
	this.bottom_left={x:0, y:0};
	this.bottom_right={x:0, y:0};
	this.center={x:0, y:0};

	// init by routines
	this.setBoundingBox(
		0, 
		0, 
		(this.tile_sheet) ? this.tile_sheet.tile_pixel_width : 0, 
		(this.tile_sheet) ? this.tile_sheet.tile_pixel_height : 0);
};

	// if false: 
	// ejection from SOLID sprites will crush if ejected into solid wall,
	// but will not re-test to see if ejection was into another SOLID sprite
	// (no infinite recursion, but also allows for zips through SOLID sprites)
	// if true:
	// performs an additional check after the SOLID sprite eject loop; if
	// still inside a SOLID sprite, sets the crushed result flag.
	// (zips not possible, but fast-moving SOLID sprites e.g. logswing become
	// lethal if the player isn't pushed fully out of the way)
	// MUST be true during BossRoom fight; remainder of game... appropriateness TBD.
	Sprite.AGGRESSIVE_CRUSH = true;

	Sprite.prototype.drawIntoCanvas = function(canvas){
		if (this.tile_sheet && this.visible){
			this.tile_sheet.drawIntoCanvas(canvas,this.frame,this.x,this.y);
		}
	};

	Sprite.prototype.setBoundingBox = function(left, top, right, bottom){
		this.box_left=left;
		this.box_right=right;
		this.box_top=top;
		this.box_bottom=bottom;

		this.top_left.x=this.box_left;
		this.top_left.y=this.box_top;

		this.top_right.x=this.box_right
		this.top_right.y=this.box_top;

		this.bottom_left.x=this.box_left;
		this.bottom_left.y=this.box_bottom;

		this.bottom_right.x=this.box_right;
		this.bottom_right.y=this.box_bottom;

		this.center.x=(this.box_left+this.box_right)>>1;
		this.center.y=(this.box_top+this.box_bottom)>>1;
	};

	Sprite.prototype.testCollision = function(other,dx,dy,  penetration){
		penetration.x=0;
		penetration.y=0;

		// NOTE: for now, at least, ignore collisions with wrapped sprites along room bounds
		if (this.x+this.box_left <= other.x+other.box_right && this.x+this.box_right >= other.x+other.box_left){
			if (this.y+this.box_top <= other.y+other.box_bottom && this.y+this.box_bottom >= other.y+other.box_top){
				penetration.x = (dx>0) 
					? (this.x+this.box_right) - (other.x+other.box_left) + 1
					: (dx<0) 
						? (other.x+other.box_right) - (this.x+this.box_left) + 1
						//: Math.min( // if we weren't moving on collision entry, assume whatever hit us did so least-invasively
						//	Math.abs((this.x+this.box_right) - (other.x+other.box_left)),
						//	Math.abs((other.x+other.box_right) - (this.x+this.box_left))
						//)+1;
						: (this.center.x<other.center.x) // if we weren't moving on collision entry, assume whatever hit us did so from the side it's currently on
							? (this.x+this.box_right) - (other.x+other.box_left) + 1
							: (other.x+other.box_right) - (this.x+this.box_left) + 1;

				penetration.y = (dy>0) 
					? (this.y+this.box_bottom) - (other.y+other.box_top) + 1
					: (dy<0)
						? (other.y+other.box_bottom) - (this.y+this.box_top) + 1
						//: Math.min( // if we weren't moving on collision entry, assume whatever hit us did so least-invasively
						//	Math.abs((this.y+this.box_bottom) - (other.y+other.box_top)),
						//	Math.abs((other.y+other.box_bottom) - (this.y+this.box_top))
						//)+1;
						: (this.center.y<other.center.y) // if we weren't moving on collision entry, assume whatever hit us did so from the side it's currently on
							? (this.y+this.box_bottom) - (other.y+other.box_top) + 1
							: (other.y+other.box_bottom) - (this.y+this.box_top) + 1;
			}
		}
	};

	Sprite.prototype.isTouchingSprite = function(other){
		// NOTE: for now, at least, ignore collisions with wrapped sprites along room bounds
		if (this.x+this.box_left <= other.x+other.box_right && this.x+this.box_right >= other.x+other.box_left){
			if (this.y+this.box_top <= other.y+other.box_bottom && this.y+this.box_bottom >= other.y+other.box_top){
				return true;
			}
		}
		return false;
	};

	// quickly builds a list of all passive-collidable sprites touching this one
	// without doing any moving/ejection.
	Sprite.prototype.getCollisions = function(collidingSprites,append,force){
		if (!append) collidingSprites.length=0;
		if ((this.collision_flags & DEFINE_SPRITE_COLLISION_FLAGS.SPRITES_ACTIVE) || force){
			for (this._heap.getCollisions.s=0; this._heap.getCollisions.s<this.map_room.sprites.length; this._heap.getCollisions.s++){
				this._heap.getCollisions.sprite = this.map_room.sprites[this._heap.getCollisions.s];
				if (this._heap.getCollisions.sprite!=this && (this._heap.getCollisions.sprite.collision_flags & DEFINE_SPRITE_COLLISION_FLAGS.SPRITES_PASSIVE)){
					this.testCollision(this._heap.getCollisions.sprite,1,1,  this._heap.getCollisions.penetration);
					if (this._heap.getCollisions.penetration.x!=0 || this._heap.getCollisions.penetration.y!=0 && (!append || collidingSprites.indexOf(this._heap.getCollisions.sprite)==-1)){
						collidingSprites.push(this._heap.getCollisions.sprite);
					}
				}
			}
		}
	};

	// internal helper for colliding with terrain and doing slope ugliness
	Sprite.prototype._traverseTerrain = function(dx,dy){
		this._heap._traverseTerrain.resultFlags = 0;

		this._heap._traverseTerrain.featureH = this.map_room.palette.min_feature_height;
		this._heap._traverseTerrain.featureW = this.map_room.palette.min_feature_width;


		if (this.collision_flags & DEFINE_SPRITE_COLLIDES_TERRAIN){
			// check the relevant corners in the distance to be travelled before applying motion
			// 1. check both corners in direction of motion.  If clear, good.  If not,
			// 2. if only one corner collides, determine would-be penetration.
			// from the point inside the collision, determine perpendicular distance back to open space
			// if this distance is less than max_step*penetration, assess clearance on both corners in this new direction
			// (NOTE: current implementation only checks clearance on leading corner)
			// if both clear, eject normal to original motion (traverse the slope)
			// else, eject reverse to motion.

			this._heap._traverseTerrain.solidTerrain = 0;
			this._heap._traverseTerrain.clearTerrain=0;
			if (this.collision_flags & DEFINE_SPRITE_COLLISION_FLAGS.GROUND){
				this._heap._traverseTerrain.solidTerrain |= DEFINE_MAP_TERRAIN_TYPES.GROUND;
			}
			else{
				this._heap._traverseTerrain.clearTerrain |= DEFINE_MAP_TERRAIN_TYPES.GROUND;
			}
			if (this.collision_flags & DEFINE_SPRITE_COLLISION_FLAGS.WATER){
				this._heap._traverseTerrain.solidTerrain |= DEFINE_MAP_TERRAIN_TYPES.WATER;
			}
			else{
				this._heap._traverseTerrain.clearTerrain |= DEFINE_MAP_TERRAIN_TYPES.WATER;
			}
			if (this.collision_flags & DEFINE_SPRITE_COLLISION_FLAGS.AIR){
				this._heap._traverseTerrain.solidTerrain |= DEFINE_MAP_TERRAIN_TYPES.AIR;
			}
			else{
				this._heap._traverseTerrain.clearTerrain |= DEFINE_MAP_TERRAIN_TYPES.AIR;
			}

			this._heap._traverseTerrain.stepHeight = 0;
			this._heap._traverseTerrain.stepClearance = 0;
			this._heap._traverseTerrain.stepIter = 0;
			this._heap._traverseTerrain.stepDelta = 0;

			if (dx!=0){
				this._heap._traverseTerrain.topCorner = (dx<0) ? this.top_left : this.top_right;
				this._heap._traverseTerrain.bottomCorner = (dx<0) ? this.bottom_left : this.bottom_right;

				this._heap._traverseTerrain.solidFromTop = this.map_room.findHorizontally(
					this._heap._traverseTerrain.solidTerrain,
					this.x+this._heap._traverseTerrain.topCorner.x, this.y+this._heap._traverseTerrain.topCorner.y,
					dx,
					Math.abs(dx),
					this.constraints.x==DEFINE_SPRITE_MOVE_CONSTRAINT.WRAP);

				this._heap._traverseTerrain.solidFromBottom = this.map_room.findHorizontally(
					this._heap._traverseTerrain.solidTerrain,
					this.x+this._heap._traverseTerrain.bottomCorner.x, this.y+this._heap._traverseTerrain.bottomCorner.y,
					dx,
					Math.abs(dx),
					this.constraints.x==DEFINE_SPRITE_MOVE_CONSTRAINT.WRAP);

				this._heap._traverseTerrain.solidFromMiddle = -1;
				for (this._heap._traverseTerrain.scanY = this._heap._traverseTerrain.topCorner.y+this._heap._traverseTerrain.featureH; this._heap._traverseTerrain.scanY<this._heap._traverseTerrain.bottomCorner.y; this._heap._traverseTerrain.scanY+=this._heap._traverseTerrain.featureH){
					this._heap._traverseTerrain.solidFromMiddle = Math.max(
						this._heap._traverseTerrain.solidFromMiddle,
						this.map_room.findHorizontally(
							this._heap._traverseTerrain.solidTerrain,
							this.x+this._heap._traverseTerrain.topCorner.x, this.y+this._heap._traverseTerrain.scanY,
							dx,
							Math.abs(dx),
							this.constraints.x==DEFINE_SPRITE_MOVE_CONSTRAINT.WRAP
						)
					);
				}
				
				if ((this._heap._traverseTerrain.solidFromTop>=0 && this._heap._traverseTerrain.solidFromBottom>=0) || this._heap._traverseTerrain.solidFromMiddle>=0){
					// probably not a slope.  Just eject.
					// NOTE: this could cause objects moving more than 1px/frame along a slope
					// to not get as near a wall as they might otherwise
					this._heap._traverseTerrain.firstObstruction = Math.min(
						this._heap._traverseTerrain.solidFromTop>=0 ? this._heap._traverseTerrain.solidFromTop : Math.abs(dx),
						this._heap._traverseTerrain.solidFromBottom>=0 ? this._heap._traverseTerrain.solidFromBottom : Math.abs(dx),
						this._heap._traverseTerrain.solidFromMiddle>=0 ? this._heap._traverseTerrain.solidFromMiddle : Math.abs(dx)
						);
					//if (this._heap._traverseTerrain.solidFromTop>=0 && this._heap._traverseTerrain.solidFromBottom>=0){
					//	this._heap._traverseTerrain.firstObstruction = Math.min(this._heap._traverseTerrain.solidFromTop,this._heap._traverseTerrain.solidFromBottom);
					//}
					//else{
					//	this._heap._traverseTerrain.firstObstruction = this._heap._traverseTerrain.solidFromMiddle;
					//}
					this.x += (dx>0) ? (this._heap._traverseTerrain.firstObstruction-1) : (1-this._heap._traverseTerrain.firstObstruction);
					this._heap._traverseTerrain.resultFlags |= DEFINE_SPRITE_MOVE_RESULT_FLAGS.EJECTED_H;
				}
				else if (this._heap._traverseTerrain.solidFromBottom>0){
					// bottom corner obstruction; attempt to traverse up a slope in 1px increments
					this._heap._traverseTerrain.stepDelta = (dx>0) ? 1 : -1;
					// move as far as we can unobstructed
					this.x += (this._heap._traverseTerrain.solidFromBottom-1) * this._heap._traverseTerrain.stepDelta;
					// move the rest by incremental test
					this._heap._traverseTerrain.totalSteps = Math.abs(dx)-(this._heap._traverseTerrain.solidFromBottom-1);
					for (this._heap._traverseTerrain.stepIter=0; this._heap._traverseTerrain.stepIter<this._heap._traverseTerrain.totalSteps; this._heap._traverseTerrain.stepIter++){
						// if we move by only a 1px increment, see how far above our baseline we find clear space (up to the max height we can step)
						this._heap._traverseTerrain.stepHeight = this.map_room.findVertically(
							this._heap._traverseTerrain.clearTerrain,
							this.x+this._heap._traverseTerrain.bottomCorner.x+this._heap._traverseTerrain.stepDelta, this.y+this._heap._traverseTerrain.bottomCorner.y,
							-1,
							this.max_step,
							this.constraints.y==DEFINE_SPRITE_MOVE_CONSTRAINT.WRAP);
						// if there's clear space that we could step up onto, see if we have the headroom
						if (this._heap._traverseTerrain.stepHeight>=0){
							this._heap._traverseTerrain.stepClearance = this.map_room.findVertically(
								this._heap._traverseTerrain.solidTerrain,
								this.x+this._heap._traverseTerrain.topCorner.x+this._heap._traverseTerrain.stepDelta, this.y+this._heap._traverseTerrain.topCorner.y,
								-1,
								this._heap._traverseTerrain.stepHeight,
								this.constraints.y==DEFINE_SPRITE_MOVE_CONSTRAINT.WRAP);
							if (this._heap._traverseTerrain.stepClearance<0){
								// we can actually climb this step!  Move horizontally, eject vertically.
								this.x += this._heap._traverseTerrain.stepDelta;
								this.y -= this._heap._traverseTerrain.stepHeight;
								this._heap._traverseTerrain.resultFlags |= DEFINE_SPRITE_MOVE_RESULT_FLAGS.EJECTED_V;
							}
							else{
								// no clearance: eject
								this._heap._traverseTerrain.resultFlags |= DEFINE_SPRITE_MOVE_RESULT_FLAGS.EJECTED_H;
								this._heap._traverseTerrain.stepIter = this._heap._traverseTerrain.totalSteps; // cut the loop
							}
						}
						else{
							// step too high : eject
							this._heap._traverseTerrain.resultFlags |= DEFINE_SPRITE_MOVE_RESULT_FLAGS.EJECTED_H;
							this._heap._traverseTerrain.stepIter = this._heap._traverseTerrain.totalSteps; // cut the loop
						}
						
					}
				}
				else if (this._heap._traverseTerrain.solidFromTop>0){
					// top corner obstruction; attempt to traverse down a slope in 1px increments
					this._heap._traverseTerrain.stepDelta = (dx>0) ? 1 : -1;
					// move as far as we can unobstructed
					this.x += (this._heap._traverseTerrain.solidFromTop-1) * this._heap._traverseTerrain.stepDelta;
					// move the rest by incremental test
					this._heap._traverseTerrain.totalSteps = Math.abs(dx)-(this._heap._traverseTerrain.solidFromTop-1);
					for (this._heap._traverseTerrain.stepIter=0; this._heap._traverseTerrain.stepIter<this._heap._traverseTerrain.totalSteps; this._heap._traverseTerrain.stepIter++){
						// if we move by only a 1px increment, see how far below our head we find clear space (up to the max height we can step)
						this._heap._traverseTerrain.stepHeight = this.map_room.findVertically(
							this._heap._traverseTerrain.clearTerrain,
							this.x+this._heap._traverseTerrain.topCorner.x+this._heap._traverseTerrain.stepDelta, this.y+this._heap._traverseTerrain.topCorner.y,
							1,
							this.max_step,
							this.constraints.y==DEFINE_SPRITE_MOVE_CONSTRAINT.WRAP);
						// if there's clear space that we could step down onto, see if we have the legroom
						if (this._heap._traverseTerrain.stepHeight>=0){
							this._heap._traverseTerrain.stepClearance = this.map_room.findVertically(
								this._heap._traverseTerrain.solidTerrain,
								this.x+this._heap._traverseTerrain.bottomCorner.x+this._heap._traverseTerrain.stepDelta, this.y+this._heap._traverseTerrain.bottomCorner.y,
								1,
								this._heap._traverseTerrain.stepHeight,
								this.constraints.y==DEFINE_SPRITE_MOVE_CONSTRAINT.WRAP);
							if (this._heap._traverseTerrain.stepClearance<0){
								// we can actually climb this step!  Move horizontally, eject vertically.
								this.x += this._heap._traverseTerrain.stepDelta;
								this.y += this._heap._traverseTerrain.stepHeight;
								this._heap._traverseTerrain.resultFlags |= DEFINE_SPRITE_MOVE_RESULT_FLAGS.EJECTED_V;
							}
							else{
								// no clearance: eject
								this._heap._traverseTerrain.resultFlags |= DEFINE_SPRITE_MOVE_RESULT_FLAGS.EJECTED_H;
								this._heap._traverseTerrain.stepIter = this._heap._traverseTerrain.totalSteps; // cut the loop
							}
						}
						else{
							// step too high : eject
							this._heap._traverseTerrain.resultFlags |= DEFINE_SPRITE_MOVE_RESULT_FLAGS.EJECTED_H;
							this._heap._traverseTerrain.stepIter = this._heap._traverseTerrain.totalSteps; // cut the loop
						}
						
					}
				}
				else{
					this.x+=dx;
				}

			}


			if (dy!=0){
				this._heap._traverseTerrain.leftCorner = (dy<0) ? this.top_left : this.bottom_left;
				this._heap._traverseTerrain.rightCorner = (dy<0) ? this.top_right : this.bottom_right;

				this._heap._traverseTerrain.solidFromLeft = this.map_room.findVertically(
					this._heap._traverseTerrain.solidTerrain,
					this.x+this._heap._traverseTerrain.leftCorner.x, this.y+this._heap._traverseTerrain.leftCorner.y,
					dy,
					Math.abs(dy),
					this.constraints.y==DEFINE_SPRITE_MOVE_CONSTRAINT.WRAP);

				this._heap._traverseTerrain.solidFromRight = this.map_room.findVertically(
					this._heap._traverseTerrain.solidTerrain,
					this.x+this._heap._traverseTerrain.rightCorner.x, this.y+this._heap._traverseTerrain.rightCorner.y,
					dy,
					Math.abs(dy),
					this.constraints.y==DEFINE_SPRITE_MOVE_CONSTRAINT.WRAP);

				this._heap._traverseTerrain.solidFromCenter = -1;
				for (this._heap._traverseTerrain.scanX = this._heap._traverseTerrain.leftCorner.x+this._heap._traverseTerrain.featureW; this._heap._traverseTerrain.scanX<this._heap._traverseTerrain.rightCorner.x; this._heap._traverseTerrain.scanX+=this._heap._traverseTerrain.featureW){
					this._heap._traverseTerrain.solidFromCenter = Math.max(
						this._heap._traverseTerrain.solidFromCenter,
						this.map_room.findVertically(
							this._heap._traverseTerrain.solidTerrain,
							this.x+this._heap._traverseTerrain.scanX, this.y+this._heap._traverseTerrain.leftCorner.y,
							dy,
							Math.abs(dy),
							this.constraints.y==DEFINE_SPRITE_MOVE_CONSTRAINT.WRAP
						)
					);
				}
				
				if ((this._heap._traverseTerrain.solidFromLeft>=0 && this._heap._traverseTerrain.solidFromRight>=0) || this._heap._traverseTerrain.solidFromCenter>=0){
					// probably not a slope.  Just eject.
					// NOTE: this could cause objects moving more than 1px/frame along a slope
					// to not get as near a wall as they might otherwise
					this._heap._traverseTerrain.firstObstruction = Math.min(
						this._heap._traverseTerrain.solidFromLeft>=0 ? this._heap._traverseTerrain.solidFromLeft : Math.abs(dy),
						this._heap._traverseTerrain.solidFromRight>=0 ? this._heap._traverseTerrain.solidFromRight : Math.abs(dy),
						this._heap._traverseTerrain.solidFromCenter>=0 ? this._heap._traverseTerrain.solidFromCenter : Math.abs(dy)
						);
					//if (this._heap._traverseTerrain.solidFromLeft>=0 && this._heap._traverseTerrain.solidFromRight>=0){
					//	this._heap._traverseTerrain.firstObstruction = Math.min(this._heap._traverseTerrain.solidFromLeft,this._heap._traverseTerrain.solidFromRight);
					//}
					//else{
					//	this._heap._traverseTerrain.firstObstruction = this._heap._traverseTerrain.solidFromCenter;
					//}
					this.y += (dy>0) ? (this._heap._traverseTerrain.firstObstruction-1) : (1-this._heap._traverseTerrain.firstObstruction);
					this._heap._traverseTerrain.resultFlags |= DEFINE_SPRITE_MOVE_RESULT_FLAGS.EJECTED_V;
				}
				else if (this._heap._traverseTerrain.solidFromRight>0){
					// right corner obstruction; attempt to traverse across a slope in 1px increments
					this._heap._traverseTerrain.stepDelta = (dy>0) ? 1 : -1;
					// move as far as we can unobstructed
					this.y += (this._heap._traverseTerrain.solidFromRight-1) * this._heap._traverseTerrain.stepDelta;
					// move the rest by incremental test
					this._heap._traverseTerrain.totalSteps = Math.abs(dy)-(this._heap._traverseTerrain.solidFromRight-1);
					for (this._heap._traverseTerrain.stepIter=0; this._heap._traverseTerrain.stepIter<this._heap._traverseTerrain.totalSteps; this._heap._traverseTerrain.stepIter++){
						// if we move by only a 1px increment, see how far left of our right edge we find clear space (up to the max width we can step)
						this._heap._traverseTerrain.stepHeight = this.map_room.findHorizontally(
							this._heap._traverseTerrain.clearTerrain,
							this.x+this._heap._traverseTerrain.rightCorner.x, this.y+this._heap._traverseTerrain.rightCorner.y+this._heap._traverseTerrain.stepDelta,
							-1,
							this.max_step,
							this.constraints.x==DEFINE_SPRITE_MOVE_CONSTRAINT.WRAP);
						// if there's clear space that we could step over onto, see if we have the room to our left
						if (this._heap._traverseTerrain.stepHeight>=0){
							this._heap._traverseTerrain.stepClearance = this.map_room.findHorizontally(
								this._heap._traverseTerrain.solidTerrain,
								this.x+this._heap._traverseTerrain.leftCorner.x, this.y+this._heap._traverseTerrain.leftCorner.y+this._heap._traverseTerrain.stepDelta,
								-1,
								this._heap._traverseTerrain.stepHeight,
								this.constraints.x==DEFINE_SPRITE_MOVE_CONSTRAINT.WRAP);
							if (this._heap._traverseTerrain.stepClearance<0){
								// we can actually climb this step!  Move vertically, eject horizontally.
								this.y += this._heap._traverseTerrain.stepDelta;
								this.x -= this._heap._traverseTerrain.stepHeight;
								this._heap._traverseTerrain.resultFlags |= DEFINE_SPRITE_MOVE_RESULT_FLAGS.EJECTED_H;
							}
							else{
								// no clearance: eject
								this._heap._traverseTerrain.resultFlags |= DEFINE_SPRITE_MOVE_RESULT_FLAGS.EJECTED_V;
								this._heap._traverseTerrain.stepIter = this._heap._traverseTerrain.totalSteps; // cut the loop
							}
						}
						else{
							// step too high : eject
							this._heap._traverseTerrain.resultFlags |= DEFINE_SPRITE_MOVE_RESULT_FLAGS.EJECTED_V;
							this._heap._traverseTerrain.stepIter = this._heap._traverseTerrain.totalSteps; // cut the loop
						}
						
					}
				}
				else if (this._heap._traverseTerrain.solidFromLeft>0){
					// left corner obstruction; attempt to traverse across a slope in 1px increments
					this._heap._traverseTerrain.stepDelta = (dy>0) ? 1 : -1;
					// move as far as we can unobstructed
					this.y += (this._heap._traverseTerrain.solidFromLeft-1) * this._heap._traverseTerrain.stepDelta;
					// move the rest by incremental test
					this._heap._traverseTerrain.totalSteps = Math.abs(dy)-(this._heap._traverseTerrain.solidFromLeft-1);
					for (this._heap._traverseTerrain.stepIter=0; this._heap._traverseTerrain.stepIter<this._heap._traverseTerrain.totalSteps; this._heap._traverseTerrain.stepIter++){
						// if we move by only a 1px increment, see how far right of our left edge we find clear space (up to the max width we can step)
						this._heap._traverseTerrain.stepHeight = this.map_room.findHorizontally(
							this._heap._traverseTerrain.clearTerrain,
							this.x+this._heap._traverseTerrain.leftCorner.x, this.y+this._heap._traverseTerrain.leftCorner.y+this._heap._traverseTerrain.stepDelta,
							1,
							this.max_step,
							this.constraints.x==DEFINE_SPRITE_MOVE_CONSTRAINT.WRAP);
						// if there's clear space that we could step over onto, see if we have the room to our right
						if (this._heap._traverseTerrain.stepHeight>=0){
							this._heap._traverseTerrain.stepClearance = this.map_room.findHorizontally(
								this._heap._traverseTerrain.solidTerrain,
								this.x+this._heap._traverseTerrain.rightCorner.x, this.y+this._heap._traverseTerrain.rightCorner.y+this._heap._traverseTerrain.stepDelta,
								1,
								this._heap._traverseTerrain.stepHeight,
								this.constraints.x==DEFINE_SPRITE_MOVE_CONSTRAINT.WRAP);
							if (this._heap._traverseTerrain.stepClearance<0){
								// we can actually climb this step!  Move vertically, eject horizontally.
								this.y += this._heap._traverseTerrain.stepDelta;
								this.x += this._heap._traverseTerrain.stepHeight;
								this._heap._traverseTerrain.resultFlags |= DEFINE_SPRITE_MOVE_RESULT_FLAGS.EJECTED_H;
							}
							else{
								// no clearance: eject
								this._heap._traverseTerrain.resultFlags |= DEFINE_SPRITE_MOVE_RESULT_FLAGS.EJECTED_V;
								this._heap._traverseTerrain.stepIter = this._heap._traverseTerrain.totalSteps; // cut the loop
							}
						}
						else{
							// step too high : eject
							this._heap._traverseTerrain.resultFlags |= DEFINE_SPRITE_MOVE_RESULT_FLAGS.EJECTED_V;
							this._heap._traverseTerrain.stepIter = this._heap._traverseTerrain.totalSteps; // cut the loop
						}
						
					}
				}
				else{
					this.y+=dy;
				}
			}
			
		}
		else{
			this.y+=dy;
			this.x+=dx;
		}

		return this._heap._traverseTerrain.resultFlags;
	};

	// result: SpriteMoveResult into which to copy movement outcome
	Sprite.prototype.move = function(dx,dy,  result){
		this._heap.move.oldx = this.x;
		this._heap.move.oldy = this.y;
		this._heap.move.reSprites.length = 0;
		this._heap.move.resultFlags = 0;

		this._heap.move.collidingSprites.length=0;
		this._heap.move.ejectingSprites.length=0;
		this._heap.move.ejectingSpritesH.length=0;
		this._heap.move.ejectingSpritesV.length=0;

		this._heap.move.resultFlags |= this._traverseTerrain(dx,dy);

		//if (this.x - this._heap.move.oldx != dx){
		//	console.log("Failed to move intended distance!");
		//}

		if (this.constraints.x == DEFINE_SPRITE_MOVE_CONSTRAINT.WRAP && (this.x<0 || this.x>=this.map_room.pixel_width)){
			this.x = this.x%this.map_room.pixel_width;
			if (this.x<0) this.x += this.map_room.pixel_width;
			this._heap.move.resultFlags |= DEFINE_SPRITE_MOVE_RESULT_FLAGS.WRAPPED_H;
		}
		else if (this.constraints.x == DEFINE_SPRITE_MOVE_CONSTRAINT.CAP){
			if (this.x+this.box_left<0){
				this.x=-(this.box_left);
				this._heap.move.resultFlags |= DEFINE_SPRITE_MOVE_RESULT_FLAGS.CAPPED_H;
			}
			else if (this.x+this.box_right>=this.map_room.pixel_width){
				this.x = this.map_room.pixel_width - this.box_right - 1;
				this._heap.move.resultFlags |= DEFINE_SPRITE_MOVE_RESULT_FLAGS.CAPPED_H;
			}
		}
		if (this.constraints.y == DEFINE_SPRITE_MOVE_CONSTRAINT.WRAP && (this.y<0 || this.y>=this.map_room.pixel_height)){
			this.y = this.y%this.map_room.pixel_height;
			if (this.y<0) this.y += this.map_room.pixel_height;
			this._heap.move.resultFlags |= DEFINE_SPRITE_MOVE_RESULT_FLAGS.WRAPPED_V;
		}
		else if (this.constraints.y == DEFINE_SPRITE_MOVE_CONSTRAINT.CAP){
			if (this.y+this.box_top<0){
				this.y=-(this.box_top);
				this._heap.move.resultFlags |= DEFINE_SPRITE_MOVE_RESULT_FLAGS.CAPPED_V;
			}
			else if (this.y+this.box_bottom>=this.map_room.pixel_height){
				this.y = this.map_room.pixel_height - this.box_bottom - 1;
				this._heap.move.resultFlags |= DEFINE_SPRITE_MOVE_RESULT_FLAGS.CAPPED_V;
			}
		}

		// EFFECTIVE dx and dy
		// since traverseTerrain may shunt us around corners/slopes
		// and penetration will compute oddly if what caused us to penetrate
		// is not represented in the original dx/dy
		this._heap.move.edx = this.x - this._heap.move.oldx;
		this._heap.move.edy = this.y - this._heap.move.oldy;

		// check any sprites we may have collided into
		if (this.collision_flags & DEFINE_SPRITE_COLLISION_FLAGS.SPRITES_ACTIVE){
			for (this._heap.move.travs=0; this._heap.move.travs<this.map_room.sprites.length; this._heap.move.travs++){
				// cache the sprite we're checking against
				this._heap.move.sprite = this.map_room.sprites[this._heap.move.travs];
				if (this._heap.move.sprite!=this && (this._heap.move.sprite.collision_flags & DEFINE_SPRITE_COLLISION_FLAGS.SPRITES_PASSIVE)){
					// register penetration distance up front
					this.testCollision(
						this._heap.move.sprite,
						this._heap.move.edx,
						this._heap.move.edy,
						this._heap.move.penetration
						);
					if (this._heap.move.penetration.x!=0 || this._heap.move.penetration.y!=0){
						// SOLID objects impede movement; eject ourself from it
						// if we can't eject, then it must have gotten into our space on its own update
						// and been unable/unwilling to shift us out of the way.  We're screwed (crushed).
						if (this._heap.move.sprite.collision_flags & DEFINE_SPRITE_COLLISION_FLAGS.SOLID){
							this._heap.move.ejectResults = 0;
							this._heap.move.ejectingSprites.push(this._heap.move.sprite);

							this._heap.move.preEjectX = this.x;
							this._heap.move.preEjectY = this.y;

							// do the lesser penetration first, then see if we need to do the greater
							// i.e. 3 check blocks: early x, y, late x
							// - if x<y, do early x, then, if still penetrating, normal y
							// - if y<x, do normal y then, if still penetrating, late x
							if (this._heap.move.penetration.x>0 && this._heap.move.penetration.x<=this._heap.move.penetration.y){
								if (this._heap.move.edx>0){
									this._heap.move.ejectResults = this._traverseTerrain(-this._heap.move.penetration.x,0);
									this._heap.move.resultFlags |= this._heap.move.ejectResults;
									if (this._heap.move.ejectResults & DEFINE_SPRITE_MOVE_RESULT_FLAGS.EJECTED_H){
										// try again, this time ejecting on both axes
										this.x = this._heap.move.preEjectX;
										this.y = this._heap.move.oldy; // assume we never even got this far
										this._heap.move.ejectResults = this._traverseTerrain(-this._heap.move.penetration.x,0);
										if (this._heap.move.ejectResults & DEFINE_SPRITE_MOVE_RESULT_FLAGS.EJECTED_H){
											// the terrain pushed us back, so there's evidently no room either way
											this._heap.move.resultFlags |= DEFINE_SPRITE_MOVE_RESULT_FLAGS.CRUSHED;
										}
									}
									this._heap.move.resultFlags |= DEFINE_SPRITE_MOVE_RESULT_FLAGS.EJECTED_SH;
									this._heap.move.ejectingSpritesH.push(this._heap.move.sprite);
								}
								else if (this._heap.move.edx<0){
									this._heap.move.ejectResults = this._traverseTerrain(this._heap.move.penetration.x,0);
									this._heap.move.resultFlags |= this._heap.move.ejectResults;
									if (this._heap.move.ejectResults & DEFINE_SPRITE_MOVE_RESULT_FLAGS.EJECTED_H){
										// try again, this time ejecting on both axes
										this.x = this._heap.move.preEjectX;
										this.y = this._heap.move.oldy; // assume we never even got this far
										this._heap.move.ejectResults = this._traverseTerrain(this._heap.move.penetration.x,0);
										if (this._heap.move.ejectResults & DEFINE_SPRITE_MOVE_RESULT_FLAGS.EJECTED_H){
											// the terrain pushed us back, so there's evidently no room either way
											this._heap.move.resultFlags |= DEFINE_SPRITE_MOVE_RESULT_FLAGS.CRUSHED;
										}
									}
									this._heap.move.resultFlags |= DEFINE_SPRITE_MOVE_RESULT_FLAGS.EJECTED_SH;
									this._heap.move.ejectingSpritesH.push(this._heap.move.sprite);
								}
								
								this.testCollision(
									this._heap.move.sprite,
									this._heap.move.edx,
									this._heap.move.edy,
									this._heap.move.penetration
									); // update penetration to theoretically clear x
							}

							this._heap.move.preEjectX = this.x;
							this._heap.move.preEjectY = this.y;

							if (this._heap.move.penetration.y>0){
								if (this._heap.move.edy>0){
									this._heap.move.ejectResults = this._traverseTerrain(0,-this._heap.move.penetration.y);
									this._heap.move.resultFlags |= this._heap.move.ejectResults;
									if (this._heap.move.ejectResults & DEFINE_SPRITE_MOVE_RESULT_FLAGS.EJECTED_V){
										// try again, this time ejecting on both axes
										this.x = this._heap.move.oldx; // assume we never even got this far
										this.y = this._heap.move.preEjectY;
										this._heap.move.ejectResults = this._traverseTerrain(0,-this._heap.move.penetration.y);
										if (this._heap.move.ejectResults & DEFINE_SPRITE_MOVE_RESULT_FLAGS.EJECTED_V){
											// the terrain pushed us back, so there's evidently no room either way
											this._heap.move.resultFlags |= DEFINE_SPRITE_MOVE_RESULT_FLAGS.CRUSHED;
										}
									}
									this._heap.move.resultFlags |= DEFINE_SPRITE_MOVE_RESULT_FLAGS.EJECTED_SV;
									this._heap.move.ejectingSpritesV.push(this._heap.move.sprite);
								}
								else if (this._heap.move.edy<0){
									this._heap.move.ejectResults = this._traverseTerrain(0,this._heap.move.penetration.y);
									this._heap.move.resultFlags |= this._heap.move.ejectResults;
									if (this._heap.move.ejectResults & DEFINE_SPRITE_MOVE_RESULT_FLAGS.EJECTED_V){
										// try again, this time ejecting on both axes
										this.x = this._heap.move.oldx; // assume we never even got this far
										this.y = this._heap.move.preEjectY;
										this._heap.move.ejectResults = this._traverseTerrain(0,this._heap.move.penetration.y);
										if (this._heap.move.ejectResults & DEFINE_SPRITE_MOVE_RESULT_FLAGS.EJECTED_V){
											// the terrain pushed us back, so there's evidently no room either way
											this._heap.move.resultFlags |= DEFINE_SPRITE_MOVE_RESULT_FLAGS.CRUSHED;
										}
									}
									this._heap.move.resultFlags |= DEFINE_SPRITE_MOVE_RESULT_FLAGS.EJECTED_SV;
									this._heap.move.ejectingSpritesV.push(this._heap.move.sprite);
								}
								
								this.testCollision(
									this._heap.move.sprite,
									this._heap.move.edx,
									this._heap.move.edy,
									this._heap.move.penetration
									); // update penetration to theoretically clear y
							}

							this._heap.move.preEjectX = this.x;
							this._heap.move.preEjectY = this.y;

							if (this._heap.move.penetration.x>0){
								if (this._heap.move.edx>0){
									this._heap.move.ejectResults = this._traverseTerrain(-this._heap.move.penetration.x,0);
									this._heap.move.resultFlags |= this._heap.move.ejectResults;
									if (this._heap.move.ejectResults & DEFINE_SPRITE_MOVE_RESULT_FLAGS.EJECTED_H){
										// try again, this time ejecting on both axes
										this.x = this._heap.move.preEjectX;
										this.y = this._heap.move.oldy; // assume we never even got this far
										this._heap.move.ejectResults = this._traverseTerrain(-this._heap.move.penetration.x,0);
										if (this._heap.move.ejectResults & DEFINE_SPRITE_MOVE_RESULT_FLAGS.EJECTED_H){
											// the terrain pushed us back, so there's evidently no room either way
											this._heap.move.resultFlags |= DEFINE_SPRITE_MOVE_RESULT_FLAGS.CRUSHED;
										}
									}
									this._heap.move.resultFlags |= DEFINE_SPRITE_MOVE_RESULT_FLAGS.EJECTED_SH;
									
									if (this._heap.move.ejectingSpritesH.length==0 || this._heap.move.ejectingSpritesH[this._heap.move.ejectingSpritesH.length-1]!=this._heap.move.sprite) this._heap.move.ejectingSpritesH.push(this._heap.move.sprite);
								}
								else if (this._heap.move.edx<0){
									this._heap.move.ejectResults = this._traverseTerrain(this._heap.move.penetration.x,0);
									this._heap.move.resultFlags |= this._heap.move.ejectResults;
									if (this._heap.move.ejectResults & DEFINE_SPRITE_MOVE_RESULT_FLAGS.EJECTED_H){
										// try again, this time ejecting on both axes
										this.x = this._heap.move.preEjectX;
										this.y = this._heap.move.oldy; // assume we never even got this far
										this._heap.move.ejectResults = this._traverseTerrain(-this._heap.move.penetration.x,0);
										if (this._heap.move.ejectResults & DEFINE_SPRITE_MOVE_RESULT_FLAGS.EJECTED_H){
											// the terrain pushed us back, so there's evidently no room either way
											this._heap.move.resultFlags |= DEFINE_SPRITE_MOVE_RESULT_FLAGS.CRUSHED;
										}
									}
									this._heap.move.resultFlags |= DEFINE_SPRITE_MOVE_RESULT_FLAGS.EJECTED_SH;
									if (this._heap.move.ejectingSpritesH.length==0 || this._heap.move.ejectingSpritesH[this._heap.move.ejectingSpritesH.length-1]!=this._heap.move.sprite) this._heap.move.ejectingSpritesH.push(this._heap.move.sprite);
								}

								//this.testCollision(this._heap.move.sprite,dx,dy,  this._heap.move.penetration); // don't need to re-test
							}
						}
						else{
							this._heap.move.collidingSprites.push(this._heap.move.sprite);
							this._heap.move.resultFlags |= DEFINE_SPRITE_MOVE_RESULT_FLAGS.COLLIDING_S;
						}
					}
				}
			}

			// because testing for ejection back into solid sprites in the main
			// sprite collision loop would become recursively complex, suffice
			// with a flat check to see if, after all other ejection, we're still
			// inside a solid sprite:
			if (Sprite.AGGRESSIVE_CRUSH){
				this.getCollisions(this._heap.move.reSprites);
				for (this._heap.move.i=0; this._heap.move.i<this._heap.move.reSprites.length; this._heap.move.i++){
					if ((this._heap.move.reSprites[this._heap.move.i].collision_flags & DEFINE_SPRITE_COLLISION_FLAGS.SOLID) && this._heap.move.reSprites[this._heap.move.i]!=this){
						this._heap.move.resultFlags |= DEFINE_SPRITE_MOVE_RESULT_FLAGS.CRUSHED;
						break;
					}
				}
			}
		}

		if (this.x+this.box_right<0) this._heap.move.resultFlags |= DEFINE_SPRITE_MOVE_RESULT_FLAGS.OFF_LEFT;
		if (this.x+this.box_left>=this.map_room.pixel_width) this._heap.move.resultFlags |= DEFINE_SPRITE_MOVE_RESULT_FLAGS.OFF_RIGHT;
		if (this.y+this.box_bottom<0) this._heap.move.resultFlags |= DEFINE_SPRITE_MOVE_RESULT_FLAGS.OFF_TOP;
		if (this.y+this.box_top>=this.map_room.pixel_height) this._heap.move.resultFlags |= DEFINE_SPRITE_MOVE_RESULT_FLAGS.OFF_BOTTOM;

		if (this.collision_flags & DEFINE_SPRITE_COLLISION_FLAGS.SOLID){
			this._heap.move.dx = this.x-this._heap.move.oldx;
			this._heap.move.dy = this.y-this._heap.move.oldy;
			this.getCollisions(this._heap.move.influenced);

			// if we are still solid and the collidee cares, then when we go to move it,
			// it may collide with us in its move call, and eject from us, opposite the 
			// direction we're trying to push it (see testCollision penetration).
			// i.e. it will clip through us.
			// so disable our own solidity for the moment
			this.collision_flags &= ~DEFINE_SPRITE_COLLISION_FLAGS.SOLID;

			for (this._heap.move.i=0; this._heap.move.i<this._heap.move.influenced.length; this._heap.move.i++){
				if (this._heap.move.influenced[this._heap.move.i].game_object.interactions & DEFINE_GAMEOBJECT_INTERACTIONS.FORCE_CRUSH){
					this._heap.move.prevx = this._heap.move.influenced[this._heap.move.i].x;
					this._heap.move.prevy = this._heap.move.influenced[this._heap.move.i].y;
					//var reactres = this._heap.move.influenced[this._heap.move.i].move(this._heap.move.dx, this._heap.move.dy);
					this._heap.move.influenced[this._heap.move.i].move(this._heap.move.dx, this._heap.move.dy);
					this._heap.move.reactdx = this._heap.move.influenced[this._heap.move.i].x-this._heap.move.prevx;
					this._heap.move.reactdy = this._heap.move.influenced[this._heap.move.i].y-this._heap.move.prevy;
					
					// method 1: see if the move was successful.  Misfires if the recipient was pushed against a slope
					// and thus substituted motion in one direction for another.
					//if (	(this._heap.move.dx>0 && this._heap.move.reactdx<=0) // couldn't push horizontal
					//	||	(this._heap.move.dx<0 && this._heap.move.reactdx>=0)
					//	||	(this._heap.move.dy>0 && this._heap.move.reactdy<=0) // couldn't push vertical
					//	||	(this._heap.move.dy<0 && this._heap.move.reactdy>=0)
					//	||	(this._heap.move.dx==0 && this._heap.move.dy==0)){ // something jumped, or solidity switched, and now it's stuck inside
						

					// method 2: are we still touching?  If so, the push implicitly failed.
					if (this.isTouchingSprite(this._heap.move.influenced[this._heap.move.i])){


						this._heap.move.influenced[this._heap.move.i].game_object.crush();

						// the motion resolution will have likely zipped the target to the far side of us, 
						// or some other legal but distant location.
						// If crush() resulted in it no longer wanting to interact (e.g. on player death)
						// then unwind the motion.
						if (!(this._heap.move.influenced[this._heap.move.i].game_object.interactions & DEFINE_GAMEOBJECT_INTERACTIONS.FORCE_CRUSH)){
							this._heap.move.influenced[this._heap.move.i].x = this._heap.move.prevx;
							this._heap.move.influenced[this._heap.move.i].y = this._heap.move.prevy;
						}
					} 
				}
			}

			// re-enable solidity
			this.collision_flags |= DEFINE_SPRITE_COLLISION_FLAGS.SOLID;
		}

		//return new SpriteMoveResult(this._heap.move.resultFlags,this._heap.move.ejectingSpritesH,this._heap.move.ejectingSpritesV,this._heap.move.ejectingSprites,this._heap.move.collidingSprites);
		if (result) result.init(this._heap.move.resultFlags,this._heap.move.ejectingSpritesH,this._heap.move.ejectingSpritesV,this._heap.move.ejectingSprites,this._heap.move.collidingSprites);
		this._heap.move.influenced.length=0;
		this._heap.move.collidingSprites.length=0;
		this._heap.move.ejectingSprites.length=0;
		this._heap.move.ejectingSpritesH.length=0;
		this._heap.move.ejectingSpritesV.length=0;
	};

	Sprite.prototype.isTouchingTerrain = function(terrainType){
		return (
			this.map_room.getTerrainAt(this.x+this.top_left.x, this.y+this.top_left.y)==terrainType ||
			this.map_room.getTerrainAt(this.x+this.top_right.x, this.y+this.top_right.y)==terrainType ||
			this.map_room.getTerrainAt(this.x+this.bottom_left.x, this.y+this.bottom_left.y)==terrainType ||
			this.map_room.getTerrainAt(this.x+this.bottom_right.x, this.y+this.bottom_right.y)==terrainType
			);
	
	};
