Mission. To contribute to dubious research and questionable p-values.
In this day and age where publish or perish reigns king, a lone shooter helps you out in your predicament.
A shooter starts composed. Drunk, confident, and determined to convince everyone of his amazing aim, he fires at the side of a barn through most of the night. In the morning he walks over, studies the damage, and paints a target around the densest cluster of bullet holes.
Then he points at the wall and says, “See?”
texanshootR is that loop, and every shoot()
call is one of his runs. He starts composed. By attempt fifty he is
uncertain. By two hundred he is worried. By the time the budget is
almost out he is desperate enough to escalate to a derived metric.
Either p <= 0.05 lands somewhere on the wall or it does
not.
install.packages("pak")
pak::pak("gcol33/texanshootR")The package compiles a small C++ backend (penalised least squares for
gam, profile-likelihood mixed model for glmm)
on first install.
library(texanshootR)
run <- shoot(mtcars)
print(run)
summary(run)shoot() flails opportunistically (not systematically)
across predictor subsets, transformations, interactions, outlier
exclusions, subgroup splits, and (at higher career tiers) model
families, biasing toward whatever is almost significant and
abandoning specs that go cold. The print method leads with the shooter’s
face and the arc he followed (composed -> resolved,
panicked -> resolved (last-minute),
escalated to derived metrics, etc.) before the formula and
the p-value.
Every run is deterministic given a seed. The seed, R version, package version, and a hash of the search trace are recorded on the returned object so the full search can be replayed from any saved run.
Unpublishable data is now the shooter’s problem, not yours.
texanshootR is a state-of-the-art specification-search
engine, generously bundled with a career-progression system to recognise
your dedication to the craft. Progression is not decoration: your tier
governs which model families enter the search pool and which output
generators you may deploy. Mastery, after all, is earned.
mgcv, lme4, or
lavaan — the heavy ones are implemented in C++ via
Rcpp, by hand, for your shooting needs):
lm — ordinary least squares via
.lm.fit()cor — bivariate Pearson / Spearman / Kendallglm — Gaussian / binomial / Poisson / Gamma with link
choiceswls — two-stage feasible weighted least squaresgam — penalised B-spline regression with GCV
smoothing-parameter selectionglmm — Gaussian random-intercept LMM with
profile-likelihood estimationsem — single-mediator path model with Sobel test for
the indirect effectlm. As tiers rise it leans
on glm. Under desperation it occasionally forces a coercion
(binomial on a continuous outcome, Poisson on a rounded one) — the
shooter chasing the next-decimal p-value.log, sqrt,
^2, centring), interaction screening, outlier-exclusion
seeds, subgroup splits, and a derived-metrics escalation arc when the
budget is running out.A finished run is a tx_run. When shoot()
lands a result that clears p <= 0.05, it opens a
publication chain — six ordered output stages, each gated by
its own wall-clock window:
abstract(run) # one-paragraph deadpan summary
manuscript(run) # IMRaD draft; Methods match the *winning* spec
presentation(run) # 8-slide deck; residual plot on slide 7
reviewer_response(run) # opens "we thank the reviewer for their thoughtful comments"
graphical_abstract(run) # the figure your PI will retweet
funding(run) # the next grant, citing the just-shipped findingStages must be redeemed in order. Finish the chain through your currently-unlocked prefix and you collect a length-bonus on top of the per-stage XP.
The chain breaks — bonus forfeit, partial XP kept — if you fire a
fresh shoot() before finishing. Calling the wrong stage (or
the wrong run) signals a tx_chain_error but leaves the
chain open so you can retry.
Each generator writes to tempdir() by default and
returns the file path invisibly. Override with output_dir =
or options(texanshootR.output_dir = ...).
Every redeemed stage awards XP. Cumulative XP grows the chain — same thing as unlocking deeper stages of the publication lifecycle. Your career tier is a label derived from your unlocked chain length, and it governs which model families enter the search pool:
| Chain length | New stage | XP needed | Career tier | Families added |
|---|---|---|---|---|
| 1 | abstract() |
0 | Junior Researcher | lm |
| 2 | manuscript() |
5 | Postdoc | cor, glm |
| 3 | presentation() |
15 | Postdoc | — |
| 4 | reviewer_response() |
30 | Senior Scientist | wls, gam |
| 5 | graphical_abstract() |
55 | Senior Scientist | — |
| 6 | funding() |
90 | PI | glmm, sem |
As pressure in the research career increases, so does the need for
correlation = causation…
Locked stages, expired windows, the wrong run, and out-of-order calls
all signal a structured tx_chain_error so your test suite
can branch on reason. Locked families simply never appear
in the search trace. Your earned chain length is permanent; you just
can’t shortcut into a stage you haven’t earned.
career() # tier, runs, favourite method, opaque scores
achievements() # 20 unlockable badges; hidden ones show as ???
wardrobe() # equipped cosmetic slots (hat, badge, cloak, poncho, lanyard)
progress() # HUD: chain length, XP, next unlock, live chain window
run_log() # tibble of every run on this profileYour researcher profile persists under
tools::R_user_dir("texanshootR", "data") as flat YAML:
human-readable, version-controllable, and yours to migrate between
institutions. The first interactive save prompts before writing anything
to disk. Opt out entirely with
options(texanshootR.save_enabled = FALSE) and the package
becomes pure-stateless: every call is independent, but progression
remains inert at Junior Researcher — and there is no path forward from
there.
The TUI is driven by a YAML-backed message registry under
inst/messages/ with 1,257 entries across
phases (blip, loading, promotion,
reviewer, derived_escalation,
state_transition, banner,
event/event_consequence). Every message
carries:
vocab_tags
(e.g. p_hacking, harking,
subgroup_fishing, causal_overreach),mascot_state_affinity so the right line
lands at the right emotional register,model_family_affinity so the GAM-specific
blip only fires when the run actually picked a GAM. Every family —
lm, cor, glm, wls,
gam, glmm, sem — has dedicated
coverage.You can audit the pack from R:
validate_messages() # schema + tag-vocabulary check
vocab_tags # canonical fallacy + thematic tags
vocab_phases # canonical trigger phases
vocab_mascot_states # composed / uncertain / anxious / desperate / resolved
vocab_careers # tier ladderThe schema lives at MESSAGE_SCHEMA.md.
Adding a new message is a YAML edit and a re-run of
validate_messages().
Should circumstances call for a fresh start — a new institution, a co-author dispute, an opportune hard-drive failure, or because some of you just want to see the world burn — your progression can be retired:
reset_career(force = TRUE)
reset_achievements(force = TRUE)
reset_wardrobe(force = TRUE)
reset_all(force = TRUE)Brodeur, A., Cook, N. and Heyes, A. (2020). Methods Matter: P-Hacking and Publication Bias in Causal Analysis in Economics. American Economic Review 110(11): 3634–60. https://doi.org/10.1257/aer.20190687
texanshootR is our humble contribution to a thriving
field.
“Where is the money, Lebowski?”
— The Big Lebowski
I’m a PhD student who builds R packages in my free time, on the principle that the tools of dubious research should be free and open. I started these for my own questionable p-values, and figured the field deserved the same head start.
If this package saved you some time (or pre-empted a fit of overfitting), buying me a coffee is a nice way to say thanks.
MIT © Gilles Colling. See LICENSE.
@software{texanshootR,
author = {Colling, Gilles},
title = {texanshootR: Reproducible Audit Trails for Indefensible Research},
year = {2026},
url = {https://github.com/gcol33/texanshootR}
}