{"name": "PlanX", "package_name": "planx", "version": "4.18.0", "experimental": false, "qgis_min": "3.28.0", "qgis_max": "4.99.0", "downloads": 340, "uploaded_by": "geo140195philo", "upload_datetime": "2026-09-20T09:41:01.927229", "changelog": "## [4.18.0] - 2026-09-20\r\n\r\nEvery tool that publishes an area, a length, a density or a rate now measures it on the ground rather than in the layer's own coordinates. Thirteen algorithms and one engine module changed. No tool was added, removed or renamed, and no input or output schema moved.\r\n\r\n### Fixed\r\n- **Thirteen algorithms were publishing the projection's own coordinate units under a metre's name.** `QgsGeometry.area()` and `.length()` return the layer's coordinate units, and two independent errors hide in that. The unit may not be the metre at all: a California state-plane layer (EPSG:2229) is in US survey feet, so a 100-unit square is 929.041 m2 of ground while the layer reports 10000 - 10.76x, the square of the 0.30480060960122 factor. And a conformal projection carries its own scale factor: EPSG:3857 is a metre CRS, but they are metres on the sphere at the equator, so at 41 N a 20-unit box is 227.835 m2 of ground against 400 square units in the layer - 1.757x, `1/cos(41 degrees)` squared. Lengths err by the first power, so the same box is 1.325x in length and a foot layer's lengths are 3.28x. Nothing errors, no column is NULL and every feature count is right, which is why this class survived a green test suite for three releases while the manual and the changelog described it.\r\n- **Building Form Metrics** - `area_m2`, `perim_m` and `court_m2` are ground quantities, so `compact` and `convexity`, which are ratios of two of them, cancel the error and were left alone.\r\n- **Morphological Tessellation** - `cell_m2` is the cell's ground area, while the cell's own layout stays in map units.\r\n- **Spacematrix** - `fp_m2` and `gfa_m2` are ground areas; `gsi`, `fsi`, `osr` and `levels` are shares of the block built from them, so the conversion cancels in each and is asserted to.\r\n- **Dasymetric Density Grid** - `dens_ha` is features per ground hectare: the cell is a map-unit square and the feature count is gated by it, so only the denominator moves.\r\n- **Frontal Area Index** - `lambda_f` is a frontal area over a cell area, both of them ground, so a metre of ground holds 1.325x as many of them; `lambda_p` is a packing density and is conserved.\r\n- **Green Space Access** - the park-hierarchy minimum sizes are hectares, which are a planning standard and not a coordinate-unit number, so a patch has to be measured on the ground before it is compared with one. On EPSG:3857 at 41 N the unconverted reading let patches 43 percent under the standard qualify.\r\n- **Urban Green Connectivity** - the patch `area_m2` and the connectivity index built on it.\r\n- **Land-Use Balance** - `area_m2` and the `m2_capita` derived from it. `balance_m2` is a ground area less a per-capita requirement that is not an area of the city, so it moves without a single factor and is not checked as one.\r\n- **Residential Capacity** - each parcel's `buildable_m2` and the district roll-up.\r\n- **Service Areas** - the isochrone lengths and the pedshed areas.\r\n- **Walkability Audit** - `int_km2` is a junction count per ground km2, and `blk_len` and `slope_pct` are ground metres. The audit radius stays in map units, because it is also what gates the junction and destination counts around each segment.\r\n- **Walking Slope Comfort** - `slope_pct` and `max_pct` are the same rise over a run 1.325x shorter, so the gradient steepens. `climb_m` and `descent_m` must *not* move: an elevation is a raster value and no projection rescales it, so dividing them by the horizontal scale would be the horizontal answer to a vertical question.\r\n- **Road Noise Screening** - the acoustics were spread over a distance the sound never travelled. The 25 m reference of the RLS-90 source term, the 20 lg r spreading and the floor on r are all physical distances, and at 41 N the unconverted version used a distance 1.325x the real one. The grid, the cell size and the source cutoff stay in the layer's own coordinates, which is what their labels say.\r\n\r\n### Added\r\n- **A check that can fail, for the whole family.** A per-row re-derivation - measure each output row's own geometry and compare - reaches exactly one shape of this defect: a tool that publishes its own feature's area. Most of these tools publish a sum of intersections, a network cost, a decibel level or a maximum, and for those there is nothing to compare row by row. So every registered tool is now run a second time on the same city rigidly translated to 29 E 41 N, where a map unit is 0.7547 of a ground metre and nothing else about the run changes. Each column carries the movement its units predict - a share or a ratio is unchanged, a ground area moves by cos squared, a length by cos, a per-ground-area count or a per-ground-metre rate by the inverse - and the five factors (0.57, 0.755, 1.0, 1.33, 1.76) are far enough apart that a misclassification announces itself. A column still in the layer's own coordinate units reads identically in both runs, which is what makes the comparison falsifiable rather than decorative. Columns are compared as the sum of absolute values over the column, not row by row, so row order and the `round(x, 1)` most of these tools format with cannot affect the verdict.\r\n- **A `vertical` column kind**, for a quantity that comes off a raster and must not scale with a projection at all.\r\n\r\n### Changed\r\n- **`planx:preparenetwork` was deliberately left alone.** Its `length_m` is the one column in this family that is documented as being in the source CRS, and the whole network family reads its costs in that same unit, so converting it in isolation would silently rescale every break, isochrone and cost downstream. The honest fix is a rename plus a documented unit switch, which is a separate breaking change and is not in this release.\r\n- **The map-unit-by-design boundary is now written down.** A parameter whose label says map units - a walking radius, a grid cell size, a noise cutoff, an extent, a tessellation cell - stays in map units, and a quantity compared against a published planning threshold is measured on the ground. Where the two meet in one tool, both are named in the source beside the conversion.\r\n- **Six column groups are deliberately *not* checked**, each named with the measurement that showed why, so a later reader does not helpfully re-add one: a truncated count, a difference of two quantities that do not scale together, a non-linear function of a moving quantity, a count of features in a spatial partition (the partition moves), a maximum over samples whose spacing moves, and a column that is identically zero in both runs - two zeros agreeing with each other are a green line no defect could turn red.\r\n\r\n## [4.17.0] - 2026-09-20\r\n\r\nCoseismic Landslide Screening (`planx:coseismiclandslide`) is new. One algorithm added, none removed. It closes the ground-failure triangle: ground motion says how hard the ground shakes, liquefaction asks whether saturated soil loses its strength, and neither asks whether the hillside itself moves. An earthquake triggers landslides on slopes that were stable before it, and in a hilly city they are a large share of the physical damage. The answer is a distance in centimetres - the only quantity in this group that is a length rather than a probability or a volume.\r\n\r\n### Added\r\n- **`planx:coseismiclandslide` - Coseismic Landslide Screening.** Newmark's sliding-block calculation in the disposable-parameter form of Jibson (2007) Equation 8. A block of ground on a slope stays put until the shaking exceeds the acceleration that would just hold it there, and how far it then travels is the displacement. The method needs no constitutive model of the soil and no pore-pressure time history, which is why four decades on it is still what regional coseismic-landslide screening is built on.\r\n- **The critical acceleration is the whole problem, and a city almost never holds it.** It is a shear-strength property of the ground, so this tool offers three routes and makes the user choose rather than picking one: a **field** of critical accelerations the user already has, which is the best route whenever it is available because it is a measurement; a **geologic group** plus groundwater state through the Hazus chain; or somebody else's **susceptibility category**. Every row records which route it took in `ac_src`, because the same 0.25 g is a measurement, a table lookup off a geological map, and a table lookup off somebody else's susceptibility map, and the three say different things about the ground.\r\n- **No route has a default, and leaving one unset stops the run.** The groundwater state on the Hazus route has no default either, and it is not a detail: dry and wet are up to four susceptibility categories apart at one slope and group - a factor of eight in the acceleration the displacement is computed from - and a third of Table 4-14's cells change between them.\r\n- **The DEM is required on every route, including the ones that do not use the slope to choose the acceleration.** The slope angle is part of reading the answer whichever route supplied it, and a feature whose sample point falls off the DEM or on a nodata cell stops the run rather than being skipped - a missing terrain input is not a missing hazard. A geographic CRS is refused for the same reason the other terrain tools refuse one: its pixels are degrees.\r\n- **Below Table 4-15's slope bound, Hazus establishes no susceptible deposit, and the tool reports an EMPTY critical acceleration rather than a zero.** The distinction is load-bearing. Zero is the value Equation 8 diverges on, so a zeroed row would be the model's loudest answer arriving disguised as its safest; the two are indistinguishable in the displacement column, which reads 0.0 for both. The run log counts the \"never evaluated\" rows separately and `ls_notes` carries the reason on each one.\r\n- **A zero critical acceleration is refused, not substituted.** The `(ac/amax)^-1.478` term diverges as the ratio goes to zero, because a material with no strength to mobilise is not a slope with a displacement, it is a flow - and a flow is the liquefaction question, not this one. The refusal is in the engine, in the run, and asserted by a test that names what diverges.\r\n- **The two non-sliding cases are reported as what they are rather than clipped into silence.** Where `ac >= amax` the block genuinely does not move: the displacement is 0.0, the log displacement is empty, and the row notes say so. Where the ratio falls below 0.05 the equation is extrapolating and the row says that instead - the value is reported as the equation gives it, not floored to a \"plausible\" ceiling.\r\n- **Table 4-15's acceleration bound is applied as a floor on the Table 4-16 category value, and it changes exactly one cell of the whole table.** Three groups by two moisture states by six slope bands is 36 cells, and the floor overrides exactly one of them: group B, wet, above 40 degrees, where the category is X at 0.05 g and the bound is 0.10 g. `ac_table_floor_conflicts()` asserts which cell that is instead of assuming it, so a future edit to either transcribed table cannot move it silently, and every row the floor touches says so in its notes.\r\n- **Table 4-17's area fraction is reported in its own column and never multiplied into the displacement.** The displacement is a property of the sliding block and the fraction is a property of the map unit; folding them together would produce a number that is neither.\r\n- **The susceptibility-category route accepts Roman numerals only.** A numeric column cannot say whether 1 means the least susceptible, as Hazus counts it, or the most, and guessing that wrong reverses the map while leaving every count and every schema check green - the failure mode a value check cannot see. Digits are refused with the list of the values found, which is what a user needs in order to fix the column.\r\n- **One output schema for all three routes**, with the columns a route does not compute left empty rather than absent, so a saved Processing model that reads a column keeps working when the route changes - and a model that reads a column which is empty for this route can see that it is empty, which a missing column could never tell it. Twelve computed columns: the median displacement and its 90th percentile, the log displacement, the acceleration and its route, the ratio the equation actually turns on, the Hazus category and area fraction, and the shaking, magnitude and slope the row was evaluated at - so any row can be re-derived from the file it is in. `RENDER_FIELD` is set to `nw_disp_cm` explicitly rather than left to the renderer's preference list, which contains no token any of these columns carries: left to itself the tool would write a correct table with no colour scale at all.\r\n\r\n### Changed\r\n- `provider.py` registers the tool last in Seismic Risk (19), after the four existing tools, and the manual's group-19 card order matches.\r\n- All three gate floors moved from 74 to 75 (`smoke_provider_catalog.py`, `qgis_runtime_algorithm_matrix.py`, `tests/smoke_plugin.py`).\r\n- `metadata.txt` is 4.17.0, with a new changelog block, the coseismic-landslide clause added to the description's seismic parenthetical, and the about text describing five chained seismic tools. No literal percent sign.\r\n- The manual gained a nine-section card for the new tool - overview, theoretical background, eight numbered equations, input requirements, 11 parameters, outputs, symbolic representation, a six-part interpretation guide and four references - plus its table-of-contents entry, and the Seismic Risk group preface now says five tools and describes where the new one sits. Counts are read off the built manual rather than estimated: 336 to 344 numbered display equations, 398 to 402 reference entries, 305 to 307 with DOIs, and 74 to 75 algorithms in each of the four places the figure appears in the manual and again the four places it appears in the README. The manual's own footer, title and introduction carry the version, so all three moved to 4.17.0 with the algorithm count - the gate that scans for that is exact, and a manual one release behind is the failure it was written to catch.\r\n- `docs/METHODS.md` gained the coseismic-landslide paragraph - Jibson Equation 8 with its dispersion and the two non-sliding cases, the three routes to the critical acceleration, the Hazus Tables 4-14 to 4-17 chain and its one floor conflict, and the D8 slope with the pixel left in the DEM's own units - and its closing caveat now says that the landslide displacement is a United States calibration with no Turkish landslide inventory behind it.\r\n- `THIRD_PARTY_NOTICES.md` gained three sections: the Jibson (2007) coefficients with the open-access source they were transcribed from, the Hazus Section 4.2.2.2 tables, and the USGS Open-File Report 98-113 Arias-intensity model that is deliberately **not** implemented.\r\n\r\n### Notes\r\n- **The paper was not read, and the coefficients say so.** Jibson (2007) is paywalled; its Equation 8 coefficients were transcribed from Yi\u011fit (2026), *Pamukkale \u00dcniversitesi M\u00fchendislik Bilimleri Dergisi* 32(1), 191-199, an open-access article that reproduces the published Newmark regressions beside its own refits of the same data. That side-by-side is what makes it usable as a transcription rather than only as a citation: its own fit of the same data prints a different exponent (1.3593 against 2.335), and its Equation 9 reproduces the Arias-intensity form digit-for-digit including the 0.616 dispersion. The provenance record in `engine/provenance.py` names the transcription source and the fact that the original was not read.\r\n- **Jibson Equation 9 and the Arias-intensity regression of Jibson, Harp & Michael (1998) are deliberately not implemented, and the manual says why.** Both are Arias forms \u2014 Equation 9 is `0.561 log10 I_a - 3.8331 log10(ac/amax) - 1.474` \u2014 and Arias intensity is the total energy the shaking deposits, which Ground Motion Scenario does not produce and no other tool in this plugin does. Deriving one would mean a ground-motion model of its own. Equation 8 is the only regression in the paper that takes exactly the two quantities the seismic chain already produces, the critical-acceleration ratio and the magnitude, with nothing to import from outside it. The USGS Open-File Report 98-113 is listed in the manual's references as the model most often misremembered as the one this tool uses.\r\n- **The runtime matrix grew a check that re-derives its rows instead of snapshotting them.** `_expect_coseismic_rows` puts every output row back on the synthetic surface's own closed form - the slope is recomputed from the terrain's analytic gradient at the row's own sample x - then re-runs the Hazus chain and the displacement from the tool's parameters and compares. It tests the tool's wiring, not the engine's constants, which is the right division: mutating a constant would move the run and the check together and stay green, and that is `test_engine.py`'s job. So the check was falsified with wiring mutations, and both were caught. Scaling the slope by 57 instead of using the right conversion failed with `row 1: slope_deg is 6.301205433 where the synthetic surface gives 6.333903108`; writing the wrong route prefix into `ac_src` failed with `row 1: ac_src is 'hazus-cat:V', which is not one of the routes this run takes (field)`. Both were reverted, and the file was restored to green.\r\n- **A check that only ever passes is a check nobody has tested, so the new one carries its own guards against going quiet.** It reads the grid the terrain was sampled on from a module-level record filled in by the fixture builder rather than inferring it from an output row, and complains out loud if that record is empty - because the slope angle alone cannot be inverted back to a cell, and a slope check that quietly stops checking is exactly the failure the fixture was rebuilt to close. It also asserts that the run actually touched every route it was asked for, all ten Table 4-16 categories on the category route, all six slope bands on the group route, and that the sampled slopes span at least 15 degrees, so a future fixture edit that stops reaching a branch fails instead of narrowing coverage in silence.\r\n- **Two defects in the test harness itself were found by this work and fixed.** Every fixture raster had been written at `GDT_Float32` while the closed form is float64, so the synthetic surface carried about 1e-7 of relative error - roughly 5e-7 degrees of slope - which is enough to make a 1e-9 tolerance meaningless. The terrain is now written in double precision, and the tolerance stays where it was rather than being loosened to accommodate the fixture. Separately, an extra run's failure message was overwriting the primary run's: the case loop assigned `result.error` unconditionally, so when both failed the first and more fundamental complaint was lost. The two are now joined in the order they were found.\r\n- **The engine suite stands at 731 checks**, up from 702, including the ratio exponents against the values the open-access source prints, the 0.424 magnitude term and the 0.454 dispersion, the divergence refusal at a zero critical acceleration, the `ac >= amax` case returning a genuine zero with empty log displacement, the one-cell Table 4-15 floor conflict, and the category matcher accepting \"very high\" and refusing \"1\".\r\n- Push, tag and the plugins.qgis.org upload remain manual, as for every release. The version is committed and tagged locally; it is not published.\r\n\r\n## [4.16.0] - 2026-09-19\r\n\r\nLiquefaction Screening (`planx:liquefaction`) is new. One algorithm added, none removed. It is the one secondary ground-failure hazard that runs from the layers a city usually has, and it carries two published models behind one interface: a global regression from a DEM and a shaking level, and the Hazus susceptibility tables for map units somebody has already classified - which it refuses to run without.\r\n\r\n### Added\r\n- **`planx:liquefaction` - Liquefaction Screening.** The rest of the seismic chain assumes the ground stays where it is: ground motion says how hard it shakes, debris and casualties say what that does to buildings. Neither asks whether the ground itself fails. Saturated sand loses its strength under cyclic shaking, and the resulting hazard is on a different scale, from a different set of inputs, and - unlike collapse - not correlated with building age. The research note behind the tool is a negative result worth keeping: almost no published ground-failure method works from shaking alone, because they generally want a modelled water table, a rainfall climatology or a susceptibility surface. Zhu et al. (2015) is the exception, and its two extra inputs are both derivable from a DEM.\r\n- **Regression mode - Zhu, Daley, Baise, Thompson, Wald & Knudsen (2015)**, *Earthquake Spectra* 31(3), 1813-1837, Table 3, the global model the literature calls GGM1. A logistic regression on a magnitude-weighted shaking level, a topographic wetness index and slope-derived Vs30. It needs a DEM and a PGA and nothing else: the wetness index and the slope come out of one D8 pass on the DEM, and the velocity comes from the slope through a published table - or from a Vs30 field or raster, which wins over everything when it is there.\r\n- **The magnitude term is inside the logarithm, and the test suite says so.** The published expression is the log of PGA times a power of magnitude, not a plain log of PGA with magnitude multiplied in beside it. The correct form scales magnitude by 2.067 x 2.56 = 5.29; the form that writes the power as its own additive term scales it by 2.56. Both produce a smooth, monotone, plausible surface, so the identity is asserted rather than trusted.\r\n- **That power of magnitude turns out to have a name.** `Mw^2.56 / 10^2.24` is the reciprocal of the magnitude scaling factor published by Youd et al. (2001) in the NCEER/NSF workshop summary - their Equation 24, `MSF = 10^2.24 / Mw^2.56` - so the quantity the model takes the logarithm of is the *magnitude-weighted* PGA, and the exponent is a citation rather than a bare constant. At Mw 7.5 the factor is exactly 1.0, which is why the model reads as plain \"PGA\" in the middle of its own range and as something else entirely at Mw 9.\r\n- **The percent-g division is not applied, and the clip ceiling is reported in the parameter's own unit.** The published term divides by 100 because the ShakeMap grid it was fitted on is in percent g; a field already in g must not be divided again, and applying it would move the logit by `2.067 x ln(100) = 9.52` - enough to turn a 2 percent probability into 0.9999. The source's clip ceiling of 270 is reported and applied as 2.7 g.\r\n- **A coefficient the literature prints wrong by a factor of ten.** Independent reproductions print the site-velocity coefficient as 0.4784; the USGS `gfail` implementation carries 4.784. This tool uses 4.784, and the reason is arithmetic: `ln(Vs30)` spans about 5.2 to 6.6 over real sites, so 0.4784 removes at most 0.7 logit units of site term where 4.784 removes up to 6.7. With 0.4784 the intercept of 24.10 dominates every other term and the logistic returns a probability of essentially 1 everywhere - a model that cannot rank anything. The coefficient is one named constant, the sensitivity it implies is pinned by a test, and the number is visible in the engine.\r\n- **The wetness index, and the unit error a logarithm hides.** `CTI = ln(a / tan \u03b2)` with `a` the D8 accumulation times the pixel size, which must be in metres: the index is the log of a metric *area*, so a DEM in feet inflates every cell by `ln(3.2808) = 1.188` - a uniform offset the CTI coefficient turns into 0.42 logit units of pure unit error. No range check can see it, because 1.19 is an ordinary index value, and no map looks wrong, because the offset is constant. The DEM's own linear unit is therefore read and converted explicitly, and a DEM whose CRS declares no linear unit at all is refused. The slope argument of the same model is safe by contrast, because a gradient is a ratio of two lengths in one unit - that asymmetry is the whole trap. The same class as 4.15.1's raw-geometry areas, fixed here before it shipped rather than after.\r\n- **A flat or ponded cell is not a missing cell.** It has no downslope neighbour, so its index is genuinely infinite - the wettest possible answer, which the model's own ceiling of 15 evaluates. Those rows are counted and reported separately from a cell that is off the grid or on nodata, which stops the run: a missing terrain input is not a missing hazard.\r\n- **Site velocity comes from a table, because the report publishes a table.** A common misconception about USGS Open-File Report 2007-1357 is corrected in the engine: there is no two-branch `log(Vs30) = a + b log(slope)` regression in it. What is published is slope ranges mapped to NEHRP classes, with the method statement that a slope inside a window takes a velocity interpolated between the subdivided class boundaries - so the bin edges transcribe as the nodes of a piecewise-linear map. Outside the published nodes the value is held flat at the table's own endpoint and the run reports how many rows that affected, rather than extrapolating a curve nobody published.\r\n- **The tectonic setting has no default, because the two columns are not close.** At one slope the active-margin and stable-continent columns differ by up to a factor of 1.96 in velocity, which the site coefficient turns into about 3.2 logit units. The regression mode refuses to run until the setting is chosen, and the log says which column was used and whether it changed any row.\r\n- **Susceptibility mode - Hazus 6.1 Earthquake Model Technical Manual, Section 4.2.2.1**, Equations 4-9 to 4-11 and Tables 4-10 to 4-13, transcribed from the manual. It reads a polygon layer of map units somebody has already classified Very High, High, Moderate, Low, Very Low or None, and gives each unit the probability and the expected settlement in inches. Both correction polynomials are applied exactly as printed, reference-value residual and all: K_M at Mw 7.5 comes out 1.0147 and K_W at the 5 ft reference depth 1.04 rather than 1.0. Renormalising them would move every number in the tool about 5 percent away from the published method to fix a cosmetic inconsistency in the source, and Hazus's own figure plots a marker at Mw 7.5 that reads 1.01. The sixth row of Table 4-11 is not a line at all - the \"None\" category is a constant zero - and it is kept in the same table as a degenerate line so that every category reaches the same code path instead of a key that a user-supplied category could fall through.\r\n- **The susceptibility mode refuses to run without a classified layer, and refuses a category it does not recognise**, listing the values it found. Hazus itself assumes no ground failure when it has no input, which makes an empty map of nowhere-liquefies the most dangerous answer this family of tools can give: an unrecognised category read as zero would report \"no liquefaction hazard\" for a unit nobody ever classified. Case and separators are ignored, so \"very high\" and \"Very-High\" are accepted; \"VH\" and \"1\" are not.\r\n- **One output schema for both modes**, with the columns a mode does not compute left empty rather than absent, so a saved Processing model that reads `liq_prob` keeps working when the combo box moves - and a model that reads a column which is empty for this mode can see that it is empty, which a missing column could never tell it. Fifteen columns: the probability and its 0.81 area reading kept strictly separate, the Hazus terms, the inputs the logit actually used (`pga_g`, `mw`, `cti`, `slope_deg`, `vs30_ms`, `vs30_src`) so a run is auditable from the layer alone, and `liq_notes` carrying the per-row remarks - a clipped CTI, a clipped PGA, a velocity held at a table endpoint, a footing on the wettest possible cell. `RENDER_FIELD` is set to `liq_prob` explicitly rather than left to the renderer's preference list, because the next column added to this table is one rename away from silently taking the colour scale off the probability.\r\n- **Shaking with no field is a refusal, not a substitute.** A blank or zero PGA row stops the run: the model takes `ln(PGA)`, and unlike the velocity there is no reference shaking level to fall back to, so substituting one would move the answer by orders of magnitude. **Ground Motion Scenario** writes the column this tool wants.\r\n\r\n### Changed\r\n- **`engine/hydro.py` gained the two terrain quantities this tool needed, and the D8 pass they share.** `slope_radians(dem, pixel)` and `cti(dem, pixel)` are new; the steepest-descent walk behind them was factored out into `d8_steepest`, which returns the direction grid and the gradient together, so the flow direction, the slope and the wetness index cannot drift apart. The gradient is what makes the pixel-size argument unit-aware at one point instead of three.\r\n- **The geographic-CRS refusal in `algorithms/_raster.py` now names the layer and the reason.** It said the DSM needs metres \"for shadow/SVF math\"; the same reader now feeds a wetness index, whose pixel size is a *length* rather than the dimensionless gradient the DSM tools use, so the message states the CRS and the layer instead of one tool family's use case.\r\n- `provider.py` registers the tool, in Seismic Risk (19), between Ground Motion Scenario and Seismic Collapse and Debris Spread.\r\n- Both gate floors moved from 73 to 74 (`smoke_provider_catalog.py`, `qgis_runtime_algorithm_matrix.py`), and `tests/smoke_plugin.py`'s hard algorithm-count assertion with them.\r\n- `metadata.txt` is 4.16.0, with a new changelog block, the liquefaction clause added to the description's seismic parenthetical, and the about text describing four chained seismic tools. No literal percent sign.\r\n- The manual gained a nine-section card for the new tool - overview, theoretical background, thirteen numbered equations, input requirements, 13 parameters, outputs, symbolic representation, a four-part interpretation guide and seven references - plus its table-of-contents entry, and the Seismic Risk group preface now says four tools and describes where the new one sits. Counts are read off the built manual rather than estimated: 323 to 336 numbered display equations, 391 to 398 reference entries, 300 to 305 with DOIs, and 73 to 74 algorithms in each of the four places the figure appears in the manual and again the four places it appears in the README. The manual's own footer, title and introduction carry the version, so all three moved to 4.16.0 with the algorithm count - the gate that scans for that is exact, and a manual one release behind is the failure it was written to catch.\r\n- `docs/METHODS.md` gained the two seismic paragraphs this tool needs - the Zhu regression with the exponent trap, the percent-g division, the metric-CTI trap, the Vs30 table and its tectonic divergence, and the Hazus Equations 4-9 to 4-11 with their tables - and its closing caveat now says that the liquefaction regression is a United States calibration with no Turkish liquefaction inventory behind it.\r\n- `THIRD_PARTY_NOTICES.md` gained three sections: the Hazus Section 4.2.2.1 tables, the Zhu et al. (2015) coefficients with their `gfail` provenance and the 0.4784/4.784 discrepancy recorded next to them, and the USGS slope-to-Vs30 table including the Youd et al. (2001) identification of the magnitude term.\r\n- `docs/icons/` gained the four tool icons the manual references and it did not carry - `tool_groundmotion.png`, `tool_liquefaction.png`, `tool_parkingbalance.png` and `tool_seismicimpact.png`. Cards added in 4.14.0 and 4.15.0 were rendering with a broken image beside them.\r\n- `docs/TRAPS.md` (monorepo) gained **5.9**, a logarithm turns a unit error into an additive constant and no range check can see it, with the 1.188/0.42 arithmetic, the contrast against 5.8's area multiplier, and the analytic-plane test in both unit systems.\r\n\r\n### Notes\r\n- **Three citations corrected, and one of them was a paper that does not exist.** The model had been attributed to \"Zhu, Baise & Thompson (2017), BSSA 107(1), Table 3\" in the tool's help, its metadata and the methods file. Verified against Crossref: the 2017 follow-up is *BSSA* 107(3), 1365-1385, and the coefficient set belongs to the 2015 *Earthquake Spectra* paper. Worse, the engine's provenance note credited the coefficient cross-check to \"Baird, Geyin & Maurer (2018)\" - no such paper exists; a Crossref author search turns up only **Geyin, Baird & Maurer (2020)**, *Earthquake Spectra* 36(3), 1386-1411, whose Table 3 carries the reproduction in question. A year and an author order written from memory became a citation to nothing, which is why the rule is to read them rather than recall them.\r\n- **The same reproduction supplied the honest performance figure.** That 2020 assessment compares 23 liquefaction models against 15,223 case histories from 24 earthquakes and reports a global median area-under-curve of **0.54** (95 percent confidence interval 0.47 to 0.61) for GGM1 - barely better than random guessing at global scale, with considerably better performance on regional datasets. That number is in the manual's limitations, with the caveat that the assessment prints the 0.4784 coefficient, so its figure may describe a saturated version of the model rather than the one implemented here. Either way the conclusion is the same and is stated wherever the output is: **this ranks places within a city, it does not predict a calibrated probability.**\r\n- **The runtime matrix gained a capability it did not have: per-extra-run value expectations.** Until this release an extra run was judged only by whether it produced populated outputs, so a branch writing the right number of rows with the wrong numbers passed every check. `EXTRA_VALUE_EXPECTATIONS` and `check_extra_values` now let an extra run assert what its numbers must be, and the liquefaction susceptibility case uses it to re-derive K_W, K_M, the clipped conditional, the probability and the settlement from the engine's own published tables and compare them row by row against the written GeoPackage.\r\n- **Falsified before trusted.** Perturbing the shared constant would have moved the run and the check together and stayed green, so the expectation alone was perturbed - `hazus_groundwater_factor(1.524 + 0.1)` - and the case failed with the exact message intended: `row 1: K_W is 1.040000 where 1.524 m gives 1.047218 - Equation 4-11 takes feet`. Restoring the file returned it to green. A check that has never failed is a check that has never been tested.\r\n- **The susceptibility extra run needed a fixture that speaks the model's vocabulary, and building one was the right answer rather than weakening the check.** It first refused with `The category column holds 4 value(s) this model does not define: 'commercial' x8, 'green' x5, 'residential' x10, 'school' x2` - the demo city's `category` column holds land-use words, and the tool refuses them on purpose. A new `hazus_units` fixture carries a `liq_category` column cycling the six names the tables are written for, which is the only way one run can touch all six branches. The fixture's spread is itself asserted, so a future edit that stops reaching every category fails rather than quietly narrowing coverage.\r\n- **These two modes are not comparable, and the manual says so in its own section.** The regression asks whether a place is liquefaction-prone given its terrain and shaking; the susceptibility mode asks what the chance is that a map unit somebody already classified lapses. Different evidence, different footing - and the susceptibility run is only ever as good as the classification it was handed. Putting the two on one map and comparing the values ranks the two datasets, not the two places.\r\n- **The engine suite stands at 702 checks**, including the exponent identity (a sensitivity of 5.29152 against the 2.56 the wrong form gives), the percent-g trap, the analytic CTI on a plane and the same plane in feet differing by exactly `ln(3.28084)`, the Vs30 piecewise interpolation and its clamping flag, Table 4-11's zero crossings reproducing Table 4-12's thresholds, and the category matcher accepting \"very high\" and refusing \"VH\".\r\n- Push, tag and the plugins.qgis.org upload remain manual, as for every release. The version is committed and tagged locally; it is not published.\r\n\r\n## [4.15.1] - 2026-09-19\r\n\r\nThree tools reported areas and lengths in the layer's own coordinate units while documenting them as metres. Fixed here, with both error classes named, so the next one is checked rather than found. No algorithm added or removed, no parameter changed, no output column renamed - the values that move are the ones that were wrong.\r\n\r\n### Fixed\r\n- **`planx:seismicdebris` now reports ground metres.** The tool fed `QgsGeometry.area()` - the layer's own square units - straight into `seismic.debris_extent`, which documents its outputs as cubic metres and tonnes, and into `footprint_area`, which documents square metres. Two different things break that assumption, and they are not the same defect:\r\n  - **The coordinate unit is not always the metre.** On EPSG:2229 - California zone 5, US survey feet - a square 100 units on a side is 929.041 m\u00b2 of ground. The layer's own `area()` says 10000. A 10.76x figure, the square of the 3.28 foot factor.\r\n  - **A conformal projection carries its own scale factor.** EPSG:3857 is a metre CRS, so its units look like metres, but they are metres on the sphere *at the equator*. At 41\u00b0N a 20-unit box is 227.835 m\u00b2 of ground against 400 square units in the layer - 1.757x, `1/cos(41\u00b0)` squared. This is the 1.76x recorded as an open defect in 4.15.0, still in a metre CRS.\r\n  Both scale a plausible number by a constant, which is why neither looks like an error in a result table.\r\n- **`debris_vol_m3`, `debris_pile_m3` and `debris_mass_t` move with it**, being built from that area. The **radius does not**: it is driven by height alone, as the 4.15.0 note said. On the demo city - which sits at the EPSG:3857 origin, the one place on that grid where the scale is exactly 1 - every number is unchanged, which is also why the runtime matrix never saw this.\r\n- **Street corridor widths are now metres too.** The debris envelopes buffered by half the debris width in layer units: on a foot layer a 5 m road was cleared by a 5-foot envelope. Same conversion for the reach along the street axis, the ROI hull and the morphological opening behind `OUT_NAVIGABLE`, where `MIN_CLEAR_WIDTH` is a metre threshold by definition.\r\n- **`planx:streetmorphology` - the defect carried from 4.14.0, fixed in the same release.** `total_length_km`, `avg_segment_length_m` and `intersection_density_km2` divided raw network units by 1000 and raw hull units by 1e6. On a foot layer those figures were wrong by 3.28 and 10.8; on EPSG:3857 at 41\u00b0N they were wrong by 1.33 in length and 1.76 in area, on a metre layer that gave no sign of it.\r\n- **`planx:seismicimpact` had the same defect in the same release it was written.** The occupant estimate reads the footprint from the geometry when the layer has no footprint column, which is exactly the fallback path, and that read was raw. The casualties and the shelter need inherited the area error the debris tool had just been fixed for.\r\n- **New module `planx/algorithms/_units.py`.** `GroundUnits` binds a `QgsDistanceArea` to the layer's CRS and ellipsoid and exposes `area()` (m\u00b2), `length()` (m), `per_metre()` (layer units per ground metre, measured along the geometry where it has length) and `scale()` (layer units per ground metre at the centre of an extent). One mechanism covers both defects, because both are the same question: how long is a layer unit on the ground here. A CRS with no ellipsoid and a geographic CRS both raise rather than guess.\r\n\r\n### Notes\r\n- **The runtime matrix is structurally blind to this class, and still is.** Its fixture city is generated at `(0, 0)` in EPSG:3857 (`engine/demo.py`), where the projection's scale factor is exactly 1 - so 73 cases ran green over a defect that any real city would have shown. `tests/smoke_plugin.py` now carries the check the matrix cannot: a 20-unit box at 41\u00b0N on EPSG:3857 against `(20*cos 41\u00b0)\u00b2`, a 100-foot square at Los Angeles on EPSG:2229 against `(100*0.30480060960122)\u00b2` with its `per_metre` reciprocal, and a 1000 m square on EPSG:32635 that must stay near 1e6 rather than be corrected away. Each expectation is written out from the CRS definition, not read back from the module under test.\r\n- **The same class is still live in about a dozen other tools** that write an `area_m2` or a `km2` column from raw geometry - `building_metrics`, `green_connectivity`, `landuse_balance`, `residential_capacity`, `spacematrix`, `tessellation`, `density_grid`, `growth_sim`, `landcover_change`, `sprawl_metrics`, `walkability` among them. They are not touched here: it is a wide pass that changes reported values in most of the plugin's analytical tools, and it deserves its own release rather than a footnote to this one.\r\n- **A value check must not read its own output through QGIS, and the matrix now says so.** The new debris check first read `OUT_BUILDINGS` with a `QgsVectorLayer` and the case went red on both runtimes with `Creation of data source failed (OGR error: A file system object called '...gpkg' already exists.)` - because the matrix's extra runs re-create a destination the primary run already wrote, and on Windows an open QGIS datasource keeps the file from being replaced. Measured: with no prior read the re-create and the delete both succeed; after one read through QGIS both fail with `winerror 32` whether the layer wrapper is kept, `del`-ed, or `del`-ed plus `gc.collect()`. All three value checks now read their output with `sqlite3` (`_read_gpkg`), which holds no GDAL handle and reads the columns exactly as any other program would. The trap was latent in the two checks that already existed; it needed only an extra run on their output to fire.\r\n- **New value check for the debris identity.** `debris_vol_m3` must equal `footprint_area \u00d7 height_m \u00d7 SOLID_VOLUME_RATIO \u00d7 f(damage_state)` for every building, feature by feature - the identity the footprint column exists to make checkable, and the one that breaks first if the reported area and the area the volume was built from ever stop being the same number.\r\n- **`tests/smoke_plugin.py` was hanging, and no longer is - and the cause is not the one the first reading suggested.** Run by hand it printed its verdict and then never returned; run under `pf verify` it finished normally. The difference is the QGIS profile, not the test: both runs hand `QgsApplication` a throwaway profile of their own, but only `pf verify` also sets `QGIS_CUSTOM_CONFIG_PATH` (TRAPS 4.6). Measured on 3.44 - the module as it stood printed `PASS` and exited 0 with that variable set, and printed the same `PASS` and then hung until killed without it - so the block is inside QGIS's own shutdown path, in the real user profile, and a profile folder passed to the constructor does not avoid it. It now exits through `os._exit` from the frame that still holds the application, as `qgis_runtime_algorithm_matrix.py` already did, which short-circuits teardown on either environment; the failure path exits 1 with its traceback and no hang, which is what `pf verify` reads. The teardown half of the hazard (`exitQgis` blocking once a provider is loaded) stays guarded the way the matrix guards it.\r\n\r\n## [4.15.0] - 2026-09-19\r\n\r\nSeismic Human Impact (`planx:seismicimpact`) is new. One algorithm added, one existing tool gained an output column, none removed. The Seismic Risk group now runs end to end: how hard the ground shakes, what that does to buildings and streets, and who is hurt when it does.\r\n\r\n### Added\r\n- **`planx:seismicimpact` - Seismic Human Impact (Casualties and Shelter).** The two existing seismic tools stop at physics. This one carries their result into the two Hazus models that end in people - Section 12 for casualties and Section 13 for displacement and shelter - and it reads the four damage-state probabilities (`prob_slight`, `prob_moderate`, `prob_extensive`, `prob_complete`) that Seismic Collapse and Debris Spread writes, so the chain runs without a file in between. Any layer carrying those four columns works too.\r\n- **Casualties from the whole damage distribution, not from collapse alone.** Hazus's event tree is implemented as the manual writes it: every damage state contributes its own four injury rates, and only the Complete state splits, into complete-but-standing and collapsed, by the Table 12-8 probability of collapse given Complete. That split carries almost all of the deaths - for a C1L frame the collapsed branch's severity-4 rate is 10 per occupant against 0.01 for the intact branch - so a model without it would report Complete damage as nearly harmless.\r\n- **Four severities, in Hazus's own order** - treatable injuries, injuries needing hospitalisation, life-threatening injuries, instant deaths (Table 12-1) - written out per building as `cas_sev1` to `cas_sev4` and summed into `cas_total`. People outside the building are counted separately on the manual's outdoor tables, which drop the slight-damage branch and do not split on collapse: falling parapets and glazing hurt people on the pavement whether or not the floors came down.\r\n- **Who is in the building is a parameter, not an assumption.** Hazus Table 12-2 gives the indoor and outdoor share of every occupancy class at 2 a.m., 2 p.m. and 5 p.m., and those shares decide the outcome: a residential building is 98.9 percent occupied at 2 a.m. and 52.5 percent at 2 p.m., a school is empty at 2 a.m., and a hotel is a fifth full at 2 p.m. A night earthquake and a day one are two runs of this tool, and the help recommends both.\r\n- **Occupants** come from a population field when the layer has one, or - when it does not - from footprint area times storeys divided by an area per occupant, 30 m\u00b2 by default. That divisor is stated as the tool's own screening assumption rather than a Hazus figure, in the parameter description and in the run log, together with which of the two sources produced the count. A dwelling-units field drives the Section 13 equations when the layer has one; without it a residential building counts as one dwelling and a non-residential building as none, which is what a non-residential occupancy is.\r\n- **The floor-area source reads the footprint from the geometry, or from a column when the buildings are points.** A point has no area, and the tool upstream of this one writes points, so the footprint is taken from a field when the geometry gives nothing - named by the new optional **Footprint area field** parameter, or found automatically in `footprint_area`, `area_m2`, `area`, `shape_area` so the debris-to-casualties chain runs with nothing to configure. With no footprint anywhere the run **stops** and says so: the floor-area estimate would be zero at every building, and a run reporting a city of nobody is exactly the answer this tool refuses to give quietly. When only some buildings lack one, it warns with the count instead.\r\n- **Shelter** is Hazus Equations 13-1 to 13-5 with Table 13-1's single-family and multi-family weights: `displaced_hh` is the households that lose their home, and `public_shelter` is the people who will seek publicly provided shelter. The demographic filter is implemented in full, with Table 13-2's category weights and four modifiers that default to 1.0. That default is deliberate and is said wherever the number appears: at neutral the filter changes nothing, \u03b1 is exactly 1.0, and the answer is an **upper bound** in which every displaced person seeks public shelter. Hazus's own Table 13-3 factors run from 0.13 to 0.62, so a real estimate sits well below the default.\r\n- **No damage columns, no answer.** If the four probability columns are absent and no damage-state column is named, the tool raises with the reason rather than returning zeros, because a zero casualty count is indistinguishable from a good outcome. A single damage-state column is accepted as a fallback and the run log says that it discards the distribution. Neither `displaced_hh` nor `public_shelter` is an error case for a non-residential building - both are zero, which is what the equations give.\r\n- **`planx:seismicdebris` now writes `footprint_area`** - the building's own `QgsGeometry.area()`, kept so the casualties tool can read a footprint back off its point output. It is not called `area_m2` because it is in the layer's area unit, not necessarily square metres. Placed next to `height_m`, which it pairs with; it carries no renderer token, so `collapse_prob` is still the field the default renderer colours by, and the field-order contract in that method's docstring and `tests/smoke_plugin.py` still holds.\r\n- Output is one feature per building with its geometry and every input attribute preserved, so the layer joins straight back onto the damage and debris outputs, plus `occ_class`, `bldg_type` (the resolved Hazus type), `occupants`, the four severity counts, `cas_total`, `displaced_hh` and `public_shelter`. `RENDER_FIELD` is set to `cas_total` so the default renderer colours by it rather than by whichever numeric field the generic candidate list happened to match last.\r\n\r\n### Changed\r\n- `metadata.txt` is 4.15.0, with the description and the about text now describing three chained seismic tools and a 4.15.0 changelog block. No literal percent sign.\r\n- The manual gained a nine-section card for the new tool - overview, theoretical background, ten numbered equations, input requirements, 22 parameters, outputs, symbolic representation, an eight-part interpretation guide and three references - plus its table-of-contents entry and a Seismic Risk group preface that now says three tools and describes what the new one closes. Counts are read off the built manual rather than estimated: 314 to 323 numbered display equations, 388 to 391 reference entries, 300 with DOIs, 72 to 73 algorithms in the four places the figure appears. The debris card's output row also gained `footprint_area` - and, in the same row, the four `prob_*` columns, which were added to that tool's schema for the casualty model in this release and had not been listed. A field list a reader takes as complete, and which is not, is a defect of a different kind from a wrong number - and it was the second one this pass turned up. The first was this file: 4.15.0 credited the new tool with 22 parameters when its manual table had 21 rows and `initAlgorithm` defined 21. The parameter added here makes the figure 22 and true, which is a coincidence, not a correction. Both counts were then re-derived by reading the parameter table and `initAlgorithm` against each other - 22 rows, 22 parameters, same order - rather than by trusting either figure.\r\n- The README's tool table had **no seismic rows at all** - nor the two parking tools - so it listed 68 of the 73 algorithms while claiming 72. It now carries a Seismic Risk group with all three tools and the two parking tools under Travel Demand, and has exactly one row per algorithm. Its algorithm count, its \"seventy-three tools\" line and its engine-check figure (627 to 670, which is what the suite actually runs) are current.\r\n- `docs/METHODS.md` gained a **Seismic Risk section, which it did not have**: the ground-motion functional form and site term, the fragility and debris equations, the casualty event tree and the shelter equations with their sources. The group was previously undocumented there even though the README points at that file for methods and sources. The two parking tools' methods were added to its Travel Demand section in the same pass.\r\n- Both gate floors moved from 72 to 73 (`smoke_provider_catalog.py`, `qgis_runtime_algorithm_matrix.py`).\r\n- `THIRD_PARTY_NOTICES.md` gained a Hazus casualty and shelter section alongside the existing fragility one, naming Sections 12 and 13, the figures and tables transcribed, and the two limits that are not FEMA's: the rates are applied to Turkish stock without calibration, and the neutral shelter modifiers are not Table 13-3.\r\n\r\n### Notes\r\n- These are **orders of magnitude, not counts**. The casualty rates were fitted to United States earthquakes and the shelter factors to United States Red Cross shelter data, and the damage distribution arrives with the uncalibrated-to-Turkish-stock caveat the debris tool already carries. The tool says so in its help, in the manual and in the run log.\r\n- Bridges and the population on the street away from any building are left out rather than approximated: both need an inventory the plugin does not model - a bridge layer carrying damage states, and a count of people in the street. Hazus's bridge rows and its street-population parameters are therefore not implemented.\r\n- `displaced_hh` and `public_shelter` answer different questions. Most displaced households go to family, friends or a hotel; only the fraction \u03b1 that stays in the area seeks a public shelter, which is why the neutral default is an upper bound and not a forecast.\r\n- **Three defects the runtime matrix caught, and the engine suite could not.** The first two were reported as the same `FAIL (72/73)` on both runtimes, and the first was hiding the second - so the count staying the same between runs was not evidence that nothing had moved. The third was hiding behind both, and behind a value check that could not see it.\r\n  - **The tool failed on its own primary workflow.** Its first input is the four damage-state columns the debris tool writes, and those four columns are only the damaged states: the undamaged share of a building is their complement. The engine's event tree indexes the distribution by state, so reading those four as a distribution raised `KeyError: 'none'` - the tool failing on the one workflow it exists for, while 665 engine checks passed, because every one of them supplied all five states by hand. `engine/impact.py` now has `fill_undamaged`, which completes a four-state distribution with its complement (clipped at zero, so a row summing above 1.0 gets no undamaged share rather than a negative one, and the run log still reports those rows), and the tool calls it. Five checks were added for the new function, including one that the filled distribution reproduces the hand-derived C1L counts. It is the second release in a row where the matrix's chained run found what the unit suite could not.\r\n  - **The chained run modelled a population of nobody and passed.** With the harness fixed the case ran, and the run log read `Population modelled: 0 people across 70 feature(s)` while the same run reported 17.7 displaced households - the tool's own \"no silent zeros\" rule, broken, with the value check green. The cause is the seam between two tools: `planx:seismicdebris` writes its buildings as **point centroids**, because the debris envelopes need a point to hang off, and a centroid has no area, so `QgsGeometry.area()` is 0.0 for every building and the floor-area occupant estimate multiplies out to zero everywhere. Casualties went to zero with it. Shelter did not, because a residential building with no dwelling-unit column counts as one dwelling and the displaced-household equation divides a default dwelling count by an occupant count it never needed - which is how a run with no people in it still produced a number, and how the loose end was spotted.\r\n    - The fix is a footprint that survives the handover: `planx:seismicdebris` now writes `footprint_area`, and the casualties tool reads a footprint from a column when the geometry gives none, auto-detected or named. It **raises** when no building has a footprint at all rather than returning the zero-population answer.\r\n    - The value check needed a floor as well as a ceiling. Every bound it asserted was an upper bound - casualties below occupants, shelter below occupants - and zero occupants satisfies all of them, so the defect was invisible to the check written to catch physical impossibilities. It now also requires the run to put somebody in a building. `planx:seismicimpact` is the only entry in `VALUE_EXPECTATIONS` with a chained case behind it, so this was also the only case in the matrix where a zero-population run was possible.\r\n    - The run log now names the source: `Footprint area: 0 building(s) from their geometry, 70 from column 'footprint_area'`.\r\n  - **The matrix's own resolver could not reach the tool's other two input paths.** With the first fixed, the next run failed in 0.10 s before the tool started: `KeyError: 'buildings_occupancy'`. The harness resolves a `fixture:` token against the file dictionary, which holds only the GTFS zip; the occupancy fixture is a layer and lives in the layer dictionary. Every `fixture:` token in the file until this release named that one zip, so the branch had never had to look elsewhere. The two coverage cases added here - a population field against an occupancy field, and a single damage-state column - were the first to name a layer, and had therefore never executed once. `_resolve_special` now looks in both dictionaries and raises naming what it does hold.\r\n- The engine suite stands at 670 checks, including the Section 12 rate tables, the collapse split, the Table 12-2 occupancy shares, the Section 13 weights and the identities that \u03b1 is exactly 1.0 at neutral modifiers and that a non-residential building contributes no shelter need.\r\n- **Still open, found while fixing the above.** `planx:seismicdebris` feeds `QgsGeometry.area()` - the layer's own area unit - straight into `seismic.debris_extent`, which documents its outputs as cubic metres and tonnes. On a metre grid the two agree; on a layer projected in feet, or one still in degrees, `debris_vol_m3`, `debris_pile_m3` and `debris_mass_t` are inflated by the square of the unit factor. It is the same class as the `streetmorphology` defect below: a unit assumption that holds for the demo city and is never checked. The radius is unaffected, being driven by height alone. Not fixed here - it changes reported values, and the same release should carry both. The constraint is now stated on `debris_extent` itself, and on the new `footprint_area` column, so the next reader meets it at the boundary rather than in a bug report. **Fixed in 4.15.1**, which also names the two error classes separately - the projection's own scale factor is the one that bit here, on a metre CRS.\r\n- **Still open, carried from 4.14.0.** `planx:streetmorphology` divides raw network length by 1000 and hull area by 1e6 to report `total_length_km` and `intersection_density_km2`, so on a layer projected in feet those two figures are wrong by 3.28 and 10.8. It remains a different tool's defect and belongs in a release that changes reported values and says so. **Fixed in 4.15.1**, in the release that changed reported values and said so.\r\n\r\n## [4.14.0] - 2026-09-19\r\n\r\nGround Motion Scenario (`planx:groundmotion`) is new. One algorithm added; none changed, none removed. The seismic group now holds a chain rather than a single tool: ground motion in, debris out.\r\n\r\n### Added\r\n- **`planx:groundmotion` - Ground Motion Scenario.** Seismic Debris could always be driven by a joined PGA field, but nothing in the plugin produced one: the only ways in were a ShakeMap or an AFAD raster from outside QGIS, or the debris tool's own magnitude guess, which has no distance, site or fault term. This tool computes the shaking itself, from Akkar, Sandikkaya & Bommer (2014) - one of the four ground-motion models in the logic tree of Turkey's 2018 national seismic hazard map - with the authors' own published coefficients.\r\n- **`SCENARIO`** - A, a point source (an epicentre plus a focal depth), or B, an extended rupture (a fault trace). An extended rupture has no single hypocentre, so geometry B offers Joyner-Boore distance only; a point source has no rupture surface, so its Joyner-Boore and epicentral distances are the same number. The tool raises with the reason rather than inventing the distance it cannot support.\r\n- **`DISTANCE_METRIC`** - Joyner-Boore, epicentral or hypocentral. The choice is written to every output row as `dist_metric` and the distance itself as `r_km`, so a result joined back onto a map can never be read as the wrong distance.\r\n- **`FAULT_MECHANISM`** - strike-slip (the model's reference), normal, reverse. **`EPSILON`** shifts every value by a chosen number of total standard deviations, which is how a scenario is pinned to a rarer level than the median.\r\n- **`VS30`** and **`VS30_FIELD`** - site velocity as a constant or per receiver. The default is 750 m/s, the model's own reference velocity where its site term is exactly zero, so a default run reports rock-reference motion and every bit of soil amplification in a result is a choice the user made. The value actually used is written to a `vs30` column; a receiver whose field is empty falls back to the constant and is flagged in `caveat` instead of quietly becoming rock.\r\n- **`SPECTRAL_PERIODS`** - any periods the model tabulates, in seconds, added to the output as `sa_<T>_g` columns. It does not interpolate: a period the paper does not publish is refused, with the nearest tabulated periods named in the error. An interpolated value would be one no source supports.\r\n- Output is the receiver layer returned with its own geometry and attributes plus `pga_g`, `pgv_cms`, `sa_<T>_g`, `r_km`, `dist_metric`, `vs30`, `pga_rock_g` (the rock PGA the site term is conditioned on, so the amplification applied to each receiver is auditable), `sigma_pga` and `caveat`.\r\n- **`caveat`** names the applicability limits each row breaks - `mag_below`, `mag_above`, `dist_above`, `vs30_below`, `vs30_above`, `depth_above`, `vs30_missing`, `vs30_capped` - and the run log repeats them as warnings. The number is still computed and still written; the point is that it is labelled rather than silently extrapolated, and the paper's own magnitude, distance, depth and site ranges are the ones the flags use.\r\n\r\n### Changed\r\n- Distances are converted from the receivers' own CRS units into kilometres. Every term of the model is in kilometres - the distance, the focal depth, the `a6` site-scale constant added to `R\u00b2` - and the receiver coordinates arrive in whatever the layer's projection uses. Assuming metres is right for UTM and wrong by 3.28 for a state-plane CRS, and a factor of 1000 is wrong enough to read the demo city's 750-metre span as 472 km, which is what an early build of this tool did: the matrix case reported receivers up to 472 km away from an epicentre 500 m from most of them, and stayed green because a wrong number is still a number. The scale is now read off the CRS, and the engine's distance functions take it as a required argument with no default, so the mistake cannot be made silently again.\r\n- A fault trace is reprojected into the receivers' CRS before distances are measured. It previously kept its own layer's coordinates, so a trace in a different projection - or one in feet - produced distances mixed from two unit systems.\r\n- The Seismic Risk group's manual section no longer describes a single tool, and the debris card's ground-motion paragraph now points at this tool as the in-plugin route to a PGA field.\r\n- Manual counts are now 314 numbered display equations, 388 reference entries, 298 with DOIs; the version literal, the README and the metadata are current. 72 algorithms, 19 groups.\r\n- The pure engine suite gained 33 checks for the ground-motion engine and stands at 627, including a committed fixture of pyGMM's own expected outputs that pins the functional form, the site term and the row indexing, and checks that state the unit contract in metre-scale coordinates - synthetic coordinates small enough to pass as either unit are how the 1000\u00d7 error survived the suite. See `THIRD_PARTY_NOTICES.md`, which is new and covers the ASB2014 coefficients, the pyGMM test values and the Hazus fragility curves.\r\n- `tests/smoke_provider_catalog.py` counted reference entries carrying a DOI by counting `doi.org` *occurrences*, so one listing with two DOIs paid for one listing with none and the README's \"with DOIs\" figure read one higher than the manual supports. It now counts entries, and the README states 298.\r\n\r\n### Notes\r\n- The model has no raster output and no raster site sampling. QGIS's own *Create grid* into the receivers parameter gives a scenario grid, and it keeps each run's inputs visible in the model designer; a hidden raster path would have been a second, less inspectable way to ask the same question.\r\n- `tests/qgis_runtime_algorithm_matrix.py` now checks values, not only presence, for tools where a silent unit error is the plausible failure. It previously asked one question of every output - was anything produced? - which no unit bug can ever fail. The ground-motion case asserts that the demo city's receiver distances are consistent with a city under a kilometre across, and fails the case if they are not.\r\n- **Known follow-up, not fixed here.** `planx:streetmorphology` divides raw network length by 1000 and hull area by 1e6 to report `total_length_km` and `intersection_density_km2`, so on a layer projected in feet those two figures are wrong by 3.28 and 10.8. It is the same class of defect as the one this release fixed in the ground-motion tool, but it is a different tool, and correcting it changes reported values, so it belongs in a release that says so.\r\n- These are median values from a shallow-crustal model for Europe and the Middle East: not design levels, not a code check, and not a substitute for the national hazard map. The model carries no basin term and no directivity, so deep soft-soil amplification and forward-directivity pulses are not represented.\r\n\r\n## [4.13.0] - 2026-09-19\r\n\r\nSeismic Collapse and Debris Spread (`planx:seismicdebris`) rebuilt on the Hazus fragility curves. One algorithm changed; none added, none removed. All five previous outputs keep their meaning, three new parameters are additive and the defaults reproduce the old single-realisation workflow.\r\n\r\n### Fixed\r\n- **The collapse probability saturated.** The model scaled a construction-year baseline by `exp(0.8 (Mw - 7.0))` and clipped at 1.0, so the pre-1985 stock hit certainty at roughly Mw 7.2 and the 1986-2000 stock at Mw 7.8 - the band a Marmara scenario occupies. The tool reported `collapse_prob = 1.0` for the entire oldest tier and carried no information about it. Probabilities now rise smoothly and separately with magnitude, from a lognormal fragility rather than an exponential clip.\r\n- **Debris came only from total collapse.** A building left standing while losing its facade, infill walls and parapets put nothing in the street, which under-counted the most common form of post-earthquake obstruction. Debris is now generated from the sampled damage state through a released-material fraction (0 for none and slight, 0.10 moderate, 0.35 extensive, 1.00 complete), so partial damage contributes to blockage.\r\n- **Source A accepted a non-polygonal layer and returned a zero-area network.** Everything downstream - blockage, corridors, navigable core - is areal, so the run looked successful and meant nothing. It now raises with the message naming sources B and C.\r\n\r\n### Added\r\n- **`PGA_FIELD`** (optional, recommended) - a peak-ground-acceleration column in g on the buildings layer. Supplying it drives fragility from real site shaking and bypasses the magnitude path entirely, which is the only route with distance and site effects; buildings with a null or non-positive value fall back to the nominal intensity and the log counts them.\r\n- **`BUILDING_TYPE`** - 13 Hazus structural types (C1, C2, S1, S2, S3, S4, W1, W2, RM1, RM2, PC1, PC2, MH). The height class is appended from the floor-count field, so C1 with five storeys reads the `C1M` row. Three Hazus types are deliberately absent: C3, S5 and URM have no Moderate- or High-Code curve in the manual, and a row invented to fill the gap would be worse than the absence. The help text says what that costs.\r\n- **`REFERENCE_PGA`** - the nominal site PGA at Mw 7.0 (default 0.35 g) used only when no PGA field is given. Exposed rather than buried, because it is a user assumption, not a physical constant.\r\n- **`VOID_RATIO`** (default 0.35) and **`DEBRIS_DENSITY`** (default 1.8 t/m3) - the pile's air fraction and the material density. `debris_vol_m3` keeps its meaning as solid material volume; the two new columns are `debris_pile_m3` (bulked, what competes with the street for space) and `debris_mass_t` (tonnage to haul).\r\n- **`MIN_CLEAR_WIDTH`** (default 3.5 m) and the **`OUT_NAVIGABLE`** output - the corridor network morphologically opened at the clear width, so a sliver of pavement beside a debris pile stops counting as an evacuation route. Unblocked is not the same as passable, and the corridors layer alone over-stated the evacuation map invisibly. The log reports the street area lost to pinching.\r\n- **`SIMULATIONS`** (default 0) - reruns the scenario under N seeds and writes an empirical `collapse_freq` per building plus mean, standard deviation and 5th/50th/95th percentiles of blocked street area. This replaces the manual's advice to repeat the tool by hand with one parameter. `collapse_freq` is NULL when the parameter is left at 0.\r\n\r\n### Changed\r\n- Damage state and `collapse_prob` now come from the Hazus equivalent-PGA structural fragilities (Technical Manual 6.1, Tables 5-37 to 5-40: High-, Moderate-, Low- and Pre-Code), with the manual's uniform dispersion of 0.64 - the root-sum-of-squares of its capacity (0.4) and demand (0.5) dispersions. The medians were transcribed from the published manual, not recalled, and the embedded table was verified row for row against a parse of the source PDF.\r\n- The four construction-year tiers keep their breakpoints but are now *Hazus design levels* rather than probabilities: 1985 and earlier Pre-Code, 1986-2000 Low-Code, 2001-2018 Moderate-Code, newer High-Code. This is an analogy to Turkish regulation years, not a calibration, and the engine, the manual and the reference list all say so.\r\n- `collapse_prob` is still the last preferred-token numeric field, so the default graduated renderer still colours the map by it. `tests/smoke_plugin.py` now asserts that against the algorithm's own field list rather than a copied one, because adding `debris_pile_m3`, `debris_mass_t`, `collapse_freq` and `damage_state` moved that choice and nothing else in the suite would have noticed.\r\n- Manual card rewritten to the new chain: 12 display equations, the design-level and released-material tables, the navigable-core definition, and a validation table of the C1M damage distribution by design level. Manual counts are now 305 numbered display equations, 386 reference entries, 298 with DOIs; the README, the version literal and the changelog are current.\r\n- The runtime matrix now covers the seismic tool through the centerline network sources it can actually express. It previously bound the demo city's line layer to source A, a polygon input, so blockage, corridors and the navigable core all had zero area while the case read green.\r\n\r\n### Notes\r\n- The engine suite stands at 596 checks. `tests/qgis_runtime_algorithm_matrix.py` gained extra-run verification: an extra run whose output is empty now fails the case instead of counting as coverage.\r\n- The Hazus curves are American: a western-United States reference spectrum (Mw 7.0, Site Class D, distance at least 15 km) and design levels tied to United States code eras. Turkish building stock, and especially its unreinforced masonry and infilled frames, is not represented by the available type list. Treat the output as comparative screening and do not quote it as a Turkish loss estimate. Calibrating these curves to Turkish damage observations is a separate piece of work and is not done here.\r\n- The same constants still live in `planx_urban_resilience`'s `processing/seismic/monte_carlo_debris.py`. The two plugins are separate repositories, so the engine change cannot be shared; that copy will drift from this one unless it is ported in its own release.\r\n\r\n## [4.12.0] - 2026-09-19\r\n\r\nParking demand and supply balance. Two new algorithms, both in the Travel Demand group. Purely additive: no existing algorithm's parameters, ids or default behaviour changed.\r\n\r\n### Added\r\n- **Parking Demand Estimator** (`planx:parkingdemand`) - estimates the parking spaces each zone demands from its land-use category and size, through an editable per-category rate table. Each entry is `category=basis:rate`, where the basis (`unit`, `sqm`, `seat`) states what one rate unit counts against, because parking standards are not written on one common denominator. A zone whose category matches no rate row demands zero and is listed separately in the log, so a gap in the rate table is never reported as a zone that needs no parking. The shipped rates are illustrative values written in the ITE convention, not figures taken from it, and the help text says so.\r\n- **Parking Supply-Demand Balance** (`planx:parkingsupplybalance`) - compares the demand each zone carries against a counted parking inventory within an access radius, and reports the surplus or deficit. Reach follows the street network when one is supplied and is straight-line otherwise, and the output records which was used. Every zone is classified `counted`, `zero supply found` (a real deficit) or `supply data absent` (a coverage gap in the inventory), and the unsurveyed case gets a NULL balance so the column cannot be summed into a false shortfall.\r\n\r\n### Changed\r\n- The manual documents both new tools to the same depth as the rest, and its counts, version literal and search index are current: 71 algorithms, 19 tool groups, 300 numbered display equations, 386 reference entries.\r\n- README and Studio dock counts updated to 71. The engine suite stands at 570 checks.\r\n\r\n### Notes\r\n- Mixed-use and shared-parking reduction factors are deliberately not implemented. The demand tool reports the sum of the single-use requirements, which is the quantity any shared-parking argument has to answer.\r\n\r\n## [4.11.2] - 2026-09-19\r\n\r\nQuality infrastructure. No algorithm behaviour changed and no algorithm was added.\r\n\r\n### Fixed\r\n- The Help button was dead for all 69 algorithms. `DOC_BASE_URL` still named the retired GitHub Pages host, so every Help link and both Studio dock documentation actions opened a 404 while `metadata.txt` advertised the live host. The URL is now defined once and imported by the dock.\r\n- `tests/test_engine.py` (528 checks, no QGIS imports) was filed under `tests_qgis` with `tests_pure = []`, so no CI-reachable runner executed it. It is now filed as a pure suite.\r\n- The manual advertised a stale version and eight stale Processing IDs (`planx:buildingformmetrics`, `planx:morphologicaltessellation`, `planx:spacematrixdensity`, `planx:streetnetworkmorphology`, `planx:multiamentiyaccess`, `planx:frontalareaindex`, `planx:noisescreening`, `planx:airscreening`), so documented IDs did not match the registry.\r\n- The README claimed counts nothing checked: it said 285 display equations against the manual's 289, and an undefined \"~600 citations\". Both are now counted and true.\r\n\r\n### Added\r\n- `tests/smoke_provider_catalog.py`: 15 pure catalog gates tying the algorithm registry to the manual (anchors one-to-one in both directions, the 19 groups as an exact set, stable ids, one icon per algorithm, version literals, help host versus `metadata.txt` homepage).\r\n- `tests/qgis_runtime_algorithm_matrix.py`: builds a fixture city, auto-wires each algorithm's declared parameters, executes all 69 on QGIS 3.44 LTR and 4.2, and verifies that every declared output is actually populated. Reports the wiring chosen and the algorithm's own log per case.\r\n\r\n## [4.11.1] - 2026-09-17\r\n\r\n- Upgraded official plugin icon to high-end tactile 3D brand identity (isometric 45\u00b0, slim teal pedestal, full bleed transparent canvas).\r\n- Synchronized documentation, repository and issue tracker endpoints to GeoPhilo and GitLab.\r\n- Fixed field preservation and fid handling in network preparation and service areas algorithms.", "external_deps": null, "download_url": "https://plugins.qgis.org/plugins/planx/version/4.18.0/download/"}