Index: branches/pap/psastro/src/Makefile.am
===================================================================
--- branches/pap/psastro/src/Makefile.am	(revision 23948)
+++ branches/pap/psastro/src/Makefile.am	(revision 25027)
@@ -89,4 +89,6 @@
 	psastroMaskUpdates.c        \
 	psastroMaskUtils.c          \
+	psastroChooseGlintStars.c   \
+	psastroLoadGlints.c   \
 	psastroLoadGhosts.c         \
 	psastroGhostUtils.c         \
Index: branches/pap/psastro/src/psastro.h
===================================================================
--- branches/pap/psastro/src/psastro.h	(revision 23948)
+++ branches/pap/psastro/src/psastro.h	(revision 25027)
@@ -73,4 +73,7 @@
 bool              psastroMaskUpdates (pmConfig *config);
 
+bool              psastroLoadGlints (pmConfig *config);
+bool              psastroChooseGlintStars (pmConfig *config, psArray *refs, const char *source);
+
 psArray          *psastroLoadRefstars (pmConfig *config, const char *source);
 bool              psastroChipAstrom (pmConfig *config);
@@ -144,5 +147,5 @@
 bool 		  psastroZeroPoint (pmConfig *config);
 bool 		  psastroZeroPointReadout(pmReadout *readout, float zeropt, float exptime);
-bool 		  psastroZeroPointFromRecipe (float *zeropt, float *exptime, pmFPA *fpa, psMetadata *recipe);
+bool 		  psastroZeroPointFromRecipe (float *zeropt, float *exptime, float *ghostMaxMag, pmFPA *fpa, psMetadata *recipe);
 
 // masking functions
@@ -162,4 +165,10 @@
 pmChip           *psastroFindChip (double *xChip, double *yChip, pmFPA *fpa, double xFPA, double yFPA);
 bool 		  psastroChipBounds (pmFPA *fpa);
+bool              psastroFindChipInXrange (pmFPA *fpa, int nChip, double xFPA, double yFPA);
+bool              psastroFindChipInYrange (pmFPA *fpa, int nChip, double xFPA, double yFPA);
+bool 		  psastroFindChipYedges (double *yFPAs, double *yFPAe, pmFPA *fpa, int nChip);
+bool 		  psastroFindChipXedges (double *yFPAs, double *yFPAe, pmFPA *fpa, int nChip);
+bool 		  psastroFPAtoChip (double *xChip, double *yChip, pmFPA *fpa, int nChip, double xFPA, double yFPA);
+
 
 // psastroExtract functions
Index: branches/pap/psastro/src/psastroAnalysis.c
===================================================================
--- branches/pap/psastro/src/psastroAnalysis.c	(revision 23948)
+++ branches/pap/psastro/src/psastroAnalysis.c	(revision 25027)
@@ -86,4 +86,10 @@
         return false;
     }
+
+    if (!psastroChooseGlintStars (config, refs, "PSASTRO.INPUT")) {
+        psError (PSASTRO_ERR_UNKNOWN, false, "failed to select glint stars from reference star list\n");
+        psFree(refs);
+        return false;
+    }
     psFree (refs);  // refs of interest are saved on readout->analysis
 
Index: branches/pap/psastro/src/psastroAstromGuess.c
===================================================================
--- branches/pap/psastro/src/psastroAstromGuess.c	(revision 23948)
+++ branches/pap/psastro/src/psastroAstromGuess.c	(revision 25027)
@@ -382,6 +382,12 @@
     psStats *statsQ = psStatsAlloc (PS_STAT_SAMPLE_MEAN | PS_STAT_SAMPLE_STDEV);
 
-    psVectorStats (statsP, cornerPd, NULL, cornerMK, 1);
-    psVectorStats (statsQ, cornerQd, NULL, cornerMK, 1);
+    if (!psVectorStats (statsP, cornerPd, NULL, cornerMK, 1)) {
+	psError(PS_ERR_UNKNOWN, false, "failure to measure stats");
+	return false;
+    }
+    if (!psVectorStats (statsQ, cornerQd, NULL, cornerMK, 1)) {
+	psError(PS_ERR_UNKNOWN, false, "failure to measure stats");
+	return false;
+    }
 
     float angle = atan2 (map->y->coeff[1][0], map->x->coeff[1][0]);
Index: branches/pap/psastro/src/psastroChipAstrom.c
===================================================================
--- branches/pap/psastro/src/psastroChipAstrom.c	(revision 23948)
+++ branches/pap/psastro/src/psastroChipAstrom.c	(revision 25027)
@@ -130,4 +130,9 @@
     }
 
+    if (numGoodChips == 0) {
+        psError(PSASTRO_ERR_UNKNOWN, false, "Failed to fit any chips");
+        return false;
+    }
+
     if (!psastroFixChips (config, recipe)) {
         psError(PSASTRO_ERR_UNKNOWN, false, "failed to align problematic chips");
Index: branches/pap/psastro/src/psastroChooseGlintStars.c
===================================================================
--- branches/pap/psastro/src/psastroChooseGlintStars.c	(revision 25027)
+++ branches/pap/psastro/src/psastroChooseGlintStars.c	(revision 25027)
@@ -0,0 +1,73 @@
+/** @file psastroChooseGlintStars.c
+ *
+ *  @brief: Select stars by magnitude which are likely glint sources
+ *
+ *  @ingroup libpsastro
+ *
+ *  @author IfA
+ *  @version $Revision: 1.21 $
+ *  @date $Date: 2009-02-07 02:03:34 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "psastroInternal.h"
+
+bool psastroChooseGlintStars (pmConfig *config, psArray *refs, const char *source) {
+
+    bool status;
+    float zeropt, exptime;
+
+    // select the current recipe
+    psMetadata *recipe  = psMetadataLookupPtr (NULL, config->recipes, PSASTRO_RECIPE);
+    if (!recipe) {
+        psError(PSASTRO_ERR_CONFIG, true, "Can't find PSASTRO recipe!\n");
+        return false;
+    }
+
+    bool REFSTAR_MASK_GLINTS = psMetadataLookupBool (&status, recipe, "REFSTAR_MASK_GLINTS");
+    if (!REFSTAR_MASK_GLINTS) return true;
+
+    // select the input data sources
+    pmFPAfile *input = psMetadataLookupPtr (NULL, config->files, source);
+    if (!input) {
+        psError(PSASTRO_ERR_CONFIG, true, "Can't find input data!\n");
+        return false;
+    }
+    pmFPA *fpa = input->fpa;
+
+    // really error-out here?  or just skip?
+    if (!psastroZeroPointFromRecipe (&zeropt, &exptime, NULL, fpa, recipe)) {
+        psLogMsg ("psastro", PS_LOG_INFO, "failed to load zeropt data from recipe");
+        return false;
+    }
+
+    // select the limiting magnitude
+    double GLINT_MAX_MAG = psMetadataLookupF32 (&status, recipe, "GLINT_MAX_MAG");
+
+    // recipe values are given in instrumental magnitudes
+    // use the zero point and exposure time to convert to apparent mags: M_ap = M_inst + C_0 + 2.5*log(exptime)
+    float MagOffset = zeropt + 2.5*log10(exptime);
+    GLINT_MAX_MAG += MagOffset;
+
+    // There is no selection based on the location of the star. We need the full astrometry
+    // solution before we can trust the star positions.
+
+    // the refstars is a subset within range of this chip
+    psArray *glintStars = psArrayAllocEmpty (100);
+
+    // select the reference objects brighter than the cutoff magnitude within range of the FPA
+    for (int i = 0; i < refs->n; i++) {
+	pmAstromObj *ref = refs->data[i];
+	if (ref->Mag > GLINT_MAX_MAG) continue;
+
+	pmAstromObj *glint = pmAstromObjCopy(ref);
+	psArrayAdd (glintStars, 100, glint);
+	psFree (glint);
+    }
+    psTrace ("psastro", 4, "Added %ld glint stars\n", glintStars->n);
+
+    psMetadataAdd (fpa->analysis, PS_LIST_TAIL, "PSASTRO.GLINT.STARS", PS_DATA_ARRAY, "possible glint stars", glintStars);
+    psFree (glintStars);
+
+    return true;
+}
Index: branches/pap/psastro/src/psastroChooseRefstars.c
===================================================================
--- branches/pap/psastro/src/psastroChooseRefstars.c	(revision 23948)
+++ branches/pap/psastro/src/psastroChooseRefstars.c	(revision 25027)
@@ -6,5 +6,5 @@
  *
  *  @author IfA
- *  @version $Revision: 1.21 $ $Name: not supported by cvs2svn $
+ *  @version $Revision: 1.21 $
  *  @date $Date: 2009-02-07 02:03:34 $
  *  Copyright 2009 Institute for Astronomy, University of Hawaii
@@ -49,4 +49,12 @@
     pmFPAview *view = pmFPAviewAlloc (0);
     pmFPA *fpa = input->fpa;
+
+    // XXX kind of a hack -- think this through a bit more clearly:
+    if (psMetadataLookupPtr (&status, fpa->analysis, "MATCHED_REFS")) {
+	// we loaded a set of matched references from an earlier astrometry
+	// analysis.  however, we are re-doing the astrometry here, so remove
+	// that prior set of matched references
+	psMetadataRemoveKey (fpa->analysis, "MATCHED_REFS");
+    }
 
     // sort by mag
@@ -114,4 +122,15 @@
                 psTrace ("psastro", 4, "Added %ld refstars\n", refstars->n);
 
+# if (0)
+		// XXX test
+		FILE *outfile = fopen ("refstars.dat", "w");
+		assert (outfile);
+		for (int nn = 0; nn < refstars->n; nn++) {
+			  pmAstromObj *ref = refstars->data[nn];
+			  fprintf (outfile, "%lf %lf\n", ref->sky->r*PS_DEG_RAD, ref->sky->d*PS_DEG_RAD);
+		}
+		fclose (outfile);
+# endif
+
 		psMetadataAdd (readout->analysis, PS_LIST_TAIL, "PSASTRO.REFSTARS", PS_DATA_ARRAY, "astrometry matches", refstars);
 		psFree (refstars);
Index: branches/pap/psastro/src/psastroExtractGhosts.c
===================================================================
--- branches/pap/psastro/src/psastroExtractGhosts.c	(revision 23948)
+++ branches/pap/psastro/src/psastroExtractGhosts.c	(revision 25027)
@@ -53,5 +53,5 @@
 
     // really error-out here?  or just skip?
-    if (!psastroZeroPointFromRecipe (&zeropt, &exptime, fpa, recipe)) {
+    if (!psastroZeroPointFromRecipe (&zeropt, &exptime, NULL, fpa, recipe)) {
         psLogMsg ("psastro", PS_LOG_INFO, "failed to load zeropt data from recipe");
         return false;
Index: branches/pap/psastro/src/psastroExtractStars.c
===================================================================
--- branches/pap/psastro/src/psastroExtractStars.c	(revision 23948)
+++ branches/pap/psastro/src/psastroExtractStars.c	(revision 25027)
@@ -65,5 +65,5 @@
 
     // really error-out here?  or just skip?
-    if (!psastroZeroPointFromRecipe (&zeropt, &exptime, fpa, recipe)) {
+    if (!psastroZeroPointFromRecipe (&zeropt, &exptime, NULL, fpa, recipe)) {
         psLogMsg ("psastro", PS_LOG_INFO, "failed to load zeropt data from recipe");
         return false;
Index: branches/pap/psastro/src/psastroFindChip.c
===================================================================
--- branches/pap/psastro/src/psastroFindChip.c	(revision 23948)
+++ branches/pap/psastro/src/psastroFindChip.c	(revision 25027)
@@ -110,4 +110,59 @@
 }
 
+// identify chips which land on this column (FP coords)
+bool psastroFindChipInXrange (pmFPA *fpa, int nChip, double xFPA, double yFPA) {
+
+    if (!chipXmin || !chipXmax) {
+	psAbort ("chip bounds not set");
+    }
+
+    if (xFPA <  chipXmin->data.F32[nChip]) return false;
+    if (xFPA >= chipXmax->data.F32[nChip]) return false;
+    return true;
+}
+
+// identify chips which land on this row (FP coords)
+bool psastroFindChipInYrange (pmFPA *fpa, int nChip, double xFPA, double yFPA) {
+
+    if (!chipYmin || !chipYmax) {
+	psAbort ("chip bounds not set");
+    }
+
+    if (yFPA <  chipYmin->data.F32[nChip]) return false;
+    if (yFPA >= chipYmax->data.F32[nChip]) return false;
+    return true;
+}
+
+// return the FPA coordinates of the Y edges of the chip
+bool psastroFindChipYedges (double *yFPAs, double *yFPAe, pmFPA *fpa, int nChip) {
+
+    *yFPAs = chipYmin->data.F32[nChip];
+    *yFPAe = chipYmax->data.F32[nChip];
+    return true;
+}
+
+// return the FPA coordinates of the X edges of the chip
+bool psastroFindChipXedges (double *yFPAs, double *yFPAe, pmFPA *fpa, int nChip) {
+
+    *yFPAs = chipXmin->data.F32[nChip];
+    *yFPAe = chipXmax->data.F32[nChip];
+    return true;
+}
+
+// convert FPA to Chip coordinates
+bool psastroFPAtoChip (double *xChip, double *yChip, pmFPA *fpa, int nChip, double xFPA, double yFPA) {
+
+    pmChip *chip = fpa->chips->data[nChip];
+
+    psPlane ptCH, ptFP;
+    ptFP.x = xFPA;
+    ptFP.y = yFPA;
+    psPlaneTransformApply (&ptCH, chip->fromFPA, &ptFP);
+
+    *xChip = ptCH.x;
+    *yChip = ptCH.y;
+    return true;
+}
+
 bool psastroExtractFreeChipBounds () {
   
Index: branches/pap/psastro/src/psastroFixChipsTest.c
===================================================================
--- branches/pap/psastro/src/psastroFixChipsTest.c	(revision 23948)
+++ branches/pap/psastro/src/psastroFixChipsTest.c	(revision 25027)
@@ -140,5 +140,8 @@
     psStats *stats;
     stats = psStatsAlloc (PS_STAT_ROBUST_MEDIAN | PS_STAT_ROBUST_STDEV);
-    psVectorStats (stats, dX, NULL, NULL, 0);
+    if (!psVectorStats (stats, dX, NULL, NULL, 0)) {
+	psError(PS_ERR_UNKNOWN, false, "failure to measure stats");
+	return false;
+    }
     Xo = stats->robustMedian;
     fprintf (stderr, "offset x: %f +/- %f\n", stats->robustMedian, stats->robustStdev);
@@ -146,5 +149,8 @@
 
     stats = psStatsAlloc (PS_STAT_ROBUST_MEDIAN | PS_STAT_ROBUST_STDEV);
-    psVectorStats (stats, dY, NULL, NULL, 0);
+    if (!psVectorStats (stats, dY, NULL, NULL, 0)) {
+	psError(PS_ERR_UNKNOWN, false, "failure to measure stats");
+	return false;
+    }
     Yo = stats->robustMedian;
     fprintf (stderr, "offset y: %f +/- %f\n", stats->robustMedian, stats->robustStdev);
@@ -168,5 +174,8 @@
     }
     stats = psStatsAlloc (PS_STAT_ROBUST_MEDIAN | PS_STAT_ROBUST_STDEV);
-    psVectorStats (stats, dT, NULL, NULL, 0);
+    if (!psVectorStats (stats, dT, NULL, NULL, 0)) {
+	psError(PS_ERR_UNKNOWN, false, "failure to measure stats");
+	return false;
+    }
     float To = stats->robustMedian;
     fprintf (stderr, "offset t: %f +/- %f\n", stats->robustMedian, stats->robustStdev);
Index: branches/pap/psastro/src/psastroLoadGhosts.c
===================================================================
--- branches/pap/psastro/src/psastroLoadGhosts.c	(revision 23948)
+++ branches/pap/psastro/src/psastroLoadGhosts.c	(revision 25027)
@@ -46,5 +46,5 @@
     pmCell *cell = NULL;
     pmReadout *readout = NULL;
-    float zeropt, exptime;
+    float zeropt, exptime, MAX_MAG;
     psMetadata *md = NULL;
     psPolynomial2D *centerX = NULL;
@@ -71,6 +71,4 @@
     if (!strcasecmp(ghostFile, "NONE")) return true;
 
-    double MAX_MAG = psMetadataLookupF32 (&status, recipe, "GHOST_MAX_MAG");
-
     if (!pmConfigFileRead (&ghostModel, ghostFile, "GHOST MODEL")) {
 	psError(PSASTRO_ERR_CONFIG, true, "Trouble loading ghost model");
@@ -97,5 +95,5 @@
 
     // really error-out here?  or just skip?
-    if (!psastroZeroPointFromRecipe (&zeropt, &exptime, fpa, recipe)) {
+    if (!psastroZeroPointFromRecipe (&zeropt, &exptime, &MAX_MAG, fpa, recipe)) {
         psLogMsg ("psastro", PS_LOG_INFO, "failed to load zeropt data from recipe");
 	goto escape;
@@ -162,8 +160,8 @@
 		    // which encompass the full size of the ghost
 		    pmChip *ghostChip = psastroFindChip (&ghost->chip->x, &ghost->chip->y, fpa, -ghost->srcFP->x, -ghost->srcFP->y);
-		    fprintf (stderr, "raw chip position: %f, %f ", ghost->chip->x, ghost->chip->y);
+		    // fprintf (stderr, "raw chip position: %f, %f ", ghost->chip->x, ghost->chip->y);
 
 		    ghostChip = psastroFindChip (&ghost->chip->x, &ghost->chip->y, fpa, ghost->FP->x, ghost->FP->y);
-		    fprintf (stderr, "-> model chip position: %f, %f\n", ghost->chip->x, ghost->chip->y);
+		    // fprintf (stderr, "-> model chip position: %f, %f\n", ghost->chip->x, ghost->chip->y);
 
 		    if (!ghostChip) goto skip;
Index: branches/pap/psastro/src/psastroLoadGlints.c
===================================================================
--- branches/pap/psastro/src/psastroLoadGlints.c	(revision 25027)
+++ branches/pap/psastro/src/psastroLoadGlints.c	(revision 25027)
@@ -0,0 +1,391 @@
+/** @file psastroLoadGlints.c
+ *
+ *  @brief calculate glint FPA and Chip positions for the stars loaded on the FPA
+ *
+ *  @ingroup libpsastro
+ *
+ *  @author IfA
+ *  @version $Revision: 1.7 $
+ *  @date $Date: 2009-02-07 02:03:34 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "psastroInternal.h"
+
+# define ESCAPE(MSG) {							\
+	psError(PS_ERR_UNKNOWN, false, "I/O failure in psastroMaskUpdate: %s", MSG); \
+	psFree (view);							\
+	return false;							\
+    }
+
+/**
+ * calculate glint FPA and Chip positions for the stars loaded on the FPA
+ */
+bool psastroLoadGlints (pmConfig *config) {
+
+    bool status;
+    float zeropt, exptime;
+
+    psLogMsg ("psastro", PS_LOG_INFO, "determine glint positions");
+
+    // select the current recipe
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSASTRO_RECIPE);
+    if (!recipe) {
+        psError(PSASTRO_ERR_CONFIG, true, "Can't find PSASTRO recipe");
+        return false;
+    }
+
+    bool REFSTAR_MASK_GLINTS = psMetadataLookupBool (&status, recipe, "REFSTAR_MASK_GLINTS");
+    if (!REFSTAR_MASK_GLINTS) return true;
+
+    // select the limiting magnitude
+    double GLINT_MAX_MAG = psMetadataLookupF32 (&status, recipe, "GLINT_MAX_MAG");
+    double GLINT_LENGTH_MAG_SLOPE = psMetadataLookupF32 (&status, recipe, "GLINT_LENGTH_MAG_SLOPE");
+    double GLINT_LENGTH_MAG_ZERO = psMetadataLookupF32 (&status, recipe, "GLINT_LENGTH_MAG_ZERO");
+    double glintWidth = psMetadataLookupF32 (&status, recipe, "GLINT_WIDTH");
+
+    // select the set of glint regions (GLINT.REGION is a MULTI of METADATA items)
+    psMetadataItem *glintRegions = psMetadataLookup (recipe, "GLINT.REGION");
+    if (!status) {
+        psWarning ("glint masking requested but glint regions are missing (GLINT.REGION)\n");
+        return true;
+    }
+    if (glintRegions->type != PS_DATA_METADATA_MULTI) {
+        psWarning ("GLINT.REGION is not a MULTI\n");
+        return true;
+    }
+
+    // select the input astrometry data (also carries the glintStars)
+    pmFPAfile *astrom = psMetadataLookupPtr (NULL, config->files, "PSASTRO.INPUT");
+    if (!astrom) {
+        psError(PSASTRO_ERR_CONFIG, true, "Can't find input data");
+        return false;
+    }
+    pmFPA *fpa = astrom->fpa;
+
+    // really error-out here?  or just skip?
+    if (!psastroZeroPointFromRecipe (&zeropt, &exptime, NULL, fpa, recipe)) {
+        psLogMsg ("psastro", PS_LOG_INFO, "failed to load zeropt data from recipe");
+        return false;
+    }
+
+    // recipe values are given in instrumental magnitudes
+    // use the zero point and exposure time to convert to apparent mags: M_ap = M_inst + C_0 + 2.5*log(exptime)
+    float MagOffset = zeropt + 2.5*log10(exptime);
+    GLINT_MAX_MAG += MagOffset;
+    GLINT_LENGTH_MAG_ZERO += MagOffset;
+
+    // select the raw objects for this readout (loaded in psastroExtract.c)
+    psArray *glintStars = psMetadataLookupPtr (&status, fpa->analysis, "PSASTRO.GLINT.STARS");
+    if (glintStars == NULL) { 
+        psLogMsg ("psastro", PS_LOG_INFO, "no glint stars found");
+        return false;
+    }
+
+    // set up the chip boundary vectors.
+    psastroChipBounds (fpa);
+
+    // find the possible glint star:s for each glint star, convert the position to FPA coordinates.
+    // search for stars within the glint regions
+    for (int i = 0; i < glintStars->n; i++) {
+	pmAstromObj *star = glintStars->data[i];
+	if (star->Mag > GLINT_MAX_MAG) continue; // XXX should not be needed...
+
+	// project glint star to the focal-plane
+	psProject (star->TP, star->sky, fpa->toSky);
+	psPlaneTransformApply (star->FP, fpa->fromTPA, star->TP);
+	fprintf (stderr, "glint: %7.2f @ %8.1f, %8.1f\n", star->Mag, star->FP->x, star->FP->y);
+
+	// find the GLINT.REGION this star lands in (if any)
+	psListIterator *glintIter = psListIteratorAlloc(glintRegions->data.list, PS_LIST_HEAD, false);
+	psMetadataItem *glintItem = NULL;
+	while ((glintItem = psListGetAndIncrement (glintIter))) {
+	    if (glintItem->type != PS_DATA_METADATA) {
+		psWarning ("GLINT.REGION entry is not a metadata folder");
+		continue;
+	    }
+	    
+	    char *glintRegionString = psMetadataLookupStr (&status, glintItem->data.md, "REGION");
+	    if (!glintRegionString) {
+		// psWarning ("GLINT.REGION entry is missing REGION entry");
+		continue;
+	    }
+	    psRegion glintRegion = psRegionFromString (glintRegionString);
+
+	    // select stars that land in this regio
+	    if (star->FP->x < glintRegion.x0) continue;
+	    if (star->FP->x > glintRegion.x1) continue;
+	    if (star->FP->y < glintRegion.y0) continue;
+	    if (star->FP->y > glintRegion.y1) continue;
+
+	    double glintLength = GLINT_LENGTH_MAG_SLOPE*(GLINT_LENGTH_MAG_ZERO - star->Mag);
+
+	    char *glintType = psMetadataLookupStr (&status, glintItem->data.md, "GLINT.TYPE");
+	    if (!status) {
+		psWarning ("GLINT.REGION entry is missing TYPE entry");
+		continue;
+	    }
+
+	    // depending on the glint type, we need to find either the chips in the row or in the column.
+	    if (!strcasecmp(glintType, "TOP") || !strcasecmp(glintType, "BOTTOM")) {
+		for (int nChip = 0; nChip < fpa->chips->n; nChip++) {
+
+		    pmChip *chip = fpa->chips->data[nChip];
+		    if (!chip) continue;
+
+		    if (!psastroFindChipInXrange (fpa, nChip, star->FP->x, star->FP->y)) {
+			continue;
+		    }
+
+		    // find the coordinate of the end point
+		    double xFPA  = star->FP->x;
+		    double yFPA0 = star->FP->y;
+		    double yFPA1;
+		    if (!strcasecmp(glintType, "TOP")) {
+			yFPA1 = yFPA0 - glintLength;
+		    } else {
+			yFPA1 = yFPA0 + glintLength;
+		    }
+
+		    // FPA coordinates of intersections with chip edges 
+		    double yFPAs, yFPAe;
+		    psastroFindChipYedges (&yFPAs, &yFPAe, fpa, nChip);
+
+		    if (yFPA0 > yFPA1) PS_SWAP (yFPA0, yFPA1);
+		    if (yFPAs > yFPAe) PS_SWAP (yFPAs, yFPAe);
+
+		    // does this glint cross this chip?
+		    if (yFPA0 > yFPAe) continue;
+		    if (yFPA1 < yFPAs) continue;
+
+		    yFPA0 = PS_MAX (yFPA0, yFPAs);
+		    yFPA1 = PS_MIN (yFPA1, yFPAe);
+
+		    double xChip0, yChip0, xChip1, yChip1;
+		    psastroFPAtoChip (&xChip0, &yChip0, fpa, nChip, xFPA, yFPA0);
+		    psastroFPAtoChip (&xChip1, &yChip1, fpa, nChip, xFPA, yFPA1);
+
+		    // we now have the location of the glint ends or edge-intersections in chip coordinates
+		    // double-check if this chip overlaps this glint
+		    if (xChip0 > xChip1) PS_SWAP (xChip0, xChip1);
+		    if (yChip0 > yChip1) PS_SWAP (yChip0, yChip1);
+
+		    // bounds of this chip
+		    psRegion *region = pmChipPixels (chip);
+		    if (yChip1 < region->y0) continue;
+		    if (yChip0 > region->y1) continue;
+
+		    // this glint touches this chip. calculate the start and end
+		    // coordinates on this chip
+		    double xChipS = PS_MAX(xChip0 - 0.5*glintWidth, region->x0);
+		    double xChipE = PS_MIN(xChip1 + 0.5*glintWidth, region->x1);
+		    double yChipS = PS_MAX (yChip0, region->y0);
+		    double yChipE = PS_MIN (yChip1, region->y1);
+
+		    // select the 0th readout of the 0th cell for this chip
+		    if (!chip->cells) continue;
+		    if (!chip->cells->n) continue;
+		    pmCell *glintCell = chip->cells->data[0];
+		    if (!glintCell) continue;
+		    if (!glintCell->readouts) continue;
+		    if (!glintCell->readouts->n) continue;
+		    pmReadout *glintReadout = glintCell->readouts->data[0];
+		    if (!glintReadout) continue;
+
+		    // save the glints on the readout->analysis metadata, creating if needed
+		    psArray *glints = psMetadataLookupPtr (&status, glintReadout->analysis, "PSASTRO.GLINTS");
+		    if (glints == NULL) { 
+			glints = psArrayAllocEmpty (100);
+			if (!psMetadataAdd (glintReadout->analysis, PS_LIST_TAIL, "PSASTRO.GLINTS", PS_DATA_ARRAY, "astrometry matches", glints)) {
+			    psWarning("failure to add glints to readout");
+			    psFree (glints);
+			    continue;
+			}
+			psFree (glints);
+		    }
+
+		    fprintf (stderr, "glint %s : %f,%f to %f,%f (%f,%f to %f,%f)\n", glintType, xChip0, yChip0, xChip1, yChip1, xChipS, yChipS, xChipE, yChipE);
+		    psRegion *glint = psRegionAlloc(xChipS, xChipE, yChipS, yChipE);
+		    psArrayAdd (glints, 100, glint);
+		    psFree (glint);
+		    psFree (region);
+		}
+	    }
+
+	    // depending on the glint type, we need to find either the chips in the row or in the column.
+	    if (!strcasecmp(glintType, "LEFT") || !strcasecmp(glintType, "RIGHT")) {
+		for (int nChip = 0; nChip < fpa->chips->n; nChip++) {
+
+		    pmChip *chip = fpa->chips->data[nChip];
+		    if (!chip) continue;
+
+		    if (!psastroFindChipInYrange (fpa, nChip, star->FP->x, star->FP->y)) {
+			continue;
+		    }
+
+		    // find the coordinate of the end point
+		    double yFPA  = star->FP->y;
+		    double xFPA0 = star->FP->x;
+		    double xFPA1;
+		    if (!strcasecmp(glintType, "RIGHT")) {
+			xFPA1 = xFPA0 - glintLength;
+		    } else {
+			xFPA1 = xFPA0 + glintLength;
+		    }
+
+		    // FPA coordinates of intersections with chip edges 
+		    double xFPAs, xFPAe;
+		    psastroFindChipXedges (&xFPAs, &xFPAe, fpa, nChip);
+
+		    if (xFPA0 > xFPA1) PS_SWAP (xFPA0, xFPA1);
+		    if (xFPAs > xFPAe) PS_SWAP (xFPAs, xFPAe);
+
+		    // does this glint cross this chip?
+		    if (xFPA0 > xFPAe) continue;
+		    if (xFPA1 < xFPAs) continue;
+
+		    xFPA0 = PS_MAX (xFPA0, xFPAs);
+		    xFPA1 = PS_MIN (xFPA1, xFPAe);
+
+		    double xChip0, yChip0, xChip1, yChip1;
+		    psastroFPAtoChip (&xChip0, &yChip0, fpa, nChip, xFPA0, yFPA);
+		    psastroFPAtoChip (&xChip1, &yChip1, fpa, nChip, xFPA1, yFPA);
+
+		    // we now have the location of the glint ends or edge-intersections in chip coordinates
+		    // double-check if this chip overlaps this glint
+		    if (xChip0 > xChip1) PS_SWAP (xChip0, xChip1);
+		    if (yChip0 > yChip1) PS_SWAP (yChip0, yChip1);
+
+		    // bounds of this chip
+		    psRegion *region = pmChipPixels (chip);
+		    if (xChip1 < region->x0) continue;
+		    if (xChip0 > region->x1) continue;
+
+		    // this glint touches this chip. calculate the start and end
+		    // coordinates on this chip
+		    double yChipS = PS_MAX (yChip0 - 0.5*glintWidth, region->y0);
+		    double yChipE = PS_MIN (yChip1 + 0.5*glintWidth, region->y1);
+		    double xChipS = PS_MAX (xChip0, region->x0);
+		    double xChipE = PS_MIN (xChip1, region->x1);
+
+		    // select the 0th readout of the 0th cell for this chip
+		    if (!chip->cells) continue;
+		    if (!chip->cells->n) continue;
+		    pmCell *glintCell = chip->cells->data[0];
+		    if (!glintCell) continue;
+		    if (!glintCell->readouts) continue;
+		    if (!glintCell->readouts->n) continue;
+		    pmReadout *glintReadout = glintCell->readouts->data[0];
+		    if (!glintReadout) continue;
+
+		    // save the glints on the readout->analysis metadata, creating if needed
+		    psArray *glints = psMetadataLookupPtr (&status, glintReadout->analysis, "PSASTRO.GLINTS");
+		    if (glints == NULL) { 
+			glints = psArrayAllocEmpty (100);
+			if (!psMetadataAdd (glintReadout->analysis, PS_LIST_TAIL, "PSASTRO.GLINTS", PS_DATA_ARRAY, "astrometry matches", glints)) {
+			    psWarning("failure to add glints to readout");
+			    psFree (glints);
+			    continue;
+			}
+			psFree (glints);
+		    }
+
+		    fprintf (stderr, "glint %s : %f,%f to %f,%f (%f,%f to %f,%f)\n", glintType, xChip0, yChip0, xChip1, yChip1, xChipS, yChipS, xChipE, yChipE);
+		    psRegion *glint = psRegionAlloc(xChipS, xChipE, yChipS, yChipE);
+		    psArrayAdd (glints, 100, glint);
+		    psFree (glint);
+		    psFree (region);
+		}
+	    }
+	}
+    }
+    psastroExtractFreeChipBounds();
+    return true;
+}
+
+// XXX need to place the glints on the right analysis...
+// psMetadataAdd (fpa->analysis, PS_LIST_TAIL, "PSASTRO.GLINT.STARS", PS_DATA_ARRAY, "possible glint stars", glintStars);
+// psFree (glintStars);
+
+
+
+# if (0)
+
+	    // depending on the glint type, we need to find either the chips in the row or in the column.
+	    if (!strcasecmp(glintType, "LEFT") || !strcasecmp(glintType, "RIGHT")) {
+		for (int nChip = 0; nChip < fpa->chips->n; nChip++) {
+
+		    double xChip0, yChip0;
+		    if (!psastroFindChipInYrange (&xChip0, &yChip0, fpa, nChip, star->FP->x, star->FP->y)) {
+			continue;
+		    }
+
+		    pmChip *chip = fpa->chips->data[nChip];
+
+		    // select the 0th readout of the 0th cell for this chip
+		    if (!chip) continue;
+		    if (!chip->cells) continue;
+		    if (!chip->cells->n) continue;
+		    pmCell *glintCell = chip->cells->data[0];
+		    if (!glintCell) continue;
+		    if (!glintCell->readouts) continue;
+		    if (!glintCell->readouts->n) continue;
+		    pmReadout *glintReadout = glintCell->readouts->data[0];
+		    if (!glintReadout) continue;
+
+		    // save the glints on the readout->analysis metadata, creating if needed
+		    psArray *glints = psMetadataLookupPtr (&status, glintReadout->analysis, "PSASTRO.GLINTS");
+		    if (glints == NULL) { 
+			glints = psArrayAllocEmpty (100);
+			if (!psMetadataAdd (glintReadout->analysis, PS_LIST_TAIL, "PSASTRO.GLINTS", PS_DATA_ARRAY, "astrometry matches", glints)) {
+			    psWarning("failure to add glints to readout");
+			    continue;
+			}
+			psFree (glints);
+		    }
+
+		    // bounds of this chip
+		    psRegion *region = pmChipPixels (chip);
+
+		    // find the coordinate of the end point
+		    double xChip1, yChip1;
+		    if (!strcasecmp(glintType, "RIGHT")) {
+			if (!psastroFindChipInYrange (&xChip1, &yChip1, fpa, nChip, star->FP->x - glintLength, star->FP->y)) {
+			    psAbort ("inconsistent chip position result"); 
+			}
+		    } else {
+			// find the coordinate of the end point
+			if (!psastroFindChipInYrange (&xChip1, &yChip1, fpa, nChip, star->FP->x + glintLength, star->FP->y)) {
+			    psAbort ("inconsistent chip position result"); 
+			}
+		    }
+
+		    // we have the location in chip coordinates of the two glint end-points.
+		    // check if this chip overlaps this glint
+		    if (xChip0 > xChip1) PS_SWAP (xChip0, xChip1);
+		    if (xChip1 < region->x0) continue;
+		    if (xChip0 > region->x1) continue;
+
+		    // this glint touches this chip. calculate the start and end
+		    // coordinates on this chip
+		    double yChip;
+		    double xChipS = PS_MAX (xChip0, region->x0);
+		    double xChipE = PS_MIN (xChip1, region->x1);
+
+		    // if the line has any tilt (in chip coordinates), interpolate for Y:
+		    if (fabs(region->y1 - region->y0) > 1.0) {
+			double yChipS = yChip0 + (yChip1 - yChip0) * (xChipS - xChip0) / (xChip1 - xChip0);
+			double yChipE = yChip0 + (yChip1 - yChip0) * (xChipE - xChip0) / (xChip1 - xChip0);
+			yChip = 0.5*(yChipS + yChipE);
+		    } else {
+			yChip = 0.5*(yChip0 + yChip1);
+		    }
+
+		    fprintf (stderr, "glint %s : %f,%f to %f,%f (%f - %f @ %f)\n", glintType, xChip0, yChip0, xChip1, yChip1, xChipS, xChipE, yChip);
+		    psRegion *glint = psRegionAlloc(xChipS, xChipE, yChip - 0.5*glintWidth, yChip + 0.5*glintWidth);
+		    psArrayAdd (glints, 100, glint);
+		    psFree (glint);
+		    psFree (region);
+		}
+	    }
+# endif
Index: branches/pap/psastro/src/psastroLoadRefstars.c
===================================================================
--- branches/pap/psastro/src/psastroLoadRefstars.c	(revision 23948)
+++ branches/pap/psastro/src/psastroLoadRefstars.c	(revision 25027)
@@ -128,4 +128,13 @@
     if (!strcmp (outformat, "PS1_DEV_0")) {
       refstars = psastroReadGetstar_PS1_DEV_0 (fits);
+
+      // XXX test
+      // FILE *outfile = fopen ("refstars.dat", "w");
+      // assert (outfile);
+      // for (int nn = 0; nn < refstars->n; nn++) {
+      // 	  pmAstromObj *ref = refstars->data[nn];
+      // 	  fprintf (outfile, "%lf %lf\n", ref->sky->r*PS_DEG_RAD, ref->sky->d*PS_DEG_RAD);
+      // }
+      // fclose (outfile);
     }
     if (refstars == NULL) {
@@ -181,8 +190,10 @@
             ref->sky->d   = RAD_DEG*psMetadataLookupF32 (&status, row, "DEC");
             ref->Mag      = 0.001*psMetadataLookupS32 (&status, row, "MAG");  // ELIXIR uses millimags
+	    ref->Color    = 0.0;
         } else {
             ref->sky->r   = RAD_DEG*psMetadataLookupF64 (&status, row, "RA");
             ref->sky->d   = RAD_DEG*psMetadataLookupF64 (&status, row, "DEC");
             ref->Mag      = psMetadataLookupF32 (&status, row, "MAG"); // PANSTARRS uses mags
+	    ref->Color    = 0.0;
         }
 
@@ -216,7 +227,15 @@
         psMetadata *row = psFitsReadTableRow(fits, i); // Table row
 
-        ref->sky->r   = RAD_DEG*psMetadataLookupF32 (&status, row, "RA");
-        ref->sky->d   = RAD_DEG*psMetadataLookupF32 (&status, row, "DEC");
+        ref->sky->r   = RAD_DEG*psMetadataLookupF64 (&status, row, "RA");
+        ref->sky->d   = RAD_DEG*psMetadataLookupF64 (&status, row, "DEC");
         ref->Mag      = psMetadataLookupF32 (&status, row, "MAG");
+        float MagC1   = psMetadataLookupF32 (&status, row, "MAG_C1");
+        float MagC2   = psMetadataLookupF32 (&status, row, "MAG_C2");
+	if (!isnan(MagC1) && !isnan(MagC2)) {
+	    ref->Color = MagC1 - MagC2;
+	} else {
+	    // XXX save the color and the slope in the table header?
+	    ref->Color = 0.0;
+	}
 
         // XXX VERY temporary hack to avoid M31 bulge
Index: branches/pap/psastro/src/psastroMaskUpdates.c
===================================================================
--- branches/pap/psastro/src/psastroMaskUpdates.c	(revision 23948)
+++ branches/pap/psastro/src/psastroMaskUpdates.c	(revision 25027)
@@ -28,7 +28,8 @@
     pmCell *cell = NULL;
     pmReadout *readout = NULL;
-    float zeropt, exptime;
+    float zeropt, exptime, GHOST_MAX_MAG;
 
     psImageMaskType ghostMaskValue = pmConfigMaskGet("GHOST", config); // Mask value for ghost pixels
+    psImageMaskType glintMaskValue = pmConfigMaskGet("GHOST", config); // Mask value for glint pixels (overload ghost)
     psImageMaskType spikeMaskValue = pmConfigMaskGet("SPIKE", config); // Mask value for ghost pixels
     psImageMaskType starMaskValue  = pmConfigMaskGet("STARCORE", config); // Mask value for ghost pixels
@@ -53,6 +54,11 @@
     }
     bool COUNT_GHOSTS = psMetadataLookupF32 (&status, recipe, "REFSTAR_COUNT_GHOSTS");
-    double GHOST_MAX_MAG = psMetadataLookupF32 (&status, recipe, "GHOST_MAX_MAG");
     int nGhosts = 0;
+
+    // convert star positions to glint positions and add to the fpa->analysis data
+    if (!psastroLoadGlints (config)) {
+        psError(PSASTRO_ERR_CONFIG, false, "Error loading glints");
+        return false;
+    }
 
     psLogMsg ("psastro", PS_LOG_INFO, "generating a bright-star mask");
@@ -79,5 +85,5 @@
 
     // really error-out here?  or just skip?
-    if (!psastroZeroPointFromRecipe (&zeropt, &exptime, fpa, recipe)) {
+    if (!psastroZeroPointFromRecipe (&zeropt, &exptime, &GHOST_MAX_MAG, fpa, recipe)) {
         psLogMsg ("psastro", PS_LOG_INFO, "failed to load zeropt data from recipe");
         return false;
@@ -221,6 +227,8 @@
                     }
 
+		    // This masking option was needed for persistent charge trails in GPC1; it
+		    // has since been replaced with 'burntool', which is applied upon readout
+		    // by the camera software, and therefore is aware of the image sequence.
 		    if (REFSTAR_MASK_BLEED) {
-
 			// convert x,y chip coordinates to cells in maskChip
 			pmCell *refCell = pmCellInChip (refChip, ref->chip->x, ref->chip->y);
@@ -245,24 +253,74 @@
                 }
 
-                // select the raw objects for this readout (loaded in psastroExtract.c)
+                // select the ghost object for this readout (loaded in psastroExtractGhosts.c).
+                // These differ from the reference stars since the star position is not
+                // contained by the readout; instead, the ghost position is predicted based on
+                // the ghost model, and the ghost positions associated with a given readout are
+                // supplied here.
                 psArray *ghosts = psMetadataLookupPtr (&status, readout->analysis, "PSASTRO.GHOSTS");
-                if (ghosts == NULL) { continue; }
-
-                // identify the bright stars of interest
-                for (int i = 0; i < ghosts->n; i++) {
-                    psastroGhost *ghost = ghosts->data[i];
-		    // XXX bright vs faint ghost bits? (OR with SUSPECT) 
-		    psastroMaskEllipticalAnnulus (readoutMask->mask, ghostMaskValue, ghost->chip->x, ghost->chip->y, ghost->inner, ghost->outer);
-                }
-
-		// select the raw objects for this readout, flag is they fall in a mask
-		psArray *rawstars = psMetadataLookupPtr (&status, readout->analysis, "PSASTRO.RAWSTARS");
-		if (rawstars == NULL) return false;
+                if (ghosts) { 
+		    // mask the ghosts on this readout
+		    for (int i = 0; i < ghosts->n; i++) {
+			psastroGhost *ghost = ghosts->data[i];
+			// XXX bright vs faint ghost bits? (OR with SUSPECT) 
+			psastroMaskEllipticalAnnulus (readoutMask->mask, ghostMaskValue, ghost->chip->x, ghost->chip->y, ghost->inner, ghost->outer);
+		    }
+		}
+
+                // Select the glint mask regions for this readout (loaded in
+                // psastroExtractGlints.c).  These glint regions are defined as rectangular
+                // boxes and are generated for each chip based on the position of the bright
+                // stars beyond the edge of the focal plane.  This masking is currently very
+                // GPC1-specific
+                psArray *glints = psMetadataLookupPtr (&status, readout->analysis, "PSASTRO.GLINTS");
+                if (glints) {
+		    // mask the glints on this readout
+		    for (int i = 0; i < glints->n; i++) {
+			psRegion *glint = glints->data[i];
+			psastroMaskRectangle (readoutMask->mask, glintMaskValue, glint->x0, glint->y0, glint->x1, glint->y1);
+		    }
+		}
+
+		// this probably should move into a function of its own:
+		{
+		    // select the raw objects for this readout, flag is they fall in a mask
+		    psArray *inSources = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.SOURCES");
+		    if (inSources == NULL) continue;
 		
-		// XXX finish this:
-		for (int i = 0; i < rawstars->n; i++) {
-		    pmAstromObj *raw = rawstars->data[i];
-		    psImageMaskType value = readoutMask->mask->data.PS_TYPE_IMAGE_MASK_DATA[(int)(raw->chip->x)][(int)(raw->chip->y)];
-		    if (value) continue;
+		    // create a replacement output array:
+		    // psArray *outSources = psAllocArrayEmpty(100);
+
+		    // XXX finish this: raise a bit for stars that land on certain types of masks;
+		    // others (eg, bright star core) should be ignored.
+		    for (int i = 0; i < inSources->n; i++) {
+			pmSource *source = inSources->data[i];
+
+			int xChip = source->peak->x;
+			int yChip = source->peak->y;
+
+			bool onChip = true;
+			onChip &= (xChip >= 0);
+			onChip &= (xChip < readoutMask->mask->numCols);
+			onChip &= (yChip >= 0);
+			onChip &= (yChip < readoutMask->mask->numRows);
+			if (!onChip) {
+			    // if the source is off the edge of the chip, raise a different bit?
+			    source->mode |= PM_SOURCE_MODE_OFF_CHIP;
+			    continue;
+			}
+
+			psImageMaskType value = readoutMask->mask->data.PS_TYPE_IMAGE_MASK_DATA[yChip][xChip];
+			if (value & ghostMaskValue) {
+			    source->mode |= PM_SOURCE_MODE_ON_GHOST;
+			}
+			// XXX note that for now, glint and ghost are identical
+			pmSourceMode PM_SOURCE_MODE_ON_GLINT = PM_SOURCE_MODE_ON_GHOST;
+			if (value & glintMaskValue) {
+			    source->mode |= PM_SOURCE_MODE_ON_GLINT;
+			}
+			if (value & spikeMaskValue) {
+			    source->mode |= PM_SOURCE_MODE_ON_SPIKE;
+			}
+		    }
 		}
             }
Index: branches/pap/psastro/src/psastroMaskUtils.c
===================================================================
--- branches/pap/psastro/src/psastroMaskUtils.c	(revision 23948)
+++ branches/pap/psastro/src/psastroMaskUtils.c	(revision 25027)
@@ -181,6 +181,15 @@
 bool psastroMaskEllipticalAnnulus (psImage *mask, psImageMaskType value, float x0, float y0, psEllipseAxes eInner, psEllipseAxes eOuter) {
 
+    // skip the masking if the outer ellipse is nonsensical
+    psEllipseShape sOuter = psEllipseAxesToShape (eOuter);
+    if (isnan(sOuter.sx) || isnan(sOuter.sy) || isnan(sOuter.sxy)) return false;
+
     psEllipseShape sInner = psEllipseAxesToShape (eInner);
-    psEllipseShape sOuter = psEllipseAxesToShape (eOuter);
+    if (isnan(sInner.sx) || isnan(sInner.sy) || isnan(sInner.sxy)) {
+	// use a solid ellipse if the inner ellipse is nonsensical
+	sInner.sx = 0.1;
+	sInner.sy = 0.1;
+	sInner.sxy = 0.0;
+    }
 
     // phi is the coordinate along the elliptical path
Index: branches/pap/psastro/src/psastroModelAnalysis.c
===================================================================
--- branches/pap/psastro/src/psastroModelAnalysis.c	(revision 23948)
+++ branches/pap/psastro/src/psastroModelAnalysis.c	(revision 25027)
@@ -153,5 +153,8 @@
 
     psStats *stats = psStatsAlloc (PS_STAT_SAMPLE_MEDIAN | PS_STAT_SAMPLE_STDEV);
-    psVectorStats (stats, posZero, NULL, NULL, 0);
+    if (!psVectorStats (stats, posZero, NULL, NULL, 0)) {
+	psError(PS_ERR_UNKNOWN, false, "failure to measure stats");
+	return false;
+    }
 
     fprintf (outfile, "# pos zero %f +/- %f\n", stats->sampleMedian, stats->sampleStdev);
Index: branches/pap/psastro/src/psastroModelFitBoresite.c
===================================================================
--- branches/pap/psastro/src/psastroModelFitBoresite.c	(revision 23948)
+++ branches/pap/psastro/src/psastroModelFitBoresite.c	(revision 25027)
@@ -62,10 +62,16 @@
 
     // center (Xo) = mean(Xo), RX = range / 2
-    psVectorStats (stats, Xo, NULL, NULL, 0);
+    if (!psVectorStats (stats, Xo, NULL, NULL, 0)) {
+	psError(PS_ERR_UNKNOWN, false, "failure to measure stats");
+	return NULL;
+    }
     params->data.F32[PAR_X0] = stats->sampleMean;
     params->data.F32[PAR_RX] = (stats->max - stats->min) / 2.0;
 
     // center (Yo) = mean(Yo), RY = range / 2
-    psVectorStats (stats, Yo, NULL, NULL, 0);
+    if (!psVectorStats (stats, Yo, NULL, NULL, 0)) {
+	psError(PS_ERR_UNKNOWN, false, "failure to measure stats");
+	return NULL;
+    }
     params->data.F32[PAR_Y0] = stats->sampleMean;
     params->data.F32[PAR_RY] = (stats->max - stats->min) / 2.0;
Index: branches/pap/psastro/src/psastroMosaicOneChip.c
===================================================================
--- branches/pap/psastro/src/psastroMosaicOneChip.c	(revision 23948)
+++ branches/pap/psastro/src/psastroMosaicOneChip.c	(revision 25027)
@@ -58,8 +58,13 @@
 
     // modify the order to correspond to the actual number of matched stars:
-    if ((match->n < 17) && (order >= 3)) order = 2;
-    if ((match->n < 13) && (order >= 2)) order = 1;
-    if ((match->n <  9) && (order >= 1)) order = 0;
-    if ((match->n <  3) || (order < 0) || (order > 3)) {
+    int Ndof_min = 3;
+    int order_max = 0.5*(sqrt(4*match->n - 4*Ndof_min + 1) - 3);
+    order = PS_MIN (order, order_max);
+
+    // if ((match->n < 17) && (order >= 3)) order = 2;
+    // if ((match->n < 13) && (order >= 2)) order = 1;
+    // if ((match->n <  9) && (order >= 1)) order = 0;
+
+    if (order < 0) {
         psLogMsg ("psastro", 3, "insufficient stars (%ld) or invalid order (%d)", match->n, order);
         return false;
Index: branches/pap/psastro/src/psastroOneChipFit.c
===================================================================
--- branches/pap/psastro/src/psastroOneChipFit.c	(revision 23948)
+++ branches/pap/psastro/src/psastroOneChipFit.c	(revision 25027)
@@ -64,6 +64,18 @@
         // modify the order to correspond to the actual number of matched stars:
         int Ndof_min = 3;
-        int order_max = 0.5*(3 + sqrt(4*match->n - 4*Ndof_min + 1));
+        int order_max = 0.5*(sqrt(4*match->n - 4*Ndof_min + 1) - 3);
         order = PS_MIN (order, order_max);
+
+	// order 0 : Ro -> nterms = 1 * 2;
+	// order 1 : Ro, Rx, Ry -> nterms = 3 * 2;
+	// order 2 : Ro, Rx, Ry, Rxx, Rxy, Ryy -> nterms = 6 * 2;
+	// order 3 : Ro, Rx, Ry, Rxx, Rxy, Ryy, Rxxx, Rxxy, Rxyy, Ryyy -> nterms = 10 * 2
+	// 2*(N+1)*(N+2)/2 = (N+1)*(N+2) = nterms;
+	// (order+1)(order+2) + ndof = nvalues
+	// order^2 + 3*order + 2 + ndof = nvalue;
+	// order^2 + 3*order + 2 + ndof - nvalue = 0;
+	// 2*order = -3 +/- sqrt (9 - 4*(2 - nvalue + ndof));
+	// 2*order = -3 +/- sqrt (9 - 8 + 4*nvalue - 4*ndof);
+	// 2*order = (sqrt (1 + 4*nvalue - 4*ndof) - 3);
 
         // if ((match->n < 11) && (order >= 3)) order = 2;
Index: branches/pap/psastro/src/psastroZeroPoint.c
===================================================================
--- branches/pap/psastro/src/psastroZeroPoint.c	(revision 23948)
+++ branches/pap/psastro/src/psastroZeroPoint.c	(revision 25027)
@@ -50,5 +50,5 @@
 
     // really error-out here?  or just skip?
-    if (!psastroZeroPointFromRecipe (&zeropt, &exptime, fpa, recipe)) {
+    if (!psastroZeroPointFromRecipe (&zeropt, &exptime, NULL, fpa, recipe)) {
 	psLogMsg ("psastro", PS_LOG_INFO, "failed to load zeropt data from recipe");
 	return false;
@@ -126,5 +126,8 @@
     // this analysis has too few data points to use the robust median method
     psStats *stats = psStatsAlloc (PS_STAT_CLIPPED_MEAN | PS_STAT_CLIPPED_STDEV);
-    psVectorStats (stats, dMag, NULL, NULL, 0);
+    if (!psVectorStats (stats, dMag, NULL, NULL, 0)) {
+	psError(PS_ERR_UNKNOWN, false, "failure to measure stats");
+	return false;
+    }
     fprintf (stderr, "zero point %f +/- %f using %d stars; transparency %f\n", stats->clippedMean, stats->clippedStdev, Npts, zeropt - stats->clippedMean);
 
@@ -145,5 +148,5 @@
   return false; }
 
-bool psastroZeroPointFromRecipe (float *zeropt, float *exptime, pmFPA *fpa, psMetadata *recipe) {
+bool psastroZeroPointFromRecipe (float *zeropt, float *exptime, float *ghostMaxMag, pmFPA *fpa, psMetadata *recipe) {
 
     bool status;
@@ -170,5 +173,5 @@
 	char *refFilter = psMetadataLookupStr (&status, refItem->data.md, "FILTER");
 	if (!status) {
-	    psLogMsg ("psastro", PS_LOG_INFO, "a PHOTCODE.DATA recipe folder is missing FILTER");
+	    // psLogMsg ("psastro", PS_LOG_INFO, "a PHOTCODE.DATA recipe folder is missing FILTER");
 	    continue;
 	}
@@ -181,6 +184,13 @@
 	*zeropt = psMetadataLookupF32 (&status, refItem->data.md, "ZEROPT");
 	if (!status) {
-	    psLogMsg ("psastro", PS_LOG_INFO, "a PHOTCODE.DATA recipe folder is missing FILTER");
+	    psLogMsg ("psastro", PS_LOG_INFO, "a PHOTCODE.DATA recipe folder is missing ZEROPT");
 	    continue;
+	}
+	if (ghostMaxMag) {
+	    *ghostMaxMag = psMetadataLookupF32 (&status, refItem->data.md, "GHOST_MAX_MAG");
+	    if (!status) {
+		psLogMsg ("psastro", PS_LOG_INFO, "a PHOTCODE.DATA recipe folder is missing GHOST_MAX_MAG");
+		continue;
+	    }
 	}
 	psFree (iter);
