How the background estimate put a bump of each star's own light under that star, why that left a hole around every faint star in the difference image, and why the ePSF fit then needed a fitted background term to look right.
hp_d every star sits in a shallow hole. For faint stars the hole is a large fraction of the outer wing.tessreduce_star_mask_pad_px: 2). With the background rebuilt this way, an ePSF fit with no background term matches the fit that had one.The background is estimated once per frame, from a first-pass difference image, and then subtracted from the science image before the final Hotpants subtraction.
$I$: the science FFI (e⁻/s). $T$: the PS1-based template on the TESS grid, with every star brighter than Tmag 13 removed (bright_maglim), so those stars are fully present in $D_{\rm raw}$.
$K$, $K'$: the PSF-matching kernels of the first pass and of the final Hotpants run.
$D_{\rm raw}$: the estimator input, stored as ks_d + ks_b.
Fit pixels: shared-mask value 0 or 32. Bit 2 marks a circle around each star of radius 9 / 8 / 7 / 6 px for Tmag 8–10 / 10–11 / 11–12 / 12–13; these pixels are excluded.
$\mathcal{F}$: smooth background fit on the fit pixels, then filling of the excluded pixels: biharmonic in production, harmonic (Laplace) since 70cba3c.
$b$: the background map ks_b (e⁻/s). $D$: the difference image hp_d that the ePSF is fitted on.
If $b$ were pure sky, a star would sit on zero in $D$. Anything of the star's own light that ends up in $b$ is subtracted from the star.
Stack the estimator input around isolated stars (no other catalogue star within 16 px), and look at the ring of fit pixels just outside the circle. That ring is what the background fill uses as its boundary value.

The circle radii were chosen for core masking, not for the wing. TESS's PSF is broad, so at the edge of a 9 px circle a T 9 star still contributes a few tenths of an e⁻/s per pixel. That is comparable to the per-pixel noise in a single 30-min FFI.
The same thing shown as images. The left column is exactly what the background fit sees: the fit pixels only, with the circle blank. The other columns are the filled background in three versions, on one colour scale.

In an actual frame, single stars are noisier than the stack: the background map varies by several tenths of an e⁻/s on 10–20 px scales. The bumps are still visible, one per star, when the fixed background is subtracted from the production one.

Hotpants is run on $I - b$, so the bump is removed from the star itself. In $D$ = hp_d each star now sits in a shallow depression the size of its mask circle. I rebuilt the F1 frame with the production code twice: once with production settings (bit-identical to the stored ks_b and hp_d), and once with the fix. Their difference is exactly what the bump took away.

hp_d(fix) − hp_d(production), core r < 3 px greyed: the two Hotpants runs fit slightly different kernels, which changes star cores by a few e⁻/s. The fix puts back +0.16 / +0.06 / +0.07 / +0.04 e⁻/s inside the circle. Bottom: azimuthal profile of ($D$ − local sky − model) / star flux, in units of 1e-4 of the flux per pixel, with one fixed ePSF model, trained on production hp_d. Only the image changes. For T 12–13 stars, production sits up to −0.6 to −1.0 × 1e-4 below that model at 3–6 px, harmonic −0.1 to −0.35, the fix about 0 to +0.2. The model itself is biased toward the production hole, so the zero line is not the truth; the next step refits it.Why it matters most for faint stars: the bump does not shrink in proportion to the star. In production it is still +0.11 e⁻/s for T 12–13, but a T 12.5 star (median flux about 1500 e⁻/s) has only 0.04–0.08 e⁻/s per pixel of its own wing at 6–8 px. So the hole is comparable to the whole faint-star outer wing, and a small fraction of a bright star's.
The forward ePSF fit models each star as flux × ePSF. A hole around every star is something it cannot represent: one shared ePSF cannot be "too faint in the wing" for faint stars only. So in a fit with no background term:
Adding a fitted flat background per star ("bg0") absorbs the hole, which is why that term looked necessary. The test is to remove the cause instead: rebuild the background, refit with no background term, and see whether the result matches production + fitted background. All fits are 5-fold, scored on held-out stars only.

| ePSF fit | local sky after fit, T 10–13 (e⁻/s) | ring 3–4 change, T 10–11 / 11–12 / 12–12.5 / 12.5–13 | XP flux rms (mmag) | axis − far (‰) |
|---|---|---|---|---|
| production, no bg term | −0.122 | — | 14.2 | −1.67 |
| production + fitted flat bg (target) | −0.010 | +0.09 / +0.25 / +0.52 / +0.84 | 14.4 | −1.28 |
| harmonic only, no bg term | −0.085 | +0.05 / +0.17 / +0.43 / +0.42 | 12.7 | −0.45 |
| harmonic + pad 2, no bg term | −0.018 | +0.08 / +0.31 / +0.77 / +0.88 | 13.1 | +0.18 |
| harmonic + pad 4, no bg term | 0.000 | +0.10 / +0.38 / +0.89 / +1.00 | 13.3 | +0.26 |
Local sky after fit: median residual in an annulus around each fitted star, over all-star-fit stars with Tmag 10–13.
Ring 3–4 change: held-out residual / flux in the r = 3–4 px ring minus the same for production without background, ×1e-4 per px; the target row is what a fitted background does.
XP flux rms: scatter of ePSF fluxes against Gaia XP synthetic photometry.
Axis − far: flux offset between stars near and far from the optical axis.
Unchanged in every arm: the brighter-fatter core width, the XP colour term, and the held-out learning curves. The faint-star ePSF memorisation is not a background effect.
The production background bump is centred 0.15–0.9 px toward −x rather than on the star. The star stacks are centred to better than 0.15 px, so this is not a coordinate mix-up. The cause is the PSF: on this CCD the wing is lopsided toward −x and +y, roughly toward the optical axis at about (−58, 2100) px. The rim on that side holds more light, and the fill follows it.

Branch bkg-star-pad, commit 2ecf558 (local, not merged, not pushed). It adds tessreduce_star_mask_pad_px to background_estimate and kernel_fit; the default 0 changes nothing. With value 2, every bit-1/bit-2 star mask is grown by a 2 px disk before the background fit, so the fill's boundary sits where the wing has dropped to about zero. Combined with the harmonic fill (already the default since 70cba3c), this is the recommended setting.
The trade-off is that the background is fitted on fewer pixels: the fit fraction goes from 0.47 to 0.35. Between stars the background swings by about ±0.13 e⁻/s, and the empty-sky noise of hp_d is unchanged (0.51–0.53 e⁻/s). Pad 8 would remove the T 8–10 bump completely, but swings the background by ±0.5 e⁻/s, so it was rejected.
kernel_fit, which use the same estimator./astro/armin/koji/syndiff/dev_runs/localbg_20260930/README.md
/astro/armin/koji/syndiff/dev_runs/localbg_20260930/explainer/ (this page, figures, make_explainer_figs.py)
/astro/armin/koji/syndiff/dev_runs/localbg_20260930/rebuild/ (rebuilt frames, refits, analysis)
/home/kshukawa/syndiff_pipeline/.claude/worktrees/bkg-star-pad (code)