diff --git a/doc/manual.pdf b/doc/manual.pdf
index bc50485a356cfc72edd235c733aa7f64aceb28c5..83c57464a58ea5605183f38c57285382a8a16709 100644
Binary files a/doc/manual.pdf and b/doc/manual.pdf differ
diff --git a/doc/manual.tex b/doc/manual.tex
index 39bb829883a366210c16e418a5abe3c85122066e..661ffba5bb30e83da0cb2362b36b3b64606c47ce 100644
--- a/doc/manual.tex
+++ b/doc/manual.tex
@@ -14,6 +14,7 @@
 \usepackage[cm]{fullpage}
 \usepackage{enumitem}
 \usepackage{subcaption}
+\usepackage{color}
 
 \title{RoboCup 2D Half Field Offense \\ Technical Manual}
 \author{Matthew Hausknecht}
@@ -613,29 +614,29 @@ below the table for the action abbreviations and notes.
 
 \begin{center}
 {\footnotesize
-\begin{tabular}{r       | c    c    c    c | c    c    c    c  | c    c    c    c    c    c    c    c    c    c}
-Action                  & Da & Tu & Ta & K & KT & MT & DT & I  & M  & S  & P  & Dr & C  & RG & DG & G & MP & Re \\
+\begin{tabular}{r       | c    c    c    c | c    c    c    c | c   c   c   c   c   c    c    c   c    c}
+Action                  & Da & Tu & Ta & K & KT & MT & DT & I & M & S & P & D & C & RG & DG & G & MP & Re \\
 \hline \hline
-Self position invalid   & Y  & Y  & Y  & Y & N  & N  & N? & N? & N  & N? & N? & Y  & ?  & N  & N  & N & N  & Y \\
-Self velocity invalid   & N  & Y? & Y? & Y & ?  & N  & N  & N  & N  & ?  & ?  & Y  & Y  & N  & N  & N & N  & Y \\
-Ball position invalid   & Y  & Y  & Y? & N & N  & Y  & N  & N  & N  & N  & N  & Y  & N  & N  & N  & N & N  & Y \\
-Ball velocity invalid   & Y  & Y  & Y  & ? & ?  & Y  & Y  & N? & ?  & N  & N? & Y? & Y? & Y  & Y  & Y & Y  & Y \\
-Teammate loc invalid    & Y  & Y  & Y  & Y & Y  & Y  & Y  & Y  & N  & Y  & N  & Y? & Y  & Y  & Y  & Y & Y  & Y \\
-Team. unum invalid      & Y  & Y  & Y  & Y & Y  & Y  & Y  & Y  & Y  & Y  & N  & Y  & Y  & Y  & Y  & Y & Y  & Y \\
-Opponent loc invalid    & Y  & Y  & Y  & Y & Y  & Y  & Y? & Y  & N  & Y? & Y  & Y? & Y  & Y  & Y  & Y & N  & Y \\
-Opp. unum invalid       & Y  & Y  & Y  & Y & Y  & Y  & Y  & Y  & Y  & Y  & Y  & Y  & Y  & Y  & Y  & Y & N  & Y \\
+Self position invalid   & Y  & Y  & Y  & Y & N  & N  & N  & N & N & N & N & Y & Y & N  & N  & N & N  & Y \\
+Self velocity invalid   & N  & Y  & Y  & Y & N  & N  & N  & N & N & N & Y & Y & Y & N  & N  & N & N  & Y \\
+Ball position invalid   & Y  & Y  & Y  & N & N  & Y  & Y  & N & N & N & N & Y & N & N  & N  & N & N  & Y \\
+Ball velocity invalid   & Y  & Y  & Y  & Y & N  & Y  & N  & Y & Y & N & N & Y & Y & Y  & Y  & Y & Y  & Y \\
+Teammate loc invalid    & Y  & Y  & Y  & Y & Y  & Y  & Y  & Y & Y & Y & N & Y & Y & Y  & Y  & Y & Y  & Y \\
+Team. unum invalid      & Y  & Y  & Y  & Y & Y  & Y  & Y  & Y & Y & Y & N & Y & Y & Y  & Y  & Y & Y  & Y \\
+Opponent loc invalid    & Y  & Y  & Y  & Y & Y  & Y  & N  & Y & N & Y & Y & N & Y & Y  & Y  & Y & N  & Y \\
+Opp. unum invalid       & Y  & Y  & Y  & Y & Y  & Y  & Y  & Y & Y & Y & Y & Y & Y & Y  & Y  & Y & N  & Y \\
 \hline
-Ball kickable           & Y  & Y  & Y  & Y & Y  & N  & Y  & N  & *  & Y  & Y  & Y  & Y  & ?  & ?  & N & Y  & N \\
-Ball not kickable       & Y  & Y  & Y  & N & N  & Y  & Y  & Y  & Y  & N  & N  & N  & Y  & Y  & Y  & Y & Y  & Y \\
+Ball kickable           & Y  & Y  & Y  & Y & Y  & N  & Y  & N & * & Y & Y & Y & Y & N  & N  & N & Y  & N \\
+Ball not kickable       & Y  & Y  & Y  & N & N  & Y  & Y  & Y & Y & N & N & N & Y & Y  & Y  & Y & Y  & Y \\
 \hline
-Frozen                  & N  & N  & N  & N & N  & N  & N? & N  & N? & N  & N  & Y  & N? & N  & N  & N & N  & Y \\
-Colliding w/ball        & Y  & Y  & ?  & N & Y  & N  & Y  & Y  & Y  & ?  & Y  & Y  & ?  & ?  & ?  & N & ?  & ? \\
-Colliding w/player      & Y  & Y  & ?  & Y & Y  & Y  & Y  & Y  & Y  & ?  & N  & Y  & ?  & ?  & ?  & Y & ?  & ? \\
-Colliding w/post        & Y  & Y  & N? & Y & Y  & Y  & N  & Y  & Y  & ?  & N  & Y  & ?  & Y  & Y  & Y & Y  & ? \\
+Frozen                  & N  & N  & N  & N & N  & N  & N  & N & N & N & N & Y & N & N  & N  & N & N  & Y \\
+Colliding w/ball        & Y  & Y  & N  & N & Y  & N  & Y  & Y & Y & Y & Y & Y & Y & N  & N  & N & N  & N \\
+Colliding w/player      & Y  & Y  & Y  & Y & Y  & Y  & Y  & Y & Y & Y & N & Y & Y & Y  & Y  & Y & Y  & Y \\
+Colliding w/post        & Y  & Y  & Y  & Y & Y  & Y  & N  & Y & Y & Y & N & Y & Y & Y  & Y  & Y & Y  & Y \\
 \hline
-Offense                 & Y  & Y  & N  & Y & Y  & Y  & Y  & Y  & Y  & Y  & Y  & Y  & N  & N  & N  & Y & N  & Y \\
-Defense, not goalie     & Y  & Y  & Y  & N & N  & Y  & N  & Y  & Y  & N  & N  & N  & N  & Y  & ?  & Y & Y  & Y \\
-Goalie (defense)        & Y  & Y  & Y  & N & N  & Y  & N  & Y  & ?  & N  & N  & N  & Y  & ?  & ?  & ? & N  & Y \\
+Offense                 & Y  & Y  & N  & Y & Y  & Y  & Y  & Y & Y & Y & Y & Y & N & N  & N  & Y & N  & Y \\
+Defense, not goalie     & Y  & Y  & Y  & N & N  & Y  & N  & Y & Y & N & N & N & N & Y  & Y  & Y & Y  & Y \\
+Goalie (defense)        & Y  & Y  & Y  & N & N  & Y  & N  & Y & Y & N & N & N & Y & N  & Y  & N & N  & Y \\
 \end{tabular}
 }
 \end{center}
@@ -643,7 +644,7 @@ Goalie (defense)        & Y  & Y  & Y  & N & N  & Y  & N  & Y  & ?  & N  & N  &
 \begin{itemize}[noitemsep]
 \item{Da:\,Dash; Tu:\,Turn; Ta:\,Tackle; K:\,Kick}
 \item{KT:\,Kick\_To; MT:\,Move\_To; DT:\,Dribble\_To; I:\,Intercept}
-\item{M:\,Move; S:\,Shoot; P:\,Pass; Dr:\,Dribble; C:\,Catch; RG:\,Reduce\_Angle\_To\_Goal; DG:\,Defend\_Goal; G:\,Go\_To\_Ball; MP:\,Mark\_Player; Re: Reorient}
+\item{M:\,Move; S:\,Shoot; P:\,Pass; D:\,Dribble; C:\,Catch; RG:\,Reduce\_Angle\_To\_Goal; DG:\,Defend\_Goal; G:\,Go\_To\_Ball; MP:\,Mark\_Player; Re: Reorient}
 \end{itemize}
 
 \section{Developing a New Agent}
diff --git a/example/explore_offense_actions.twoplayer.py b/example/explore_offense_actions.twoplayer.py
new file mode 100755
index 0000000000000000000000000000000000000000..b26b5b8a6c2b8cc0a17495e73a0c0630c126ec65
--- /dev/null
+++ b/example/explore_offense_actions.twoplayer.py
@@ -0,0 +1,1710 @@
+#!/usr/bin/env python
+"""
+This is a script to explore what actions are available under what circumstances,
+plus testing out feedback and the use of two players controlled by the same script
+but two (coordinated) processes.
+"""
+from __future__ import print_function
+# encoding: utf-8
+
+import argparse
+#import functools
+import itertools
+import math
+import multiprocessing
+import multiprocessing.sharedctypes
+import os
+import random
+import struct
+import subprocess
+import sys
+import time
+import warnings
+
+try:
+  import hfo
+except ImportError:
+  print('Failed to import hfo. To install hfo, in the HFO directory'\
+    ' run: \"pip install .\"')
+  exit()
+
+HALF_FIELD_WIDTH = 68 # y coordinate
+HALF_FIELD_FULL_WIDTH = HALF_FIELD_WIDTH * 1.2
+HALF_FIELD_LENGTH = 52.5 # x coordinate
+HALF_FIELD_FULL_LENGTH = HALF_FIELD_LENGTH * 1.2
+GOAL_WIDTH = 14.02
+MAX_RADIUS = math.sqrt(((HALF_FIELD_WIDTH/2)**2) + (HALF_FIELD_LENGTH**2)) # not actually correct, but works...
+ERROR_TOLERANCE = math.pow(sys.float_info.epsilon,0.25)
+POS_ERROR_TOLERANCE = 0.05
+
+MAX_REAL_X_VALID = 1.1*HALF_FIELD_LENGTH
+MIN_REAL_X_VALID = -0.1*HALF_FIELD_LENGTH
+MAX_REAL_Y_VALID = 1.1*(HALF_FIELD_WIDTH/2)
+MIN_REAL_Y_VALID = -1.1*(HALF_FIELD_WIDTH/2)
+
+def unnormalize(pos, min_val, max_val, silent=False):
+  assert max_val > min_val
+  if (not silent) and (abs(pos) > 1.0+ERROR_TOLERANCE):
+    print("Pos {0:n} fed to unnormalize (min_val {1:n}, max_val {2:n})".format(
+      pos, min_val, max_val), file=sys.stderr)
+    sys.stderr.flush()
+  pos = min(1.0,max(-1.0,pos))
+  top = (pos - -1.0)/(1 - -1.0)
+  bot = max_val - min_val
+  return (top*bot) + min_val
+
+def get_y_unnormalized(y_pos, silent=False):
+  y_pos_real = unnormalize(y_pos, MIN_REAL_Y_VALID, MAX_REAL_Y_VALID, silent=silent)
+  est_y_pos = get_y_normalized(y_pos_real, silent=silent)
+  if abs(y_pos - est_y_pos) > POS_ERROR_TOLERANCE:
+    raise RuntimeError(
+      "Bad denormalization/normalization of {0:n} to {1:n}; reverse {2:n}".format(
+        y_pos, y_pos_real, est_y_pos))
+  return y_pos_real
+
+def get_x_unnormalized(x_pos, silent=False):
+  x_pos_real = unnormalize(x_pos, MIN_REAL_X_VALID, MAX_REAL_X_VALID, silent=silent)
+  est_x_pos = get_x_normalized(x_pos_real, silent=silent)
+  if abs(x_pos - est_x_pos) > POS_ERROR_TOLERANCE:
+    raise RuntimeError(
+      "Bad denormalization/normalization of {0:n} to {1:n}; reverse {2:n}".format(
+        x_pos, x_pos_real, est_x_pos))
+  return x_pos_real
+
+def normalize(pos, min_val, max_val, silent=False):
+  assert max_val > min_val
+  top = (pos - min_val)/(max_val - min_val)
+  bot = 1.0 - -1.0
+  num = (top*bot) + -1.0
+  if abs(num) > 1.0+POS_ERROR_TOLERANCE:
+    if abs(num) > 1.1:
+      raise RuntimeError("Pos {0:n} gives normalized num {1:n} (min_val {2:n}, max_val {3:n})".format(
+        pos, num, min_val, max_val))
+    elif not silent:
+      print(
+        "Pos {0:n} gives normalized num {1:n} (min_val {2:n}, max_val {3:n})".format(
+          pos, num, min_val, max_val), file=sys.stderr)
+      sys.stderr.flush()
+  return min(1.0,max(-1.0,num))
+
+def get_y_normalized(y_pos, silent=False):
+  y_pos_norm = normalize(y_pos, MIN_REAL_Y_VALID, MAX_REAL_Y_VALID, silent=silent)
+##  est_y_pos = get_y_unnormalized(y_pos_norm, silent=silent)
+##  if abs(y_pos - est_y_pos) > POS_ERROR_TOLERANCE:
+##    raise RuntimeError("Bad normalization/denormalization of {0:n} to {1:n}; reverse {2:n}".format(
+##      y_pos, y_pos_norm, est_y_pos))
+  return y_pos_norm
+
+def get_x_normalized(x_pos, silent=False):
+  return normalize(x_pos, MIN_REAL_X_VALID, MAX_REAL_X_VALID, silent=silent)
+
+GOAL_POS_X = get_x_normalized(HALF_FIELD_LENGTH)
+GOAL_TOP_POS_Y = get_y_normalized(-1*(GOAL_WIDTH/2))
+GOAL_BOTTOM_POS_Y = get_y_normalized(GOAL_WIDTH/2)
+
+MAX_POS_Y_BALL_SAFE = get_y_normalized(HALF_FIELD_WIDTH/2) - POS_ERROR_TOLERANCE
+MIN_POS_Y_BALL_SAFE = get_y_normalized(-0.5*HALF_FIELD_WIDTH) + POS_ERROR_TOLERANCE
+MAX_POS_X_BALL_SAFE = get_x_normalized(HALF_FIELD_LENGTH) - POS_ERROR_TOLERANCE
+MIN_POS_X_BALL_SAFE = get_x_normalized(0) + POS_ERROR_TOLERANCE
+
+MAX_POS_X_OK = 1.0 - ERROR_TOLERANCE
+MIN_POS_X_OK = -1.0 + ERROR_TOLERANCE
+MAX_POS_Y_OK = MAX_POS_X_OK
+MIN_POS_Y_OK = MIN_POS_X_OK
+
+def get_dist_real(ref_x, ref_y, src_x, src_y, silent=False):
+  ref_x_real = get_x_unnormalized(ref_x, silent=silent)
+  ref_y_real = get_y_unnormalized(ref_y, silent=silent)
+  src_x_real = get_x_unnormalized(src_x, silent=silent)
+  src_y_real = get_y_unnormalized(src_y, silent=silent)
+
+  return math.sqrt(math.pow((ref_x_real - src_x_real),2) +
+                   math.pow((ref_y_real - src_y_real),2))
+
+def get_dist_from_real(ref_x_real, ref_y_real, src_x_real, src_y_real):
+    return math.sqrt(math.pow((ref_x_real - src_x_real),2) +
+                     math.pow((ref_y_real - src_y_real),2))
+
+def get_dist_normalized(ref_x, ref_y, src_x, src_y):
+  return math.sqrt(math.pow((ref_x - src_x),2) +
+                   math.pow(((HALF_FIELD_WIDTH/HALF_FIELD_LENGTH)*(ref_y - src_y)),2))
+
+#Instead of adding six as a dependency, this code was copied from the six implementation.
+#six is Copyright (c) 2010-2015 Benjamin Peterson
+
+if sys.version_info[0] >= 3:
+    def iterkeys(d, **kw):
+        return iter(d.keys(**kw))
+
+    def iteritems(d, **kw):
+        return iter(d.items(**kw))
+
+    def itervalues(d, **kw):
+        return iter(d.values(**kw))
+else:
+    def iterkeys(d, **kw):
+        return iter(d.iterkeys(**kw))
+
+    def iteritems(d, **kw):
+        return iter(d.iteritems(**kw))
+
+    def itervalues(d, **kw):
+        return iter(d.itervalues(**kw))
+
+INTENT_BALL_COLLISION = 0
+INTENT_BALL_KICKABLE = 1
+INTENT_GOAL_COLLISION = 2
+INTENT_PLAYER_COLLISION = 3
+INTENT_RANDOM_MANEUVER = 4
+
+INTENT_DICT = {INTENT_BALL_COLLISION: 'INTENT_BALL_COLLISION',
+               INTENT_BALL_KICKABLE: 'INTENT_BALL_KICKABLE',
+               INTENT_GOAL_COLLISION: 'INTENT_GOAL_COLLISION',
+               INTENT_PLAYER_COLLISION: 'INTENT_PLAYER_COLLISION',
+               INTENT_RANDOM_MANEUVER: 'INTENT_RANDOM_MANEUVER'}
+
+BIT_LIST_LEN = 8
+STRUCT_PACK = "BH"
+
+def get_dist_from_proximity(proximity, max_dist=MAX_RADIUS):
+  proximity_real = unnormalize(proximity, 0.0, 1.0)
+  dist = (1 - proximity_real)*max_dist
+  if (dist > (max_dist+ERROR_TOLERANCE)) or (dist <= (-1.0*ERROR_TOLERANCE)):
+    warnings.warn("Proximity {0:n} gives dist {1:n} (max_dist {2:n})".format(proximity,dist,max_dist))
+  return min(max_dist,max(0,dist))
+
+def get_proximity_from_dist(dist, max_dist=MAX_RADIUS):
+  if dist > (max_dist+ERROR_TOLERANCE):
+    print("Dist {0:n} is above max_dist {1:n}".format(dist, max_dist), file=sys.stderr)
+    sys.stderr.flush()
+  proximity_real = min(1.0, max(0.0, (1.0 - (dist/max_dist))))
+  return normalize(proximity_real, 0.0, 1.0)
+
+# MAX_GOAL_DIST - may wish to work out...
+
+def get_angle(sin_angle,cos_angle):
+  if max(abs(sin_angle),abs(cos_angle)) <= sys.float_info.epsilon:
+    warnings.warn(
+      "Unable to accurately determine angle from sin_angle {0:n} cos_angle {1:n}".format(
+        sin_angle, cos_angle))
+    return None
+  angle = math.degrees(math.atan2(sin_angle,cos_angle))
+  if angle < 0:
+    angle += 360
+  return angle
+
+def get_abs_angle(body_angle,rel_angle):
+  if (body_angle is None) or (rel_angle is None):
+    return None
+  angle = body_angle + rel_angle
+  while angle >= 360:
+    angle -= 360
+  if angle < 0:
+    if abs(angle) <= ERROR_TOLERANCE:
+      return 0.0
+    warnings.warn("Bad body_angle {0:n} and/or rel_angle {1:n}".format(body_angle,rel_angle))
+    return None
+  return angle
+
+def get_angle_diff(angle1, angle2):
+  return min(abs(angle1 - angle2),abs(angle1 - angle2 - 360),abs(angle2 - angle1 - 360))
+
+def reverse_angle(angle):
+  angle += 180
+  while angle >= 360:
+    angle -= 360
+  return angle
+
+def get_abs_x_y_pos(abs_angle, dist, self_x_pos, self_y_pos, warn=True, of_what=""):
+  poss_xy_pos_real = {}
+  max_deviation_xy_pos_real = {}
+  total_deviation_xy_pos_real = {}
+  dist_xy_pos_real = {}
+  self_x_pos_real = get_x_unnormalized(self_x_pos)
+  self_y_pos_real = get_y_unnormalized(self_y_pos)
+
+  start_string = ""
+  if of_what:
+    start_string = of_what + ': '
+
+  for angle in [(abs_angle-1),(abs_angle+1),abs_angle]:
+    angle_radians = math.radians(angle)
+    sin_angle = math.sin(angle_radians)
+    cos_angle = math.cos(angle_radians)
+
+
+    est_x_pos_real = (cos_angle*dist) + self_x_pos_real
+    est_y_pos_real = (sin_angle*dist) + self_y_pos_real
+    if ((MIN_REAL_X_VALID*(1-POS_ERROR_TOLERANCE))
+        <= est_x_pos_real <=
+        (MAX_REAL_X_VALID*(1+POS_ERROR_TOLERANCE))) and ((MIN_REAL_Y_VALID*(1-POS_ERROR_TOLERANCE))
+                                                         <= est_y_pos_real <=
+                                                         (MAX_REAL_Y_VALID*(1+POS_ERROR_TOLERANCE))):
+      poss_xy_pos_real[angle] = (est_x_pos_real, est_y_pos_real)
+
+      if est_x_pos_real < MIN_REAL_X_VALID:
+        x_deviation = (MIN_REAL_X_VALID-est_x_pos_real)/(MAX_REAL_X_VALID-MIN_REAL_X_VALID)
+      elif est_x_pos_real > MAX_REAL_X_VALID:
+        x_deviation = (est_x_pos_real-MAX_REAL_X_VALID)/(MAX_REAL_X_VALID-MIN_REAL_X_VALID)
+      else:
+        x_deviation = 0.0
+
+      if est_y_pos_real < MIN_REAL_Y_VALID:
+        y_deviation = (MIN_REAL_Y_VALID-est_y_pos_real)/(MAX_REAL_Y_VALID-MIN_REAL_Y_VALID)
+      elif est_y_pos_real > MAX_REAL_Y_VALID:
+        y_deviation = (est_y_pos_real-MAX_REAL_Y_VALID)/(MAX_REAL_Y_VALID-MIN_REAL_Y_VALID)
+      else:
+        y_deviation = 0.0
+
+      max_deviation_xy_pos_real[angle] = max(x_deviation,y_deviation)
+      total_deviation_xy_pos_real[angle] = x_deviation+y_deviation
+
+      dist_xy_pos_real[angle] = get_dist_from_real(est_x_pos_real,
+                                                   est_y_pos_real,
+                                                   min(MAX_REAL_X_VALID,
+                                                       max(MIN_REAL_X_VALID,
+                                                           est_x_pos_real)),
+                                                   min(MAX_REAL_Y_VALID,
+                                                       max(MIN_REAL_Y_VALID,
+                                                           est_y_pos_real)))
+
+    elif (angle == abs_angle) and (not poss_xy_pos_real):
+      error_strings = []
+      if not ((MIN_REAL_X_VALID*(1-POS_ERROR_TOLERANCE))
+              <= est_x_pos_real <=
+              (MAX_REAL_X_VALID*(1+POS_ERROR_TOLERANCE))):
+        error_strings.append(
+          "{0!s}Bad est_x_pos_real {1:n} from self_x_pos_real {2:n} (self_x_pos {3:n}), angle {4:n} ({5:n} {6:n}), dist {7:n}".format(
+            start_string,est_x_pos_real, self_x_pos_real, self_x_pos, abs_angle, sin_angle, cos_angle, dist))
+      if not ((MIN_REAL_Y_VALID*(1-POS_ERROR_TOLERANCE))
+              <= est_y_pos_real <=
+              (MAX_REAL_Y_VALID*(1+POS_ERROR_TOLERANCE))):
+        error_strings.append(
+          "{0!s}Bad est_y_pos_real {1:n} from self_y_pos_real {2:n} (self_y_pos {3:n}), angle {4:n} ({5:n} {6:n}), dist {7:n}".format(
+            start_string, est_y_pos_real, self_y_pos_real, self_y_pos, abs_angle, sin_angle, cos_angle, dist))
+      if dist < 10:
+        raise RuntimeError("\n".join(error_strings))
+      else:
+        if warn or (dist < 50):
+          print("\n".join(error_strings), file=sys.stderr)
+          sys.stderr.flush()
+        return (None, None)
+
+  poss_angles = list(poss_xy_pos_real.keys())
+  if len(poss_angles) > 1:
+    poss_angles.sort(key=lambda angle: abs(abs_angle-angle))
+    poss_angles.sort(key=lambda angle: total_deviation_xy_pos_real[angle])
+    poss_angles.sort(key=lambda angle: max_deviation_xy_pos_real[angle])
+    poss_angles.sort(key=lambda angle: dist_xy_pos_real[angle])
+  est_x_pos_real, est_y_pos_real = poss_xy_pos_real[poss_angles[0]]
+  if warn:
+    est_x_pos = get_x_normalized(est_x_pos_real)
+    est_y_pos = get_y_normalized(est_y_pos_real)
+  else:
+    est_x_pos = get_x_normalized(est_x_pos_real, silent=True)
+    est_y_pos = get_y_normalized(est_y_pos_real, silent=True)
+
+
+  if warn:
+    est_dist = get_dist_real(self_x_pos, self_y_pos,
+                             est_x_pos, est_y_pos)
+
+    if abs(dist - est_dist) > ERROR_TOLERANCE:
+      est2_dist = get_dist_from_real(self_x_pos_real, self_y_pos_real,
+                                     est_x_pos_real, est_y_pos_real)
+      print(
+        "{0!s}Difference in input ({1:n}), output ({2:n}) distances (est2 {3:n}) for get_abs_x_y_pos".format(
+          start_string, dist, est_dist, est2_dist),
+        file=sys.stderr)
+      print(
+        "Input angle {0:n}, used angle {1:n}, self_x_pos {2:n}, self_y_pos {3:n}".format(
+          abs_angle, poss_angles[0], self_x_pos, self_y_pos),
+        file=sys.stderr)
+      print(
+        "Self_x_pos_real {0:n}, self_y_pos_real {1:n}, est_x_pos_real {2:n}, est_y_pos_real {3:n}".format(
+          self_x_pos_real, self_y_pos_real, est_x_pos_real, est_y_pos_real), file=sys.stderr)
+      print("Est_x_pos {0:n}, est_y_pos {1:n}".format(est_x_pos, est_y_pos), file=sys.stderr)
+      sys.stderr.flush()
+
+  return (est_x_pos, est_y_pos)
+
+landmark_start_to_location = {13: (GOAL_POS_X, get_y_normalized(0.0)), # center of goal
+                              31: (get_x_normalized(0.0), get_y_normalized(0.0)), # center of RoboCup field
+                              34: (get_x_normalized(0.0), get_y_normalized(-0.5*HALF_FIELD_WIDTH)), # Top Left
+                              37: (get_x_normalized(HALF_FIELD_LENGTH), get_y_normalized(-0.5*HALF_FIELD_WIDTH)), # Top Right
+                              40: (get_x_normalized(HALF_FIELD_LENGTH), get_y_normalized(HALF_FIELD_WIDTH/2)), # Bottom Right
+                              43: (get_x_normalized(0,0), get_y_normalized(HALF_FIELD_WIDTH/2))} # Bottom Left
+
+
+def filter_low_level_state(state, namespace):
+  bit_list = []
+  for pos in (0, 1, 9, 11, 12, 50, 54, 10): # TODO: Replace with constants!
+    if state[pos] > 0:
+      bit_list.append(True)
+    else:
+      bit_list.append(False)
+##  if (state[0] > 0) and (max(abs(state[58]),abs(state[59]))
+##                         >= sys.float_info.epsilon): # player2 angle valid
+##    bit_list.append(True)
+##  else:
+##    bit_list.append(False)
+  if len(bit_list) != BIT_LIST_LEN:
+    raise RuntimeError(
+      "Length of bit_list {0:n} not same as BIT_LIST_LEN {1:n}".format(
+        len(bit_list), BIT_LIST_LEN))
+
+  self_dict = {}
+  if state[0] > 0: # self position valid
+
+    if max(abs(state[5]),abs(state[6])) <= sys.float_info.epsilon:
+      warnings.warn("Validity {0:d}{1:d} but invalid body angle {2:n}, {3:n}".format(
+        int(bit_list[0]),int(bit_list[1]),state[5],state[6]))
+      self_dict['body_angle'] = None
+    else:
+      self_dict['body_angle'] = get_angle(state[5],state[6])
+
+    x_pos_from = {}
+    x_pos_weight = {}
+    y_pos_from = {}
+    y_pos_weight = {}
+
+    x_pos1_real = get_dist_from_proximity(state[46],HALF_FIELD_LENGTH)
+    x_pos2_real = HALF_FIELD_LENGTH - get_dist_from_proximity(state[47],HALF_FIELD_LENGTH)
+    x_pos_from['OOB'] = get_x_normalized((x_pos1_real+x_pos2_real)/2)
+    x_pos_weight['OOB'] = max(ERROR_TOLERANCE,(1.0-min(1.0,abs(x_pos_from['OOB']))))
+
+    y_pos1_real = get_dist_from_proximity(state[48],HALF_FIELD_WIDTH) - (HALF_FIELD_WIDTH/2)
+    y_pos2_real = (HALF_FIELD_WIDTH/2) - get_dist_from_proximity(state[49],HALF_FIELD_WIDTH)
+    y_pos_from['OOB'] = get_y_normalized((y_pos1_real+y_pos2_real)/2)
+    y_pos_weight['OOB'] = max(ERROR_TOLERANCE,(1.0-min(1.0,abs(y_pos_from['OOB']))))
+
+    if (abs(namespace.other_dict['self_x_pos'].value) <= 1) and (abs(namespace.other_dict['self_y_pos'].value) <= 1):
+      x_pos_from['OTHER'] = namespace.other_dict['self_x_pos'].value
+      y_pos_from['OTHER'] = namespace.other_dict['self_y_pos'].value
+      other_proximity = get_proximity_from_dist(get_dist_real(x_pos_from['OTHER'],
+                                                              y_pos_from['OTHER'],
+                                                              namespace.other_dict['x_pos'].value,
+                                                              namespace.other_dict['y_pos'].value))
+      x_pos_weight['OTHER'] = y_pos_weight['OTHER'] = max(ERROR_TOLERANCE,(((other_proximity+1)/2)**2))
+      if abs(x_pos_from['OTHER']) > (1.0-ERROR_TOLERANCE):
+        x_pos_weight['OTHER'] = min(ERROR_TOLERANCE,x_pos_weight['OTHER'])
+      if abs(y_pos_from['OTHER']) > (1.0-ERROR_TOLERANCE):
+        y_pos_weight['OTHER'] = min(ERROR_TOLERANCE,y_pos_weight['OTHER'])
+
+    if self_dict['body_angle'] is not None:
+      for landmark_start, xy_location in iteritems(landmark_start_to_location):
+        rel_angle = get_angle(state[landmark_start],state[landmark_start+1])
+        abs_angle = get_abs_angle(self_dict['body_angle'],rel_angle)
+        if abs_angle is not None:
+          rev_angle = reverse_angle(abs_angle)
+          dist = get_dist_from_proximity(state[landmark_start+2])
+          x_pos, y_pos = get_abs_x_y_pos(abs_angle=rev_angle,
+                                         dist=dist,
+                                         self_x_pos=xy_location[0],
+                                         self_y_pos=xy_location[1],
+                                         warn=False,
+                                         of_what="Rev " + str(landmark_start))
+          if x_pos is not None:
+            x_pos_from[landmark_start] = x_pos
+            y_pos_from[landmark_start] = y_pos
+            x_pos_weight[landmark_start] = y_pos_weight[landmark_start] = max(ERROR_TOLERANCE,
+                                                                              (((state[landmark_start+2]+1)/2)**2))
+            # except extremely close can be a problem for the angle...
+            if state[landmark_start+2] > (1.0-(POS_ERROR_TOLERANCE/5)):
+              max_weight = (1.0-state[landmark_start+2])/(POS_ERROR_TOLERANCE/5)
+              max_weight = max(max_weight, ERROR_TOLERANCE)
+              x_pos_weight[landmark_start] = y_pos_weight[landmark_start] = min(x_pos_weight[landmark_start],
+                                                                                max_weight)
+
+    x_pos_total = 0.0
+    x_pos_weight_total = 0.0
+    for from_which, pos in iteritems(x_pos_from):
+      x_pos_total += pos*x_pos_weight[from_which]
+      x_pos_weight_total += x_pos_weight[from_which]
+    self_dict['x_pos'] = x_pos_total/x_pos_weight_total
+    for from_which, pos in iteritems(x_pos_from):
+      if ((x_pos_weight[from_which]/x_pos_weight_total)*abs(pos-self_dict['x_pos'])) > POS_ERROR_TOLERANCE:
+        print("Source {0!r} (weight {1:n}) x_pos {2:n} vs overall {3:n}".format(from_which,
+                                                                                x_pos_weight[from_which]/
+                                                                                x_pos_weight_total,
+                                                                                pos,
+                                                                                self_dict['x_pos']),
+              file=sys.stderr)
+        sys.stderr.flush()
+    y_pos_total = 0.0
+    y_pos_weight_total = 0.0
+    for from_which, pos in iteritems(y_pos_from):
+      y_pos_total += pos*y_pos_weight[from_which]
+      y_pos_weight_total += y_pos_weight[from_which]
+    self_dict['y_pos'] = y_pos_total/y_pos_weight_total
+    for from_which, pos in iteritems(y_pos_from):
+      if ((y_pos_weight[from_which]/y_pos_weight_total)*abs(pos-self_dict['y_pos'])) > POS_ERROR_TOLERANCE:
+        print("Source {0!r} (weight {1:n}) y_pos {2:n} vs overall {3:n}".format(from_which,
+                                                                                y_pos_weight[from_which]/
+                                                                                y_pos_weight_total,
+                                                                                pos,
+                                                                                self_dict['y_pos']),
+              file=sys.stderr)
+        sys.stderr.flush()
+
+    if max(abs(state[58]),abs(state[59])) <= sys.float_info.epsilon:
+      warnings.warn("Validity {0:d}{1:d} but invalid other angle {2:n}, {3:n}".format(
+        int(bit_list[0]),int(bit_list[1]),state[58],state[59]))
+      self_dict['other_angle'] = None
+    else:
+      self_dict['other_angle'] = get_angle(state[58],state[59])
+  else:
+    self_dict['x_pos'] = None
+    self_dict['y_pos'] = None
+    if max(abs(state[5]),abs(state[6])) > sys.float_info.epsilon:
+      warnings.warn("Validity {0:d}{1:d} but valid body angle {2:n}, {3:n}".format(
+        int(bit_list[0]),int(bit_list[1]),state[5],state[6]))
+      self_dict['body_angle'] = get_angle(state[5],state[6])
+    else:
+      self_dict['body_angle'] = None
+    if max(abs(state[58]),abs(state[59])) > sys.float_info.epsilon:
+      warnings.warn("Validity {0:d}{1:d} but valid other angle {2:n}, {3:n}".format(
+        int(bit_list[0]),int(bit_list[1]),state[58],state[59]))
+      self_dict['other_angle'] = get_angle(state[58],state[59])
+    else:
+      self_dict['other_angle'] = None
+
+  if state[1] > 0: # self velocity valid
+    self_dict['vel_rel_angle'] = get_angle(state[2],state[3])
+    self_dict['vel_mag'] = state[4]
+  else:
+    self_dict['vel_rel_angle'] = None
+    self_dict['vel_mag'] = -1
+
+  goal_dict = {}
+  if (state[0] > 0) and (max(state[18],state[21]) > -1): # self position valid, goal pos possible
+    if state[18] > state[21]: # top post closer
+      which_goal = "Top"
+      goal_dict['dist'] = get_dist_from_proximity(state[18])
+      goal_dict['rel_angle'] = get_angle(state[16],state[17])
+    else:
+      which_goal = "Bottom"
+      goal_dict['dist'] = get_dist_from_proximity(state[21])
+      goal_dict['rel_angle'] = get_angle(state[19],state[20])
+    if (state[11] < 0) and (goal_dict['rel_angle']
+                            is not None) and (goal_dict['dist']
+                                              < (0.15+0.03)) and (get_angle_diff(0.0,
+                                                                                goal_dict['rel_angle'])
+                                                                 <= 1.0):
+      raise RuntimeError("Should be in collision with goal - distance is {0:n}".format(goal_dict['dist']))
+    elif goal_dict['dist'] > (MAX_RADIUS-POS_ERROR_TOLERANCE):
+      raise RuntimeError(
+        "Should not be possible to have dist {0:n} for goal (state[18] {1:n}, state[21] {2:n})".format(
+          goal_dict['dist'], state[18], state[21]))
+    goal_dict['abs_angle'] = get_abs_angle(self_dict['body_angle'],goal_dict['rel_angle'])
+    if goal_dict['abs_angle'] is not None:
+
+      est_x_pos, est_y_pos = get_abs_x_y_pos(abs_angle=goal_dict['abs_angle'],
+                                             dist=goal_dict['dist'],
+                                             self_x_pos=self_dict['x_pos'],
+                                             self_y_pos=self_dict['y_pos'],
+                                             warn=bool(goal_dict['dist'] < 10),
+                                             of_what=which_goal+" goal post")
+
+      if est_x_pos is not None:
+        if (abs(est_x_pos - GOAL_POS_X) > POS_ERROR_TOLERANCE) and (goal_dict['dist'] < 10):
+          print(
+            "Estimated x_pos of goal is {0:n} (should be {1:n}; from est abs_angle {2:n}, dist {3:n}, self_x_pos {4:n})".format(
+              est_x_pos,GOAL_POS_X,goal_dict['abs_angle'],goal_dict['dist'],self_dict['x_pos']),
+            file=sys.stderr)
+          sys.stderr.flush()
+      goal_dict['x_pos'] = GOAL_POS_X
+      if (state[18] > state[21]):
+        goal_dict['y_pos'] = GOAL_TOP_POS_Y
+        if est_y_pos is not None:
+          if (abs(est_y_pos-GOAL_TOP_POS_Y) > POS_ERROR_TOLERANCE) and (goal_dict['dist'] < 10):
+            print(
+              "Estimated y_pos of top goalpost is {0:n} (should be {1:n}; from est abs_angle {2:n}, dist {3:n}, self_y_pos {4:n})".format(
+                est_y_pos,GOAL_TOP_POS_Y,goal_dict['abs_angle'],goal_dict['dist'],self_dict['y_pos']),
+              file=sys.stderr)
+            sys.stderr.flush()
+      else:
+        goal_dict['y_pos'] = GOAL_BOTTOM_POS_Y
+        if est_y_pos is not None:
+          if (abs(est_y_pos-GOAL_BOTTOM_POS_Y) > POS_ERROR_TOLERANCE) and (goal_dict['dist'] < 10):
+            print(
+              "Estimated y_pos of bottom goalpost is {0:n} (should be {1:n}; from est abs_angle {2:n}, dist {3:n}, self_y_pos {4:n})".format(
+                est_y_pos,GOAL_BOTTOM_POS_Y,goal_dict['abs_angle'],goal_dict['dist'],self_dict['y_pos']),
+              file=sys.stderr)
+            sys.stderr.flush()
+      if (state[11] < 0) and (get_angle_diff(0.0, goal_dict['rel_angle'])
+                              <= 1.0) and (get_dist_real(self_dict['x_pos'],
+                                                         self_dict['y_pos'],
+                                                         goal_dict['x_pos'],
+                                                         goal_dict['y_pos']) < (0.15+0.03)):
+        print(
+          "Should be in collision with goal ({0:n}, {1:n} vs {2:n}, {3:n}; angle {4:n})".format(
+            self_dict['x_pos'],self_dict['y_pos'],
+            goal_dict['x_pos'],goal_dict['y_pos'],
+            goal_dict['rel_angle']),
+          file=sys.stderr)
+        sys.stderr.flush()
+    else:
+      goal_dict['x_pos'] = GOAL_POS_X
+      if state[18] > state[21]:
+        goal_dict['y_pos'] = GOAL_TOP_POS_Y
+      else:
+        goal_dict['y_pos'] = GOAL_BOTTOM_POS_Y
+
+  else:
+    goal_dict['dist'] = get_dist_from_proximity(-1)
+    goal_dict['rel_angle'] = None
+    goal_dict['abs_angle'] = None
+    goal_dict['x_pos'] = GOAL_POS_X
+    if random.random() < 0.5:
+      goal_dict['y_pos'] = GOAL_BOTTOM_POS_Y
+    else:
+      goal_dict['y_pos'] = GOAL_TOP_POS_Y
+
+  ball_dict = {}
+  if state[50] > 0: # ball position valid
+    ball_dict['dist'] = get_dist_from_proximity(state[53])
+    ball_dict['rel_angle'] = get_angle(state[51],state[52])
+    if (state[9] < 0) and (ball_dict['rel_angle']
+                           is not None) and (ball_dict['dist']
+                                             < (0.15+0.0425)) and (get_angle_diff(0.0, ball_dict['rel_angle'])
+                                                                 <= 1.0):
+      raise RuntimeError("Should be in collision with ball - distance is {0:n}".format(ball_dict['dist']))
+    ball_dict['abs_angle'] = get_abs_angle(self_dict['body_angle'],ball_dict['rel_angle'])
+    if ball_dict['abs_angle'] is not None:
+      ball_dict['x_pos'], ball_dict['y_pos'] = get_abs_x_y_pos(abs_angle=ball_dict['abs_angle'],
+                                                               dist=ball_dict['dist'],
+                                                               self_x_pos=self_dict['x_pos'],
+                                                               self_y_pos=self_dict['y_pos'],
+                                                               warn=bool(ball_dict['dist'] < 10),
+                                                               of_what='Ball')
+      if (ball_dict['x_pos'] is None) or (ball_dict['y_pos'] is None):
+        if (ball_dict['dist'] < 10):
+          raise RuntimeError("Unknown ball position despite state[50] > 0")
+      else:
+        if (state[9] < 0) and (get_angle_diff(0.0, ball_dict['rel_angle'])
+                               <= 1.0) and (get_dist_real(self_dict['x_pos'],self_dict['y_pos'],
+                                                          ball_dict['x_pos'],ball_dict['y_pos']) < (0.15+0.0425)):
+          raise RuntimeError(
+            "Should be in collision with ball ({0:n}, {1:n} vs {2:n}, {3:n}; rel_angle {4:n})".format(
+              self_dict['x_pos'],self_dict['y_pos'],
+              ball_dict['x_pos'],ball_dict['y_pos'],
+              ball_dict['rel_angle']))
+  else:
+    ball_dict['dist'] = get_dist_from_proximity(-1)
+    ball_dict['rel_angle'] = None
+    ball_dict['abs_angle'] = None
+    ball_dict['x_pos'] = None
+    ball_dict['y_pos'] = None
+
+  if (ball_dict['x_pos'] is None) and (abs(namespace.other_dict['ball_x_pos'].value) <= 1):
+    ball_dict['x_pos'] = namespace.other_dict['ball_x_pos'].value
+  if (ball_dict['y_pos'] is None) and (abs(namespace.other_dict['ball_y_pos'].value) <= 1):
+    ball_dict['y_pos'] = namespace.other_dict['ball_y_pos'].value
+
+  if state[54] > 0: # ball velocity valid
+    ball_dict['vel_rel_angle'] = get_angle(state[55],state[56])
+    ball_dict['vel_mag'] = state[57]
+  else:
+    ball_dict['vel_rel_angle'] = None
+    ball_dict['vel_mag'] = 0
+
+  return (bit_list, self_dict, goal_dict, ball_dict)
+
+
+def bool_list_to_int(bit_list):
+  return sum(2**i*b for i, b in enumerate(bit_list))
+
+def int_to_bool_list(intval, num_bits=BIT_LIST_LEN):
+  if num_bits < intval.bit_length():
+    raise RuntimeError(
+      "Integer value {0:d} will not fit into {1:d} bits (needs {2:d})".format(intval,
+                                                                              num_bits,
+                                                                              intval.bit_length()))
+  result = []
+  for bit in range(num_bits):
+    mask = 1 << bit
+    result.append(bool((intval & mask) == mask))
+  return result
+
+def pack_action_bit_list(action, bit_list):
+  bool_int = bool_list_to_int(bit_list)
+
+  return struct.pack(STRUCT_PACK, action, bool_int)
+
+def unpack_action_bit_list(bytestring):
+  action, bool_int = struct.unpack(STRUCT_PACK, bytestring)
+
+  bit_list = int_to_bool_list(bool_int)
+
+  return (action, bit_list)
+
+
+def evaluate_previous_action(hfo_env,
+                             state,
+                             namespace):
+  if namespace.action in (hfo.NOOP, hfo.QUIT):
+    warnings.warn("evaluate_previous_action should not have been called with 'action' NOOP/QUIT")
+    return
+  bit_list, self_dict, goal_dict, ball_dict = filter_low_level_state(state, namespace)
+  action_status = hfo_env.getLastActionStatus(namespace.action)
+
+  action_string = hfo_env.actionToString(namespace.action)
+  if namespace.action_params:
+    action_string += '(' + ",".join(list(["{:n}".format(x) for x in namespace.action_params])) + ')'
+
+  action_status_observed = hfo.ACTION_STATUS_UNKNOWN # NOTE: Check intent + other bitflags!
+
+
+  if (namespace.action in
+      (hfo.DRIBBLE, hfo.DRIBBLE_TO)) and namespace.prestate_bit_list[4] and (not bit_list[4]):
+    action_status_observed = hfo.ACTION_STATUS_BAD
+  elif (namespace.action in
+        (hfo.DRIBBLE,
+         hfo.INTERCEPT,
+         hfo.MOVE)) and (((namespace.intent != INTENT_GOAL_COLLISION)
+                          and (not namespace.prestate_bit_list[3])
+                          and bit_list[3]) or
+                         ((namespace.intent != INTENT_BALL_COLLISION)
+                          and (not namespace.prestate_bit_list[2])
+                          and bit_list[2]) or
+                         ((namespace.intent != INTENT_PLAYER_COLLISION)
+                          and (not namespace.prestate_bit_list[7])
+                          and bit_list[7])):
+    action_status_observed = hfo.ACTION_STATUS_BAD
+  elif (namespace.intent !=
+      INTENT_GOAL_COLLISION) and namespace.prestate_bit_list[3] and (not bit_list[3]):
+    action_status_observed = hfo.ACTION_STATUS_MAYBE
+  elif (namespace.intent !=
+        INTENT_BALL_COLLISION) and namespace.prestate_bit_list[2] and (not bit_list[2]):
+    action_status_observed = hfo.ACTION_STATUS_MAYBE
+  elif (namespace.intent !=
+        INTENT_PLAYER_COLLISION) and namespace.prestate_bit_list[7] and (not bit_list[7]):
+    action_status_observed = hfo.ACTION_STATUS_MAYBE
+  elif (namespace.intent == INTENT_BALL_KICKABLE) and (not namespace.prestate_bit_list[4]) and bit_list[4]:
+    action_status_observed = hfo.ACTION_STATUS_MAYBE
+  elif (namespace.intent == INTENT_GOAL_COLLISION) and (not namespace.prestate_bit_list[3]) and bit_list[3]:
+    action_status_observed = hfo.ACTION_STATUS_MAYBE
+  elif (namespace.intent == INTENT_BALL_COLLISION) and (((not namespace.prestate_bit_list[2]) and bit_list[2]) or
+                                                        ((not namespace.prestate_bit_list[4]) and bit_list[4])):
+    action_status_observed = hfo.ACTION_STATUS_MAYBE
+  elif (namespace.intent == INTENT_PLAYER_COLLISION) and (not namespace.prestate_bit_list[7]) and bit_list[7]:
+    action_status_observed = hfo.ACTION_STATUS_MAYBE
+  elif (namespace.action == hfo.DASH) or (namespace.action == hfo.MOVE) or (namespace.action == hfo.REORIENT):
+      pass # TODO
+  elif namespace.action == hfo.TURN:
+    if (namespace.prestate_self_dict['body_angle'] is not None) and (self_dict['body_angle'] is not None):
+      intended_body_angle = namespace.prestate_self_dict['body_angle'] + namespace.action_params[0]
+      if get_angle_diff(namespace.prestate_self_dict['body_angle'],
+                        intended_body_angle) > get_angle_diff(self_dict['body_angle'],
+                                                              intended_body_angle):
+        action_status_observed = hfo.ACTION_STATUS_MAYBE
+      else:
+        action_status_observed = hfo.ACTION_STATUS_BAD
+  elif namespace.action == hfo.PASS:
+    if (namespace.prestate_ball_dict['x_pos'] is not None) and (ball_dict['x_pos'] is not None):
+      dist_after = get_dist_real(ball_dict['x_pos'],
+                                 ball_dict['y_pos'],
+                                 namespace.other_dict['x_pos'].value,
+                                 namespace.other_dict['y_pos'].value)
+      self_dist_after = get_dist_real(ball_dict['x_pos'],
+                                      ball_dict['y_pos'],
+                                      self_dict['x_pos'],
+                                      self_dict['y_pos'])
+      if (dist_after < self_dist_after):
+        action_status_observed = hfo.ACTION_STATUS_MAYBE
+      else:
+        action_status_observed = hfo.ACTION_STATUS_BAD
+    elif bit_list[4]:
+      action_status_observed = hfo.ACTION_STATUS_BAD
+  elif namespace.action == hfo.KICK:
+    if bit_list[4]:
+      action_status_observed = hfo.ACTION_STATUS_BAD
+    else:
+      pass # TODO
+  elif namespace.action == hfo.KICK_TO:
+    if (namespace.prestate_ball_dict['x_pos'] is not None) and (ball_dict['x_pos'] is not None):
+      dist_before = get_dist_real(namespace.prestate_ball_dict['x_pos'],
+                                  namespace.prestate_ball_dict['y_pos'],
+                                  namespace.action_params[0],
+                                  namespace.action_params[1])
+      dist_after = get_dist_real(ball_dict['x_pos'],
+                                 ball_dict['y_pos'],
+                                 namespace.action_params[0],
+                                 namespace.action_params[1])
+      if dist_before > dist_after:
+        action_status_observed = hfo.ACTION_STATUS_MAYBE
+      else:
+        action_status_observed = hfo.ACTION_STATUS_BAD
+  elif namespace.action == hfo.MOVE_TO:
+    if (namespace.prestate_self_dict['x_pos'] is not None) and (self_dict['x_pos'] is not None):
+      dist_before = get_dist_real(namespace.prestate_self_dict['x_pos'],
+                                  namespace.prestate_self_dict['y_pos'],
+                                  namespace.action_params[0],
+                                  namespace.action_params[1])
+      dist_after = get_dist_real(self_dict['x_pos'],
+                                 self_dict['y_pos'],
+                                 namespace.action_params[0],
+                                 namespace.action_params[1])
+      if dist_before > dist_after:
+        action_status_observed = hfo.ACTION_STATUS_MAYBE
+      else:
+        action_status_observed = hfo.ACTION_STATUS_BAD
+    else:
+      pass # ???
+  elif namespace.action == hfo.DRIBBLE_TO:
+    if namespace.prestate_bit_list[4]:
+      if not bit_list[4]:
+        action_status_observed = hfo.ACTION_STATUS_BAD # should have been done above, actually
+      elif (namespace.prestate_self_dict['x_pos'] is not None) and (self_dict['x_pos'] is not None):
+        dist_before = get_dist_real(namespace.prestate_self_dict['x_pos'],
+                                    namespace.prestate_self_dict['y_pos'],
+                                    namespace.action_params[0],
+                                    namespace.action_params[1])
+        dist_after = get_dist_real(self_dict['x_pos'],
+                                   self_dict['y_pos'],
+                                   namespace.action_params[0],
+                                   namespace.action_params[1])
+        if dist_before > dist_after:
+          action_status_observed = hfo.ACTION_STATUS_MAYBE
+        else:
+          action_status_observed = hfo.ACTION_STATUS_BAD
+      elif (namespace.prestate_ball_dict['x_pos'] is not None) and (ball_dict['x_pos'] is not None):
+        dist_before = get_dist_real(namespace.prestate_ball_dict['x_pos'],
+                                    namespace.prestate_ball_dict['y_pos'],
+                                    namespace.action_params[0],
+                                    namespace.action_params[1])
+        dist_after = get_dist_real(ball_dict['x_pos'],
+                                   ball_dict['y_pos'],
+                                   namespace.action_params[0],
+                                   namespace.action_params[1])
+        if dist_before > dist_after:
+          action_status_observed = hfo.ACTION_STATUS_MAYBE
+        else:
+          action_status_observed = hfo.ACTION_STATUS_BAD
+      else:
+        pass # ???
+    elif bit_list[4]:
+      action_status_observed = hfo.ACTION_STATUS_MAYBE
+    elif namespace.prestate_ball_dict['dist'] > ball_dict['dist']:
+      action_status_observed = hfo.ACTION_STATUS_MAYBE
+    elif namespace.prestate_ball_dict['dist'] < ball_dict['dist']:
+      action_status_observed = hfo.ACTION_STATUS_BAD
+    elif ball_dict['dist'] < ((1-ERROR_TOLERANCE)*MAX_RADIUS):
+      action_status_observed = hfo.ACTION_STATUS_BAD
+  elif namespace.action in (hfo.INTERCEPT, hfo.GO_TO_BALL):
+    if namespace.prestate_bit_list[4]:
+      if bit_list[4]:
+        if (namespace.intent != INTENT_BALL_COLLISION) and bit_list[2]:
+          action_status_observed = hfo.ACTION_STATUS_BAD
+        elif (namespace.intent != INTENT_GOAL_COLLISION) and bit_list[3]:
+          action_status_observed = hfo.ACTION_STATUS_BAD
+        else:
+          action_status_observed = hfo.ACTION_STATUS_MAYBE
+      else:
+        action_status_observed = hfo.ACTION_STATUS_BAD
+    elif bit_list[4]:
+      action_status_observed = hfo.ACTION_STATUS_MAYBE
+    elif namespace.prestate_ball_dict['dist'] > ball_dict['dist']:
+      action_status_observed = hfo.ACTION_STATUS_MAYBE
+    elif namespace.prestate_ball_dict['dist'] < ball_dict['dist']:
+      action_status_observed = hfo.ACTION_STATUS_BAD
+    elif ball_dict['dist'] < ((1-ERROR_TOLERANCE)*MAX_RADIUS):
+      action_status_observed = hfo.ACTION_STATUS_BAD
+  elif namespace.action == hfo.DRIBBLE:
+    pass # todo?
+  else:
+    raise RuntimeError("Unknown how to evaluate {0!s}".format(action_string))
+
+  if action_status_observed == hfo.ACTION_STATUS_UNKNOWN:
+    action_status_guessed = action_status
+  else:
+    action_status_guessed = action_status_observed
+    if (action_status != action_status_observed) and (action_status != hfo.ACTION_STATUS_UNKNOWN):
+      print(
+        "{0!s}: Difference between feedback ({1!s}), observed ({2!s}) action_status (prestate bit_list {3!s}, current bit list {4!s})".format(
+          action_string,
+          hfo_env.actionStatusToString(action_status),
+          hfo_env.actionStatusToString(action_status_observed),
+          "".join(map(str,map(int,namespace.prestate_bit_list))),
+          "".join(map(str,map(int,bit_list)))),
+        file=sys.stderr)
+      sys.stderr.flush()
+
+  if (action_status_guessed == hfo.ACTION_STATUS_BAD) and (not namespace.checking_intent):
+    # unexpected lack of success
+    print(
+      "Unexpected lack of success for last action {0!s} (prestate bit_list {1!s}, current bit list {2!s})".format(
+        action_string,
+        "".join(map(str,map(int,namespace.prestate_bit_list))),
+        "".join(map(str,map(int,bit_list)))),
+      file=sys.stderr)
+    if namespace.intent == INTENT_GOAL_COLLISION:
+      print("Prestate goal distance is {0:n}, current goal distance is {1:n}".format(namespace.prestate_goal_dict['dist'],
+                                                                                     goal_dict['dist']),
+            file=sys.stderr)
+    elif namespace.intent == INTENT_BALL_COLLISION:
+      print("Prestate ball distance is {0:n}, current ball distance is {1:n}".format(namespace.prestate_ball_dict['dist'],
+                                                                                     ball_dict['dist']),
+            file=sys.stderr)
+    sys.stderr.flush()
+    namespace.action_tried[namespace.action] += 1
+    namespace.struct_tried[pack_action_bit_list(namespace.action,namespace.prestate_bit_list)] += 1
+    # further analysis...
+
+  if action_status_guessed == hfo.ACTION_STATUS_MAYBE:
+    # further analysis...
+    if (not namespace.prestate_bit_list[0]) and (namespace.action in (hfo.KICK_TO, hfo.MOVE_TO, hfo.DRIBBLE_TO, hfo.INTERCEPT,
+                                                                      hfo.MOVE, hfo.PASS, hfo.GO_TO_BALL)):
+      print("OK status from {0!s} despite unknown self location in prestate (poststate: {1!s})".format(
+        action_string, bit_list[0]))
+      sys.stdout.flush()
+
+    if (not namespace.prestate_bit_list[1]) and (namespace.action in (hfo.DASH, hfo.TURN, hfo.KICK_TO, hfo.MOVE_TO,
+                                                                      hfo.DRIBBLE_TO, hfo.INTERCEPT, hfo.MOVE, hfo.PASS,
+                                                                      hfo.GO_TO_BALL)):
+      print("OK status from {0!s} despite unknown self velocity in prestate (poststate: {1!s})".format(
+        action_string, bit_list[1]))
+      sys.stdout.flush()
+
+    if (not namespace.prestate_bit_list[5]) and (namespace.action in (hfo.KICK, hfo.KICK_TO, hfo.DRIBBLE_TO, hfo.INTERCEPT,
+                                                                      hfo.MOVE, hfo.PASS, hfo.GO_TO_BALL)):
+      print("OK status from {0!s} despite unknown ball location in prestate (poststate: {1!s})".format(
+        action_string, bit_list[0]))
+      sys.stdout.flush()
+
+    if (not namespace.prestate_bit_list[6]) and (namespace.action in (hfo.KICK, hfo.KICK_TO,
+                                                                      hfo.PASS, hfo.DRIBBLE)):
+      print("OK status from {0!s} despite unknown ball velocity in prestate (poststate: {1!s})".format(
+        action_string, bit_list[1]))
+      sys.stdout.flush()
+
+    if (namespace.action == hfo.REORIENT) and (namespace.prestate_bit_list[2] or
+                                               namespace.prestate_bit_list[3] or
+                                               namespace.prestate_bit_list[7]):
+      print("OK status from {0!s} despite collision in prestate {1!s} (poststate: {2!s})".format(
+        action_string,
+        "".join(map(str,map(int,namespace.prestate_bit_list))),
+        "".join(map(str,map(int,bit_list)))))
+      sys.stdout.flush()
+  elif action_status_guessed == hfo.ACTION_STATUS_BAD:
+    if namespace.action in (hfo.REORIENT, hfo.TURN, hfo.DRIBBLE):
+      print("Bad status from {0!s}: prestate {1!s}, poststate {2!s}".format(
+        action_string,
+        "".join(map(str,map(int,namespace.prestate_bit_list))),
+        "".join(map(str,map(int,bit_list)))))
+      sys.stdout.flush()
+
+def save_action_prestate(action,
+                         prestate_bit_list,
+                         prestate_self_dict,
+                         prestate_goal_dict,
+                         prestate_ball_dict,
+                         intent,
+                         checking_intent,
+                         namespace,
+                         action_params=None):
+  namespace.action = action
+  namespace.prestate_bit_list = prestate_bit_list
+  namespace.prestate_self_dict = prestate_self_dict
+  namespace.prestate_goal_dict = prestate_goal_dict
+  namespace.prestate_ball_dict = prestate_ball_dict
+  if action_params is None:
+    action_params = []
+  namespace.action_params = action_params
+  namespace.intent = intent
+  if intent is not None:
+    namespace.intent_done[intent] += 1
+  namespace.checking_intent = checking_intent
+  if checking_intent:
+    namespace.action_tried[action] += 1
+    namespace.struct_tried[pack_action_bit_list(action, prestate_bit_list)] += 1
+  if action_params:
+    return [action] + action_params
+  return action
+
+def determine_which_action(poss_actions_list, namespace, bit_list):
+  """
+  Decides between poss_actions based on lowest struct_tried,
+  then lowest action_tried, then random.
+  """
+  if len(poss_actions_list) > 1:
+    random.shuffle(poss_actions_list)
+    poss_actions_list.sort(key=lambda action: namespace.struct_tried[pack_action_bit_list(action, bit_list)])
+   
+    if (namespace.struct_tried[pack_action_bit_list(poss_actions_list[0],
+                                                    bit_list)]
+        == namespace.struct_tried[pack_action_bit_list(poss_actions_list[1], bit_list)]):
+      poss_actions_list.sort(key=lambda action: namespace.action_tried[action])
+
+  return poss_actions_list[0]
+
+def do_intent(hfo_env,
+              state,
+              namespace):
+  bit_list, self_dict, goal_dict, ball_dict = filter_low_level_state(state, namespace)
+  prestate_dict = {'prestate_bit_list': bit_list,
+                   'prestate_self_dict': self_dict,
+                   'prestate_goal_dict': goal_dict,
+                   'prestate_ball_dict': ball_dict,
+                   'checking_intent': False,
+                   'intent': namespace.intent,
+                   'namespace': namespace}
+
+  if (namespace.intent == INTENT_BALL_COLLISION) and bit_list[4]: # ball is kickable
+    ball_rel_angle = ball_dict['rel_angle']
+    if ball_rel_angle > 180:
+      ball_rel_angle -= 360
+    poss_actions_list = []
+    if bit_list[0] and (ball_dict['x_pos'] is not None): # self position valid
+      if get_dist_real(self_dict['x_pos'],self_dict['y_pos'],
+                       ball_dict['x_pos'],ball_dict['y_pos']) < (0.15+0.0425):
+        if abs(ball_rel_angle) > 0.5:
+          hfo_env.act(*save_action_prestate(action=hfo.TURN,action_params=[ball_rel_angle],
+                                            **prestate_dict))
+        else:
+          print(
+            "Should already be in collision with ball ({0:n}, {1:n} vs {2:n}, {3:n})".format(
+              self_dict['x_pos'],self_dict['y_pos'],
+              ball_dict['x_pos'],ball_dict['y_pos']),
+            file=sys.stderr)
+          sys.stderr.flush()
+          hfo_env.act(*save_action_prestate(action=hfo.DASH,
+                                            action_params=[80, ball_rel_angle],
+                                            **prestate_dict))
+        return
+
+      poss_actions_list.append(hfo.MOVE_TO)
+
+    if abs(ball_rel_angle) > 0.5:
+      poss_actions_list.append(hfo.TURN)
+    if abs(ball_rel_angle) < 1.0:
+      poss_actions_list.append(hfo.DASH)
+
+    action = determine_which_action(poss_actions_list, namespace, bit_list)
+
+    if action == hfo.MOVE_TO:
+      hfo_env.act(*save_action_prestate(action=hfo.MOVE_TO,
+                                        action_params=[ball_dict['x_pos'],
+                                                       ball_dict['y_pos']],
+                                        **prestate_dict))
+    elif action == hfo.TURN:
+      hfo_env.act(*save_action_prestate(action=hfo.TURN,
+                                        action_params=[ball_rel_angle],
+                                        **prestate_dict))
+    elif action == hfo.DASH:
+      hfo_env.act(*save_action_prestate(action=hfo.DASH,
+                                        action_params=[80, ball_rel_angle],
+                                        **prestate_dict))
+    else:
+      raise RuntimeError("Unknown action {0!r}".format(action))
+
+    return
+
+
+  elif (namespace.intent == INTENT_BALL_KICKABLE) or (namespace.intent == INTENT_BALL_COLLISION):
+    poss_actions_list = [hfo.INTERCEPT]
+    if bit_list[0]:
+      poss_actions_list.append(hfo.GO_TO_BALL)
+
+    ball_rel_angle = ball_dict['rel_angle']
+    if ball_rel_angle > 180:
+      ball_rel_angle -= 360
+    if abs(ball_rel_angle) > 0.5:
+      poss_actions_list.append(hfo.TURN)
+    if abs(ball_rel_angle) < 1.0:
+      poss_actions_list.append(hfo.DASH)
+    if bit_list[0] and (ball_dict['x_pos'] is not None):
+      poss_actions_list.append(hfo.MOVE_TO)
+
+    action = determine_which_action(poss_actions_list, namespace, bit_list)
+
+    if (action == hfo.GO_TO_BALL) or (action == hfo.INTERCEPT):
+      hfo_env.act(save_action_prestate(action=action,**prestate_dict))
+    elif action == hfo.TURN:
+      hfo_env.act(*save_action_prestate(action=hfo.TURN,
+                                        action_params=[ball_rel_angle],
+                                        **prestate_dict))
+    elif action == hfo.DASH:
+      hfo_env.act(*save_action_prestate(action=hfo.DASH,
+                                        action_params=[80, ball_rel_angle],
+                                        **prestate_dict))
+    elif action == hfo.MOVE_TO:
+      hfo_env.act(*save_action_prestate(action=hfo.MOVE_TO,
+                                        action_params=[ball_dict['x_pos'],
+                                                       ball_dict['y_pos']],
+                                        **prestate_dict))
+    else:
+      raise RuntimeError("Unknown action {0!r}".format(action))
+
+    return
+
+  elif namespace.intent == INTENT_GOAL_COLLISION:
+    goal_rel_angle = goal_dict['rel_angle']
+    if goal_rel_angle > 180:
+      goal_rel_angle -= 360
+    poss_actions_list = []
+
+    if (get_dist_real(self_dict['x_pos'],
+                      self_dict['y_pos'],
+                      goal_dict['x_pos'],
+                      goal_dict['y_pos']) < (0.15+0.03)):
+      if abs(goal_rel_angle) > 0.5:
+        hfo_env.act(*save_action_prestate(action=hfo.TURN,
+                                          action_params=[goal_rel_angle],
+                                          **prestate_dict))
+      else:
+        print(
+          "Should already be in collision with goal ({0:n}, {1:n} vs {2:n}, {3:n})".format(
+            self_dict['x_pos'],self_dict['y_pos'],
+            goal_dict['x_pos'],goal_dict['y_pos']),
+          file=sys.stderr)
+        sys.stderr.flush()
+        if not bit_list[4]:
+          hfo_env.act(*save_action_prestate(action=hfo.DASH,
+                                            action_params=[80, goal_rel_angle],
+                                            **prestate_dict))
+        else:
+          hfo_env.act(*save_action_prestate(action=hfo.DRIBBLE_TO,
+                                            action_params=[min(1.0,max(-1.0,(self_dict['x_pos']+
+                                                                             (2*(goal_dict['x_pos']-
+                                                                                 self_dict['x_pos']))))),
+                                                           min(1.0,max(-1.0,(self_dict['y_pos']+
+                                                                             (2*(goal_dict['y_pos']-
+                                                                                 self_dict['y_pos'])))))],
+                                            **prestate_dict))
+
+      return
+
+    poss_actions_list.append(hfo.DRIBBLE_TO)
+    if not bit_list[4]:
+      poss_actions_list.append(hfo.MOVE_TO)
+
+    if goal_rel_angle is not None:
+      if abs(goal_rel_angle) > 0.5:
+        poss_actions_list.append(hfo.TURN)
+      if (abs(goal_rel_angle) < 1.0) and ((not bit_list[4]) or (goal_dict['dist'] < 0.36)):
+        poss_actions_list.append(hfo.DASH)
+
+    action = determine_which_action(poss_actions_list, namespace, bit_list)
+
+    if (action == hfo.TURN):
+      hfo_env.act(*save_action_prestate(action=hfo.TURN,
+                                        action_params=[goal_rel_angle],
+                                        **prestate_dict))
+    elif action in (hfo.DRIBBLE_TO, hfo.MOVE_TO):
+      x_pos_target = min(1.0,(goal_dict['x_pos']+POS_ERROR_TOLERANCE),
+                         max(-1.0,(goal_dict['x_pos']-POS_ERROR_TOLERANCE),
+                             (self_dict['x_pos']+(2*(goal_dict['x_pos']-self_dict['x_pos'])))))
+      y_pos_target = min(1.0,(goal_dict['y_pos']+POS_ERROR_TOLERANCE),
+                         max(-1.0,(goal_dict['y_pos']-POS_ERROR_TOLERANCE),
+                             (self_dict['y_pos']+(2*(goal_dict['y_pos']-self_dict['y_pos'])))))
+      hfo_env.act(*save_action_prestate(action=action,
+                                        action_params=[x_pos_target,y_pos_target],
+                                        **prestate_dict))
+    elif (action == hfo.DASH):
+      hfo_env.act(*save_action_prestate(action=hfo.DASH,
+                                        action_params=[80, goal_rel_angle],
+                                        **prestate_dict))
+    else:
+      raise RuntimeError("Unknown action {0!r}".format(action))
+
+    return
+
+  elif namespace.intent == INTENT_PLAYER_COLLISION:
+    rel_angle = self_dict['other_angle']
+    if rel_angle > 180:
+      rel_angle -= 360
+
+    other_dist = get_dist_real(self_dict['x_pos'],
+                               self_dict['y_pos'],
+                               namespace.other_dict['x_pos'].value,
+                               namespace.other_dict['y_pos'].value)
+
+    if (other_dist < 0.3):
+      if abs(rel_angle) > 0.5:
+        hfo_env.act(*save_action_prestate(action=hfo.TURN,
+                                          action_params=[rel_angle],
+                                          **prestate_dict))
+      else:
+        print(
+          "Should already be in collision with player2 ({0:n}, {1:n} vs {2:n}, {3:n})".format(
+            self_dict['x_pos'],self_dict['y_pos'],
+            namespace.other_dict['x_pos'].value,
+            namespace.other_dict['y_pos'].value),
+          file=sys.stderr)
+        sys.stderr.flush()
+        if not bit_list[4]:
+          hfo_env.act(*save_action_prestate(action=hfo.DASH,
+                                            action_params=[80, rel_angle],
+                                            **prestate_dict))
+        else:
+          hfo_env.act(*save_action_prestate(action=hfo.DRIBBLE_TO,
+                                            action_params=[(self_dict['x_pos']+
+                                                            (2*(namespace.other_dict['x_pos'].value-
+                                                                self_dict['x_pos']))),
+                                                           (self_dict['y_pos']+
+                                                            (2*(namespace.other_dict['y_pos'].value-
+                                                                self_dict['y_pos'])))],
+                                            **prestate_dict))
+
+      return
+    
+    poss_actions_list = []
+
+    poss_actions_list.append(hfo.DRIBBLE_TO)
+    if not bit_list[4]:
+      poss_actions_list.append(hfo.MOVE_TO)
+    if rel_angle is not None:
+      if abs(rel_angle) > 0.5:
+        poss_actions_list.append(hfo.TURN)
+      if (abs(rel_angle) < 1.0) and ((not bit_list[4]) or (other_dist < 0.6)):
+        poss_actions_list.append(hfo.DASH)
+      elif bit_list[4] and (other_dist < 0.6):
+        poss_actions_list.append(hfo.DASH)
+
+    action = determine_which_action(poss_actions_list, namespace, bit_list)
+
+    if action == hfo.TURN:
+      hfo_env.act(*save_action_prestate(action=hfo.TURN,
+                                        action_params=[rel_angle],
+                                        **prestate_dict))
+    elif (action == hfo.MOVE_TO) or (action == hfo.DRIBBLE_TO):
+      hfo_env.act(*save_action_prestate(action=action,
+                                        action_params=[namespace.other_dict['x_pos'].value,
+                                                       namespace.other_dict['y_pos'].value],
+                                        **prestate_dict))
+    elif action == hfo.DASH:
+      hfo_env.act(*save_action_prestate(action=hfo.DASH,
+                                        action_params=[80, rel_angle],
+                                        **prestate_dict))
+    else:
+      raise RuntimeError("Unknown action {0!r}".format(action))
+
+    return
+
+  elif namespace.intent == INTENT_RANDOM_MANEUVER:
+
+    if not bit_list[1]:
+      hfo_env.act(save_action_prestate(action=hfo.REORIENT,
+                                       **prestate_dict))
+      return
+
+    poss_actions_set = set([hfo.DASH, hfo.MOVE_TO, hfo.INTERCEPT, hfo.MOVE, hfo.GO_TO_BALL])
+
+    if not bit_list[0]:
+      poss_actions_set.discard(hfo.MOVE_TO)
+      poss_actions_set.discard(hfo.MOVE)
+      poss_actions_set.discard(hfo.GO_TO_BALL)
+    if bit_list[4] or (not bit_list[5]):
+      poss_actions_set.discard(hfo.INTERCEPT)
+      poss_actions_set.discard(hfo.MOVE)
+      poss_actions_set.discard(hfo.GO_TO_BALL)
+
+    action = determine_which_action(list(poss_actions_set), namespace, bit_list)
+
+    if action in (hfo.INTERCEPT, hfo.MOVE, hfo.GO_TO_BALL):
+      hfo_env.act(save_action_prestate(action=action,
+                                       **prestate_dict))
+    elif action == hfo.DASH:
+      hfo_env.act(*save_action_prestate(action=hfo.DASH,
+                                        action_params=[random.uniform(60,100),
+                                                       random.uniform(-180,180)],
+                                        **prestate_dict))
+    elif action == hfo.MOVE_TO:
+      r = random.random()
+      if r < 0.25:
+        hfo_env.act(*save_action_prestate(action=hfo.MOVE_TO,
+                                          action_params=[random.uniform(-1.0,0.0),
+                                                         random.uniform(-1.0,-0.5)],
+                                          **prestate_dict))
+      elif r < 0.5:
+        hfo_env.act(*save_action_prestate(action=hfo.MOVE_TO,
+                                          action_params=[random.uniform(-1.0,0.0),
+                                                         random.uniform(0.5,1.0)],
+                                          **prestate_dict))
+      elif r < 0.75:
+        hfo_env.act(*save_action_prestate(action=hfo.MOVE_TO,
+                                          action_params=[random.uniform(0.0,1.0),
+                                                         random.uniform(-1.0,-0.5)],
+                                          **prestate_dict))
+      else:
+        hfo_env.act(*save_action_prestate(action=hfo.MOVE_TO,
+                                          action_params=[random.uniform(0.0,1.0),
+                                                         random.uniform(0.5,1.0)],
+                                          **prestate_dict))
+    else:
+      raise RuntimeError("Unknown action {0!r}".format(action))
+
+  else:
+    raise RuntimeError("Unexpected intent {0!r}".format(namespace.intent))
+
+def decide_intent(poss_intent_list, namespace):
+    if len(poss_intent_list) > 1:
+      random.shuffle(poss_intent_list)
+      poss_intent_list.sort(key=lambda intent: namespace.intent_done[intent])
+    return poss_intent_list[0]
+
+def do_next_action(hfo_env,
+                   state,
+                   namespace):
+  bit_list, self_dict, goal_dict, ball_dict = filter_low_level_state(state, namespace)
+
+  reorient_dict = {'action': hfo.REORIENT,
+                   'prestate_bit_list': bit_list,
+                   'prestate_self_dict': self_dict,
+                   'prestate_goal_dict': goal_dict,
+                   'prestate_ball_dict': ball_dict,
+                   'checking_intent': False,
+                   'intent': namespace.intent,
+                   'namespace': namespace}
+
+  if not bit_list[1]: # self velocity invalid
+    if namespace.intent is not None:
+      hfo_env.act(save_action_prestate(**reorient_dict))
+      return None
+
+  prior_intent = namespace.intent
+  # admittedly, need to check for some of "intent" circumstances in combo!
+  if namespace.intent == INTENT_RANDOM_MANEUVER:
+    if ((bit_list[0] < namespace.prestate_bit_list[0]) or
+        (bit_list[1] < namespace.prestate_bit_list[1]) or
+        (bit_list[5] < namespace.prestate_bit_list[5]) or
+        (bit_list[6] < namespace.prestate_bit_list[6])):
+      namespace.intent = None
+  elif namespace.intent == INTENT_BALL_COLLISION:
+    if bit_list[2]:
+      namespace.intent = None
+    else:
+      if (not bit_list[5]) or (ball_dict['x_pos'] is None): # first: ball location not valid
+        namespace.intent_done[INTENT_BALL_COLLISION] -= 1
+        if bit_list[0]: # self location valid
+          if bit_list[3] or bit_list[7]:
+            namespace.intent = None
+          else:
+            namespace.intent = decide_intent([INTENT_GOAL_COLLISION, INTENT_PLAYER_COLLISION],
+                                             namespace)
+        else:
+          hfo_env.act(save_action_prestate(**reorient_dict))
+          return None
+  elif namespace.intent == INTENT_BALL_KICKABLE:
+    if bit_list[4]:
+      namespace.intent = None
+    else:
+      if (not bit_list[5]) or (ball_dict['x_pos'] is None): # first: ball location not valid
+        namespace.intent_done[INTENT_BALL_KICKABLE] -= 1
+        if bit_list[0]: # self location valid
+          if bit_list[3] or bit_list[7]:
+            namespace.intent = None
+          else:
+            namespace.intent = decide_intent([INTENT_GOAL_COLLISION, INTENT_PLAYER_COLLISION],
+                                             namespace)
+        else:
+          hfo_env.act(save_action_prestate(**reorient_dict))
+  elif namespace.intent == INTENT_GOAL_COLLISION:
+    if bit_list[3]:
+      namespace.intent = None
+    else:
+      if (not bit_list[0]) or (goal_dict['rel_angle'] is None): # first: self location not valid
+        namespace.intent_done[INTENT_GOAL_COLLISION] -= 1
+        if bit_list[4] or bit_list[2] or bit_list[7]:
+          namespace.intent = None
+        else:
+          poss_intent_list = [INTENT_PLAYER_COLLISION]
+          if bit_list[5] and (ball_dict['x_pos'] is not None):
+            poss_intent_list += [INTENT_BALL_COLLISION, INTENT_BALL_KICKABLE]
+          namespace.intent = decide_intent(poss_intent_list, namespace)
+  elif namespace.intent == INTENT_PLAYER_COLLISION:
+    if bit_list[7]:
+      namespace.intent = None
+    else:
+      if not bit_list[0]: # self location not valid
+        if bit_list[4] or bit_list[2] or bit_list[3]:
+          namespace.intent = None
+        else:
+          poss_intent_list = [INTENT_GOAL_COLLISION]
+          if bit_list[5] and (ball_dict['x_pos'] is not None):
+            poss_intent_list += [INTENT_BALL_COLLISION, INTENT_BALL_KICKABLE]
+          namespace.intent = decide_intent(poss_intent_list, namespace)
+  if namespace.intent is not None:
+    namespace.times_intent_NONE = 0
+    return do_intent(hfo_env, state, namespace)
+
+  # figure out what to do next
+
+  actions_want_check = set([])
+  if not bit_list[0]:
+    actions_want_check |= set([hfo.DASH, hfo.TURN, hfo.REORIENT, hfo.DRIBBLE_TO])
+    if bit_list[4]:
+      actions_want_check |= set([hfo.KICK])
+      if self_dict['other_angle'] is not None:
+        actions_want_check |= set([hfo.PASS])
+    else:
+      actions_want_check |= set([hfo.INTERCEPT])
+  elif not bit_list[1]:
+    actions_want_check |= set([hfo.TURN, hfo.REORIENT])
+    if bit_list[4]:
+      actions_want_check |= set([hfo.KICK_TO])
+      if self_dict['other_angle'] is not None:
+        actions_want_check |= set([hfo.PASS])
+    elif bit_list[5]:
+      actions_want_check |= set([hfo.INTERCEPT])
+
+  if bit_list[5] and not bit_list[6]:
+    actions_want_check |= set([hfo.REORIENT, hfo.DRIBBLE_TO])
+    if bit_list[4]:
+      actions_want_check |= set([hfo.KICK, hfo.DRIBBLE])
+      if bit_list[0]:
+        actions_want_check |= set([hfo.KICK_TO])
+        if self_dict['other_angle'] is not None:
+          actions_want_check |= set([hfo.PASS])
+    else:
+      if bit_list[1] and (not bit_list[0]):
+        actions_want_check |= set([hfo.INTERCEPT])
+
+  if not actions_want_check:
+    poss_intent_set = set([INTENT_BALL_KICKABLE,INTENT_RANDOM_MANEUVER])
+
+    if (not bit_list[5]) or (ball_dict['x_pos'] is None):
+      poss_intent_set.discard(INTENT_BALL_KICKABLE)
+      poss_intent_set.discard(INTENT_BALL_COLLISION)
+    elif bit_list[4]:
+      poss_intent_set.discard(INTENT_BALL_KICKABLE)
+
+    if not bit_list[0]:
+      poss_intent_set.discard(INTENT_GOAL_COLLISION)
+      poss_intent_set.discard(INTENT_PLAYER_COLLISION)
+    elif self_dict['other_angle'] is None:
+      poss_intent_set.discard(INTENT_PLAYER_COLLISION)
+
+    if not poss_intent_set:
+      hfo_env.act(save_action_prestate(**reorient_dict))
+      return None
+      #raise NotImplementedError("Not yet set up for self+ball location invalid")
+
+    namespace.intent = decide_intent(list(poss_intent_set), namespace)
+
+    print("INTENT: {}".format(INTENT_DICT[namespace.intent]))
+    sys.stdout.flush()
+    namespace.times_intent_NONE = 0
+
+    return do_intent(hfo_env, state, namespace)
+
+  if bit_list[4] and (prior_intent not in (None, INTENT_BALL_KICKABLE)):
+    namespace.intent_done[INTENT_BALL_KICKABLE] += 1
+  if bit_list[2] and (prior_intent not in (None, INTENT_BALL_COLLISION)):
+    namespace.intent_done[INTENT_BALL_COLLISION] += 1
+  if bit_list[3] and (prior_intent not in (None, INTENT_GOAL_COLLISION)):
+    namespace.intent_done[INTENT_GOAL_COLLISION] += 1
+  if bit_list[7] and (prior_intent not in (None, INTENT_PLAYER_COLLISION)):
+    namespace.intent_done[INTENT_PLAYER_COLLISION] += 1
+
+  actions_maybe_check = set([hfo.DASH, hfo.TURN, hfo.KICK_TO, hfo.INTERCEPT, hfo.MOVE,
+                             hfo.DRIBBLE, hfo.REORIENT])
+  if not bit_list[2]: # ball collision
+    actions_maybe_check |= set([hfo.KICK, hfo.MOVE_TO, hfo.GO_TO_BALL])
+  if not bit_list[3]: # post collision
+    actions_maybe_check |= set([hfo.DRIBBLE_TO])
+    if not bit_list[7]: # player collision
+      actions_maybe_check |= set([hfo.PASS])
+
+  poss_actions = actions_want_check & actions_maybe_check
+
+  if not poss_actions:
+    raise RuntimeError(
+      "Unknown what to do - actions_want_check {0!r}, actions_maybe_check {1!r}".format(actions_want_check,
+                                                                                        actions_maybe_check))
+
+  action = determine_which_action(list(poss_actions), namespace, bit_list)
+
+  if prior_intent is not None:
+    namespace.times_intent_NONE = 0
+    print("INTENT: WAS {}".format(INTENT_DICT[prior_intent]))
+  else:
+    namespace.times_intent_NONE += 1
+    print("INTENT: NONE (action {})".format(hfo_env.actionToString(action)))
+    if namespace.times_intent_NONE > 5:
+      print("Actions_want_check: {0!s}; actions_maybe_check: {1!s}".format(
+        ", ".join([hfo_env.actionToString(x) for x in list(actions_want_check)]),
+        ", ".join([hfo_env.actionToString(x) for x in list(actions_maybe_check)])))
+  sys.stdout.flush()
+
+  prestate_dict = {'action': action,
+                   'prestate_bit_list': bit_list,
+                   'prestate_self_dict': self_dict,
+                   'prestate_goal_dict': goal_dict,
+                   'prestate_ball_dict': ball_dict,
+                   'checking_intent': True,
+                   'intent': None,
+                   'namespace': namespace}
+
+  # added: DRIBBLE, PASS, MOVE
+
+  # ends due to out-of-bounds are annoying/noise in the data
+  if (action in (hfo.DRIBBLE_TO, hfo.KICK_TO)) or ((action == hfo.MOVE_TO) and (bit_list[4] or bit_list[2])):
+    if ball_dict['x_pos'] < -1*POS_ERROR_TOLERANCE:
+      x_pos = random.uniform(-0.5,min(0.9,MAX_POS_X_BALL_SAFE))
+    elif ball_dict['x_pos'] > POS_ERROR_TOLERANCE:
+      x_pos = random.uniform(max(-0.9,MIN_POS_X_BALL_SAFE),0.5)
+    else:
+      x_pos = random.uniform(max(-0.9,MIN_POS_X_BALL_SAFE),min(0.9,MAX_POS_X_BALL_SAFE))
+    if ball_dict['y_pos'] < -1*POS_ERROR_TOLERANCE:
+      y_pos = random.uniform(-0.5,min(0.9,MAX_POS_Y_BALL_SAFE))
+    elif ball_dict['y_pos'] > POS_ERROR_TOLERANCE:
+      y_pos = random.uniform(max(-0.9,MIN_POS_Y_BALL_SAFE),0.5)
+    else:
+      y_pos = random.uniform(max(-0.9,MIN_POS_Y_BALL_SAFE),min(0.9,MAX_POS_Y_BALL_SAFE))
+  else:
+    x_pos = random.uniform(MIN_POS_X_OK,MAX_POS_X_OK)
+    y_pos = random.uniform(MIN_POS_Y_OK,MAX_POS_Y_OK)
+
+
+  max_power = 80
+  if (action == hfo.KICK) and (get_dist_normalized(ball_dict['x_pos'],
+                                                   ball_dict['y_pos'],
+                                                   0.0,0.0) > 1):
+    if (self_dict['other_angle'] is not None):
+      max_power = 100
+      direction = self_dict['other_angle']
+      if direction > 180:
+        direction -= 360
+    else:
+      max_power = 50
+      direction = random.uniform(-180,180)
+  else:
+    direction = random.uniform(-180,180)
+
+  if action in (hfo.INTERCEPT, hfo.GO_TO_BALL, hfo.DRIBBLE, hfo.MOVE, hfo.REORIENT):
+    hfo_env.act(save_action_prestate(**prestate_dict))
+  elif action is hfo.PASS:
+    hfo_env.act(*save_action_prestate(action_params=[namespace.other_dict['unum'].value],
+                                      **prestate_dict))
+  elif action in (hfo.MOVE_TO, hfo.DRIBBLE_TO):
+    hfo_env.act(*save_action_prestate(action_params=[x_pos,y_pos],
+                                      **prestate_dict))
+  elif action == hfo.KICK_TO:
+    if min(abs(x_pos),abs(y_pos)) > 0.5:
+      max_speed = 1
+    elif max(abs(x_pos),abs(y_pos)) > 0.5:
+      max_speed = 2
+    else:
+      max_speed = 3
+    hfo_env.act(*save_action_prestate(action_params=[x_pos,y_pos,
+                                                     random.uniform(0,max_speed)],
+                                      **prestate_dict))
+  elif action == hfo.DASH:
+    hfo_env.act(*save_action_prestate(action_params=[random.uniform(-100,100), # power
+                                                     direction],
+                                      **prestate_dict))
+  elif action == hfo.TURN:
+    hfo_env.act(*save_action_prestate(action_params=[direction],**prestate_dict))
+  elif action == hfo.KICK:
+    hfo_env.act(*save_action_prestate(action_params=[random.uniform(0,max_power),
+                                                     direction],**prestate_dict))
+  else:
+    raise RuntimeError("Unknown action {!r}".format(action))
+
+  return None
+
+def run_player2(conf_dir, args, namespace):
+  hfo_env2 = hfo.HFOEnvironment()
+
+  if args.seed:
+    random.seed(args.seed+1)
+
+  connect2_args = [hfo.HIGH_LEVEL_FEATURE_SET, conf_dir, args.port, "localhost",
+                   "base_left", False]
+
+  if args.record:
+    connect2_args.append(record_dir=args.rdir)
+
+  time.sleep(5)
+
+  print("Player2 connecting")
+  sys.stdout.flush()
+
+  hfo_env2.connectToServer(*connect2_args)
+
+  print("Player2 unum is {0:d}".format(hfo_env2.getUnum()))
+  sys.stdout.flush()
+  namespace.other_dict['unum'].value = hfo_env2.getUnum()
+
+  status=hfo.IN_GAME
+  while status != hfo.SERVER_DOWN:
+    did_reorient = False
+    if status == hfo.IN_GAME:
+      state2 = hfo_env2.getState()
+      namespace.other_dict['x_pos'].value = state2[0]
+      namespace.other_dict['y_pos'].value = state2[1]
+      namespace.other_dict['ball_x_pos'].value = state2[3]
+      namespace.other_dict['ball_y_pos'].value = state2[4]
+      namespace.other_dict['self_x_pos'].value = state2[13]
+      namespace.other_dict['self_y_pos'].value = state2[14]
+
+      if (abs(state2[0])
+          < 1) and (abs(state2[1])
+                    < 1):
+        if get_dist_normalized(0.0,
+                               0.0,
+                               state2[0],
+                               state2[1]) > POS_ERROR_TOLERANCE:
+          hfo_env2.act(hfo.MOVE_TO,0.0,0.0)
+          did_reorient = False
+        else:
+          hfo_env2.act(hfo.TURN,random.uniform(-180,180))
+          did_reorient = False
+      elif did_reorient and (abs(state2[0]) <= 1) and (abs(state2[1]) <= 1):
+        hfo_env2.act(hfo.MOVE_TO,0.0,0.0)
+        did_reorient = False
+      else:
+        hfo_env2.act(hfo.REORIENT)
+        did_reorient = True
+        
+      
+      status = hfo_env2.step()
+
+    hfo_env2.act(hfo.NOOP)
+    status = hfo_env2.step()
+
+  hfo_env2.act(hfo.QUIT)
+  sys.exit(0)
+
+
+def main_explore_offense_actions():
+  parser = argparse.ArgumentParser()
+  parser.add_argument('--port', type=int, default=6000,
+                      help="Server port")
+  parser.add_argument('--seed', type=int, default=None,
+                      help="Python randomization seed; uses python default if 0 or not given")
+  parser.add_argument('--server-seed', type=int, default=None,
+                      help="Server randomization seed; uses default if 0 or not given")
+  parser.add_argument('--record', default=False, action='store_true',
+                      help="Doing HFO --record")
+  parser.add_argument('--rdir', type=str, default='log/',
+                      help="Set directory to use if doing HFO --record")
+  args=parser.parse_args()
+
+  namespace = argparse.Namespace()
+  namespace.intent_done = {}
+  namespace.intent_done[INTENT_BALL_COLLISION] = 0
+  namespace.intent_done[INTENT_BALL_KICKABLE] = 0
+  namespace.intent_done[INTENT_GOAL_COLLISION] = 0
+  namespace.intent_done[INTENT_PLAYER_COLLISION] = 0
+  namespace.intent_done[INTENT_RANDOM_MANEUVER] = 0
+  namespace.action_tried = {}
+  namespace.struct_tried = {}
+  for n in list(range(hfo.NUM_HFO_ACTIONS)):
+    namespace.action_tried[n] = 0
+    for x in list(range((2**(BIT_LIST_LEN+1))-1)):
+      namespace.struct_tried[struct.pack(STRUCT_PACK, n, x)] = 0
+  namespace.other_dict = {'unum': multiprocessing.sharedctypes.Value('B', 0)}
+  for name in ['x_pos', 'y_pos', 'ball_x_pos', 'ball_y_pos', 'self_x_pos', 'self_y_pos']:
+    namespace.other_dict[name] = multiprocessing.sharedctypes.Value('d', -2.0)
+
+  script_dir   = os.path.dirname(os.path.abspath(os.path.realpath(__file__)))
+  binary_dir = os.path.normpath(script_dir + "/../bin")
+  conf_dir = os.path.join(binary_dir, 'teams/base/config/formations-dt')
+  bin_HFO = os.path.join(binary_dir, "HFO")
+
+  player2_process = multiprocessing.Process(target=run_player2,
+                                            kwargs={'conf_dir': conf_dir,
+                                                    'args': args,
+                                                    'namespace': namespace}
+                                            )
+
+  popen_list = [sys.executable, "-x", bin_HFO,
+                "--frames-per-trial=3000", "--untouched-time=2000",
+                "--port={0:d}".format(args.port),
+                "--offense-agents=2", "--defense-npcs=0",
+                "--offense-npcs=0", "--trials=20", "--headless"]
+
+  if args.record:
+    popen_list.append("--record")
+  if args.server_seed:
+    popen_list.append("--seed={0:d}".format(args.server_seed))
+
+  HFO_subprocess = subprocess.Popen(popen_list)
+
+
+  player2_process.daemon = True
+  player2_process.start()
+
+  time.sleep(0.2)
+
+  assert (HFO_subprocess.poll() is
+          None), "Failed to start HFO with command '{}'".format(" ".join(popen_list))
+  assert player2_process.is_alive(), "Failed to start player2 process (exit code {!r})".format(
+    player2_process.exitcode)
+
+  if args.seed:
+    random.seed(args.seed)
+
+  hfo_env = hfo.HFOEnvironment()
+
+  connect_args = [hfo.LOW_LEVEL_FEATURE_SET, conf_dir, args.port, "localhost",
+                  "base_left", False]
+
+
+  if args.record:
+    connect_args.append(record_dir=args.rdir)
+
+  time.sleep(9.8)
+
+  try:
+    assert player2_process.is_alive(), "Player2 process exited (exit code {!r})".format(
+      player2_process.exitcode)
+
+    print("Main player connecting")
+    sys.stdout.flush()
+
+    hfo_env.connectToServer(*connect_args)
+
+    print("Main player unum {0:d}".format(hfo_env.getUnum()))
+
+    for ignored_episode in itertools.count():
+      status = hfo.IN_GAME
+      namespace.action = hfo.NOOP
+      namespace.action_params = []
+      namespace.prestate_bit_list = [0,0,0,0,0,0,0]
+      namespace.prestate_self_dict = {'x_pos': None,
+                                      'y_pos': None,
+                                      'body_angle': None,
+                                      'other_angle': None,
+                                      'vel_rel_angle': None,
+                                      'vel_mag': -1.0}
+      namespace.prestate_goal_dict = {'dist': get_dist_from_proximity(-1),
+                                      'rel_angle': None,
+                                      'abs_angle': None,
+                                      'x_pos': GOAL_POS_X,
+                                      'y_pos': 0.0}
+      namespace.prestate_ball_dict = {'dist': get_dist_from_proximity(-1),
+                                      'rel_angle': None,
+                                      'abs_angle': None,
+                                      'x_pos': None,
+                                      'y_pos': None,
+                                      'vel_rel_angle': None,
+                                      'vel_mag': None}
+      namespace.intent = None
+      namespace.times_intent_NONE = 0
+      namespace.checking_intent = True
+
+      while status == hfo.IN_GAME:
+        state = hfo_env.getState()
+
+        if namespace.action != hfo.NOOP:
+          evaluate_previous_action(hfo_env, state, namespace)
+        do_next_action(hfo_env, state, namespace)
+        status = hfo_env.step()
+
+      if status == hfo.SERVER_DOWN:
+        # summarize results
+        hfo_env.act(hfo.QUIT)
+        break
+  finally:
+    if HFO_subprocess.poll() is None:
+      HFO_subprocess.terminate()
+    os.system("killall -9 rcssserver") # remove if ever start doing multi-server testing!
+
+
+if __name__ == '__main__':
+  main_explore_offense_actions()
diff --git a/example/explore_offense_actions_fullstate.twoplayer.v2.py b/example/explore_offense_actions_fullstate.twoplayer.v2.py
index b1aeb4b41872f624e8c0534576420efdf889d470..68dd3042fb996d9ad8a1d9b6a76354dadd549b19 100755
--- a/example/explore_offense_actions_fullstate.twoplayer.v2.py
+++ b/example/explore_offense_actions_fullstate.twoplayer.v2.py
@@ -33,7 +33,7 @@ HALF_FIELD_FULL_WIDTH = HALF_FIELD_WIDTH * 1.2
 HALF_FIELD_LENGTH = 52.5 # x coordinate
 HALF_FIELD_FULL_LENGTH = HALF_FIELD_LENGTH * 1.2
 GOAL_WIDTH = 14.02
-MAX_RADIUS = math.sqrt((HALF_FIELD_WIDTH**2) + ((HALF_FIELD_LENGTH/2)**2)) # latter is incorrect for _actual_ maximum distance...
+MAX_RADIUS = math.sqrt(((HALF_FIELD_WIDTH/2)**2) + (HALF_FIELD_LENGTH**2)) # not actually correct, but works...
 ERROR_TOLERANCE = math.pow(sys.float_info.epsilon,0.25)
 POS_ERROR_TOLERANCE = 0.05