--- title: "Shooting reconstruction with explicit uncertainty" output: rmarkdown::html_vignette: toc: true vignette: > %\VignetteIndexEntry{Shooting reconstruction with explicit uncertainty} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set(collapse = TRUE, comment = "#>", fig.width = 7, fig.height = 6, dpi = 96, out.width = "100%") # on Linux the default X11 png device cannot draw semi-transparent fills; use cairo there if (Sys.info()[["sysname"]] == "Linux" && isTRUE(capabilities("cairo"))) knitr::opts_chunk$set(dev.args = list(png = list(type = "cairo"))) library(forensicR) set.seed(2026) ``` Shooting incident reconstruction answers a narrow question with geometry: given the defects a bullet left, where could it have come from? forensicR follows the standard field workflow (Haag & Haag, *Shooting Incident Reconstruction*; Hueske, *Practical Analysis and Reconstruction of Shooting Incidents*; OSAC 2025-N-0003 terminology) and adds the one thing paper cannot: every result carries the uncertainty of the measurements it came from. Throughout, the frame is x east, y north, z up; azimuths are degrees clockwise from north; a trajectory's direction is the direction of **flight**, and its **anchor** is the defect where the bullet struck. ## 1. Impact angle from a defect A bullet striking a flat, non-yielding surface leaves an elliptical defect. The angle of impact relative to the surface is approximately `asin(width / length)`. Measure both axes and give the measurement error: ```{r impact} impact_angle(width = c(9.1, 6.0), length = c(9.3, 12.5), se = 0.5) ``` Two things to notice. Near 90 degrees (a nearly round defect) the interval is wide: the ellipse simply does not carry much information there. And the method is only valid on surfaces that do not deform: drywall, wood, sheet metal. Not glass, not fabric. ## 2. Three ways to establish a trajectory ### Rod or laser angles at the defect The most common method. Record the vertical angle (angle finder) and the horizontal angle (protractor or total station), both as the direction of flight. Five degrees is a commonly cited uncertainty for rods; use what your validation supports. ```{r rod} t_rod <- trajectory_from_angles(anchor = c(2.0, 5.0, 1.35), azimuth_deg = 352, vertical_deg = -3, se_azimuth = 5, se_vertical = 5, id = "D2 rod") t_rod ``` ### Two points on the path Entry and exit of a wall, a defect and a probe tip, two defects in line. Positional uncertainty is propagated to the angles. The trajectory is anchored at the second point (the impact); the first is kept and becomes the joint of a segmented path (section 6). ```{r twopt} t_2pt <- trajectory_2pt(p1 = c(3.4, 4.2, 1.20), p2 = c(3.73, 4.92, 1.10), se_position = 0.01, id = "D3 two-point") t_2pt ``` ### A single defect's ellipse plus directionality When no rod can be placed. Needs the ellipse, the orientation of its major axis on the wall (0 = vertical, 90 = horizontal, clockwise as seen from the shooter's side) and which end the bullet came from (lead-in mark, pinch point). The wall's facing direction fixes the frame. ```{r ellipse} t_ell <- trajectory_from_defect(anchor = c(1.2, 5.0, 1.60), width = 9.3, length = 10.0, major_axis_deg = 85, wall_azimuth_deg = 180, came_from = "right", se = 0.5, se_orientation = 5, id = "D1 ellipse") t_ell trajs <- list(t_rod, t_2pt, t_ell) trajectory_table(trajs)[, c("id", "method", "azimuth_deg", "se_azimuth", "vertical_deg", "se_vertical")] ``` Compare the uncertainties: the two-point method with centimeter positions is ten times tighter than the rod; the ellipse of a nearly round defect is the loosest. The plots below show the same thing as fans. ## 3. Seeing the cone, not the line ```{r plan} walls <- rbind(room_rect(0, 0, 6, 5), opening(2.5, 0, 3.5, 0, "door")) plot_trajectories(trajs, view = "plan", walls = walls, back = 6, convergence = TRUE) ``` ```{r elev, fig.height = 3.4} plot_trajectories(trajs, view = "elevation", back = 6) ``` The dashed lines in the elevation view are assumed muzzle heights. Where the fan crosses them is where a shooter of that posture could have been. ## 4. Where was the shooter? `origin_zone()` intersects the back-projected line with each assumed height and reports the horizontal distance and position, with Monte Carlo intervals. `max_distance` bounds the search to the scene; `furniture` excludes positions inside objects. ```{r origin} origin_zone(t_rod, heights = c(standing = 1.5, kneeling = 1.0, prone = 0.3), max_distance = 12) ``` `p_reachable` is the fraction of draws that reach that height within the distance limit. A low value means the height is poorly supported by the trajectory, and the interval is conditional on the draws that do reach it. Report both numbers. ## 5. Several shots: do they converge? If two shots came from one position, their back-projected lines should pass close to each other *behind* both defects. `intersect_trajectories()` gives the closest approach, the miss distance and whether it lies behind both defects, all with Monte Carlo spread. ```{r converge} ix <- intersect_trajectories(t_rod, t_2pt) ix$point ix$miss_distance ix$behind_both ix$summary ``` In this example the three defects were simulated from one shooter standing at (2.4, 2.0, 1.5) and the reconstruction recovers that point to a few centimeters. A small miss distance is consistent with, but does not prove, a single firing position. ## 6. Objects on the path With the scene's furniture, `trajectory_obstructions()` reports which objects each back-projected line passes through and between which distances. A hit means the object is an intermediate target (look for a defect in it) or the path must be reconsidered. ```{r obstructions} furn <- rbind(along_wall(walls, "north", from = 3.4, length = 2.1, depth = 0.9, id = "Sofa", type = "sofa"), furniture("Floor lamp", "box", x = 1.95, y = 3.85, width = 0.3, depth = 0.3, height = 1.7)) trajectory_obstructions(trajs, furn, back = 6) ``` ## 7. Segmented paths through intermediate targets A bullet that passes through a table, a door or a wardrobe follows two or more straight segments. Each segment is measured on its own; the path orders them from the shooter's side to the terminal defect and records the joints (exit points). ```{r path} seg_in <- trajectory_from_angles(anchor = c(4.0, 2.6, 0.5), azimuth_deg = 69, vertical_deg = -30, id = "D4 into table") seg_out <- trajectory_2pt(p1 = c(4.25, 3.1, 0.45), p2 = c(4.9, 5.0, 0.2), se_position = 0.01, id = "D4 to wall") shot4 <- trajectory_path(seg_in, seg_out, id = "Shot 4") shot4 path_deflections(shot4)[, c("joint", "deflection_deg", "lower", "upper", "miss_in", "miss_out", "joint_source")] ``` The deflection is the angle between the measured segments, with its interval. `miss_in` and `miss_out` are the distances from the joint to each segment's line: if either is larger than your measurement uncertainty allows, the documented segments do not actually meet at that joint. **Deflection is never predicted.** How much a bullet deviates when it passes through something depends on the bullet, its velocity, the material and the angle, and cannot be computed reliably from the scene. When the path after an object was not documented, `assumed_segment()` continues the incoming direction with a cone whose width *you* state. It is flagged `ASSUMED` in every table and plot, drawn dashed, and refused by `origin_zone()`. ```{r assumed} shot5 <- trajectory_path(seg_in, assumed_segment(seg_in, joint = c(4.25, 3.1, 0.45), length = 2.5, se_deflection = 10, id = "after table (assumed)"), id = "Shot 5") trajectory_table(shot5)[, c("id", "path", "segment", "assumed", "method")] plot_trajectories(list(shot4, shot5), view = "plan", walls = walls, furniture = furniture("Coffee table", "table", x = 3.9, y = 2.6, width = 1.0, depth = 0.5), back = 4) ``` For origin analysis and convergence, only the first segment of a path (the one on the shooter's side) is used. ## 8. In three dimensions ```{r fig3d, echo = FALSE, fig.cap = "The three trajectories of section 2 rendered to scale: rods with arrowheads at the defects, wireframe cones for the angular uncertainty, floor grid with numbered axes."} knitr::include_graphics("figures/shooting-dollhouse.png") ``` ```{r render, eval = FALSE} render_scene_3d(walls, NULL, trajs, file = "shooting.png", view = "dollhouse") ``` ## 9. What to write in the report - The **method** for each trajectory and the **uncertainty you assigned** to it, with the reason (validation study, manufacturer's specification, rule of thumb). - Origin distances as **intervals at stated muzzle heights**, together with `p_reachable`, and the assumptions behind the heights. - Convergence as **consistent with**, never as proof of, a single position. - Any **assumed** segment as an assumption, separately from the evidence. The report template does all of this by default; see `vignette("reports-and-3d")`.