-
2026-09-10 - "connect a headset" was a dead end for someone who already had (
net_utils/preview/server.py,net_utils/preview/viewer.html). Reported as the preview never noticing a headset that was plugged in. Measured against the live server:immersive-vrgenuinely was false, withnavigator.xrpresent and the context secure, so the page was telling the truth -- the machine was exposing no device because the Link compositor was not running, though the same check had returned true earlier the same day. The page can only see thatimmersive-vris unsupported, so it asked for the one thing already done: cable in, headset listed by adb, Oculus service running. What was missing was a Link session, and nothing on screen named it. The machine CAN tell the difference, from the sameActiveRuntimevalue the browser reads to find a runtime, somanifest()now carriesxrRuntimeand the viewer says "No headset presenting - start Link or SteamVR." when a runtime is installed (named generically: this ships to PyPI, and an Index or WMR user should not be told to start something they do not have), keeping "No headset - connect one" for machines that have none. Every message this page shows is deliberately one short line: it is read at a glance, often by someone holding a headset. Applied on the FIRST poll rather than on a publish, so an already-open page corrects itself without waiting for a push. The runtime is read outside the publish lock. Also confirmed NOT a bug in the same report: a second push appearing to do nothing is the hot-swap working -- the open tab had already taken v2, andopen_browser="auto"declines to open a duplicate tab while a viewer is polling. -
2026-09-10 - a lost GPU context blanked the preview while the overlay still said everything was fine (
net_utils/preview/viewer.html). Reported as "the WebXR preview ends up broken with textures missing". Reproduced by forcing a context loss against a real 69 MB production push: the canvas went completely blank while the overlay went on showing the live green dot, "v4 ┬╖ updated 3:04:24 PM", "1500 meshes ┬╖ 96,928 tris ┬╖ 47/71 lightmapped materials" and "Headset detected ΓÇö press Enter VR". The page reported perfect health while drawing nothing, and every cause of that is silent: a display-driver reset, GPU memory pressure, or a headset link renegotiating the adapter under an immersive session, which is why it showed up while chasing headset trouble. three.js registers its own handlers and re-uploads everything if a restore arrives (measured: the compressed 2K textures all come back), but the browser owes no restore, so a page can sit blank indefinitely with nothing on screen to say why or what to do. The canvas now reports both events: loss sets the status to the error state, so the indicator dot goes red, and names the three causes plus the one cure; restore hands the hint back to the XR state rather than leaving a stale recovery message where the headset status belongs. The warning is STICKY, which the first cut got wrong: a completed load repaints the status green, and pushing again is the first thing anyone does when a preview looks wrong, so the next push put the healthy overlay straight back over a canvas that was still blank. A lost context now outranks every other status until a restore clears it. What this was NOT, each ruled out by measurement rather than by reading: all eleven published GLBs are structurally intact (28 images, 28 textures, every material textured, nothing by external URI, none truncated); the KTX2 path is sound, with the Basis transcoder and its wasm both fetching and 312 compressed sub-image uploads landing 26 textures with full mip chains; republishing disposes correctly, holding at exactly 26 live 2048x2048 compressed textures across four hot-swaps; and seven concurrent tabs each holding the whole model showed no texture loss at all. An apparent per-republish texture leak was a measurement artifact -- the probe racing disposal -- and the only genuinely unfreed textures are 12x12 RGBA32F, about 2 KB each, far too small to starve anything. -
2026-09-10 -- a channel the declared mode throws away is NAMED (
img_utils/_img_utils.py,.../map_factory/processor.py). A map type's declared mode is enforced before the write, so an RGBA source of a type declaring"RGB"is flattened -- a cutout packed into aBase_Colornever reaches any shader, and nothing said so.ImgUtils.dropped_channelsalready answered the same question for a CONTAINER, and the mode-enforcement site consulted nothing. Deliberately NOT reclassified: a base-colour alpha is TRANSPARENCY in glTF/Unreal/Painter and SMOOTHNESS under Unity's Standard shader, so declaring one meaning inMapRegistrywould be provably wrong for a major consumer, and every registry-set package inherits that answer through output naming -- declining to guess is the decision, and the silence was the part nobody could defend.dropped_channelsnow answers for an explicittarget_mode=as well as for a container, because a registry mode narrows the way a container does and that band rule is subtle enough (a widening replicates rather than truncates, so"L"to RGB loses nothing) that asking it twice would be two chances to get it wrong. The substance half -- a discarded band is only a LOSS when something was in it -- moved out ofMapOptimizer.channel_loss_warningintoImgUtils.channels_carrying_data, so the container warning and this one cannot drift; the optimizer trades 14 lines for 3 and is behaviourally identical. The processor could not simply call the optimizer:map_optimizerimportsmap_factory, so the dependency only runs one way. A uniformly opaque alpha stays quiet on purpose -- warning about it trains the reader past the line that matters. Moving the check also fixed a latent bug in it: PIL answers a SINGLE(min, max)pair for a one-band image rather than a tuple of them, so zipping it raw paired the band with the minimum and the range test read it as absent. Unreachable where it sat --dropped_channelsonly ever names alpha, which a one-band image does not have -- and reachable the moment the helper became callable with any band, so it is fixed rather than inherited.test_img+9,test_map_factory+2 (including the quiet case, so the pair cannot pass vacuously); removing the call fails 1 of 333. Full suite: 4521 passed. -
2026-09-10 --
FileUtils.get_file_contentstakes adefault=(file_utils/_file_utils.py). The function catches OSError, prints a traceback and answers None, and that contract lived only in the docstring -- so a caller that had not read it fed the None straight on. Measured 2026-08-20:_PkgVersionUtils.update_versionhanded it toenumerateand raisedTypeError: 'NoneType' object is not iterablemid version-bump, reported nowhere near the read that actually failed. Additive rather than raising, which is the whole point: this is the root of a five-package chain where a break costs an alias plus a minor bump, anderrors="replace"shows the leniency is deliberate -- so the failure mode moves into the signature without contradicting it. Mirrorsread_json's shape, the sibling primitive for the same "cannot be read" case, withdefaultLAST so no positional caller of the older signature changes meaning.test_file+2; dropping the return fails 1. -
2026-09-10 - the release chain's
validate-uitkjob is confirmed green. It had been red for at least two releases, dying mid-run at 28% with no reason in the log. The fix (73005fd: pinPySide6<6.11as uitk's Qt binding, and stop the step swallowing why it died) landed on main, and this entry stayed open only for want of an observation. Now measured three ways: the two runs the backlog named both showvalidate-uitkFAILURE (34084027361, 33948778075), while the two release PRs since the fix both show SUCCESS (34299082767 on 2026-09-09, 34431208085 on 2026-09-10), and aworkflow_dispatchon main withvalidate_downstream=true(34507887754) came back success across all five jobs. No code change here - the value is the confirmation, which is what the entry was waiting for. -
2026-09-10 - the +88% texture inflation is REPRODUCED, explained, and was never a bug (
core_utils/engines/textures/map_optimizer.py). Two production 4K maps came out of a scene export at 5.98 MB -> 11.23 MB and 7.40 MB -> 13.94 MB, and the cause went unfound for weeks; a synthetic pure re-encode inflates by 0.1% at most, so the magnitude never matched. Run against the two named files directly, it reproduces to the digit: both sources are modeP, 8-bit PALETTE PNGs, andallow_palettedefaults off, so the optimizer upcasts them to RGB. The growth is the upcast and nothing else. Withallow_palette=Truethe same two files come out at -2.0% and -0.7%. Nothing was wrong: the upcast is deliberate, and the keep-original-bytes rule correctly stays out of it because a mode change is not a pure re-encode. What was wrong is that nothing SAID any of this, so a correct conversion read as a defect.optimize_mapnow names it whenever a palettised source is upcast - which of resize, re-encode or bit depth grew the file, and the flag that keeps the palette. The finding under the finding, and the reason the note leads with it: a palette had already cost the source its colour precision before any of this ran. One of the two is a NORMAL map quantised to 256 colours, which is a defect in the art, not in the pipeline.test_map_optimizer+3, red first, covering the upcast, the preserved palette and a plain RGB source that must stay silent. -
2026-09-10 -- the preview gained a host-free source, and the GLB special case that bypassed the pipeline is gone (
net_utils/preview/bridge.py,file_utils/mesh_convert/glb_pipeline.py,net_utils/preview/deliverer.py). Three changes that are one change.FilePreviewBridgeis the third peer ofmayatk.WebXrPreviewandblendertk.WebXrPreviewand the only one that needs no host:_resolve_objectstakes the paths it is handed and_producewraps one in aPayloadwithout exporting anything, because the file already IS the export. Everything downstream is the shared chain, so a file pushed from a standalone panel and a selection pushed from Maya reach the page through one code path.GlbPipeline.buildnow short-circuits on a.glbsource, reporting every stage as skipped. That is what removes the second delivery shape:publish_fileused to bypass the pipeline entirely, and a bypass is exactly where "the preview shows X but the export ships Y" comes back. The passes it skips exist to repair what an FBX translation loses, and a finished GLB has answered all of them -- re-running them would re-encode textures the author chose. It COPIES todstrather than reporting the source in place, which is load-bearing rather than tidy: the deliverer publishes withmove=Truebecause the GLB is normally its own scratch artifact, and on this path it is the user's file. Pinned by a test that asserts the source still exists with its original digest after a push, because that failure would be unrecoverable. The user's file is safe on the other side too, and by a mechanism that was already there:_release_payloadrefuses any path the bridge did not itself mint, so both the deliverer's consumed-payload delete and the pipeline'srelease_sourcedecline it silently.push(progress=...)is named rather than left to**paramsfor the same reasontexture_formatandscriptsare -- that bag goes to the EXPORTER, so a callback swept up there would be handed to the FBX write and never reach the build -- and the deliverer forwards it request-scoped, never parked on the deliverer, which is a CLASS attribute shared by every instance of a bridge and would outlive the panel that supplied the callback.test_preview_server/test_preview_playblast/test_mesh_convert: 518 passed, 1 skipped. -
2026-09-10 -- a push opened the DEFAULT browser, which frequently cannot enter VR at all (
net_utils/preview/server.py,core_utils/test_sandbox.py).open_in_browserwent throughwebbrowser.open, which only ever opens the system default -- and a default that is a Chromium FORK loads the page perfectly, renders the model, and simply never grows a VR button, with nothing on screen to say why. Measured on this machine at the binary level rather than inferred from behavior:openxr_loader/XR_KHR_D3D11are present in Edge'smsedge.dlland absent from every binary Vivaldi ships, and absent from Qt'sQt6WebEngineCore.dllin both PySide6 and Maya. Vivaldi accordingly answersisSessionSupported('immersive-vr')false andrequestSessionwithNotSupportedError: No XR hardware found, while Edge answers true against the same server in the same minute. Vivaldi's own position is that VR was disabled at compile time and the feature request was archived for want of votes, so this is not configurable and no extension can supply it (an extension cannot register an XR runtime; the emulator that exists FAKES a device).open_in_browsernow picks the first installed browser from a SHORT list of builds verified to carry the loader -- but ONLY when an OpenXR runtime is actually registered, read fromHKLM\SOFTWARE\Khronos\OpenXR�\ActiveRuntime, which is the same value Chromium itself reads to find one. That gate is the whole design: with no runtime the preferred browser could not enter VR either, so switching would buy nothing, and a machine with no headset keeps the browser its user chose. It falls back to the system default when no runtime or no capable build is present, so a machine with neither is no worse off, and falls back a SECOND time if the preferred exe is present but will not start, because a broken or half-removed install must not be a new way to end up with no browser at all. Verified live against the headset, which is the only way to see the parts a unit test cannot reach (it cannot launch a browser at all --TestSandboxrefuses): against the running Oculus runtime the push resolved Edge, the page was polled by anEdg/User-Agent and never by Vivaldi's, and that page reportedimmersive-vr: truewith anENTER VRbutton on it. Two instruments had to be thrown away first -- process counting cannot see a URL handed to an already-running browser, andhas_viewer()reads True before any page exists becauseopen_in_browserdeliberately touches it on launch. The list names builds, not a family: Vivaldi, Opera, Brave and Qt's WebEngine are all Chromium, so an "is it Chromium?" test would confidently pick a browser that can never show the button. This also closed a hole inTestSandbox, which is the part worth reading: its browser guard patchedwebbrowser.open/open_new/open_new_tab, but those functions only open the default -- so the moment any code needs a SPECIFIC browser it constructsBackgroundBrowser/GenericBrowseritself, and that path reached a real launch straight past the guard. Latent until this change, live the moment it landed, so the guard now covers those classes too andis_activeverifies them.test_preview_server+4 andtest_test_sandbox+1. The deliverer cases needed pinning as well: they assert WHETHER a tab opens, not which browser gets it, and the preference would otherwise have made them pass on a machine without Chrome or Edge and fail on one with them. -
2026-09-10 -- "Enter VR does nothing" was accurate, and the page said nothing about why (
net_utils/preview/viewer.html). Reported from real use, and it has two distinct causes that both present as a dead button. An immersive session is EXCLUSIVE per device, so one held by another tab, another browser or a crashed one refuses every later request -- and Chromium raises thatInvalidStateErrorSYNCHRONOUSLY, out of VRButton's own click handler, where nothing catches it. The other cause raises nothing at all: a headset that is asleep or not presenting leavesrequestSessionPENDING forever, so there is no error to catch, no session, and no change to the button. In both cases the only trace was a console line, which is exactly what a person wearing a headset cannot open. The page now reports both -- the reason for a refusal, and after 12 s a plain statement that the headset never answered, the same treatment the three.js CDN watchdog already gets. The first attempt listened only forunhandledrejectionand was wrong, which is worth recording because it looked right: the refusal is a synchronous throw, so it arrives as anerrorevent and the rejection listener never fired. Caught by testing it against a live stuck session rather than reasoning about which event should fire. A third bug came out of the fix: the failure text was sticky, so a press that later SUCCEEDED left the page still showing the old failure. It is cleared on the way into each attempt by a CAPTURE-phase listener -- bubble phase runs after the throw and would have wiped the fresh message instead of the stale one. Verified live against a real headset and a real stuck session: the refusal path shows the runtime's own reason, the pending path shows the watchdog line after 12 s, and a seeded stale message is gone by the next press.test_preview_server+1, static, in the same spirit as the beacon-path and device-arrival checks. -
2026-09-10 -- the preview server answered a DNS-rebound page, on reads as well as writes (
net_utils/preview/server.py). The guard comparedOriginagainst the request's OWNHost, which is exactly the pair an attacker controls: a page atevil.exampleresolved to127.0.0.1sends both headers namingevil.example, they MATCH, and the same-origin check passes for a server that page has no business reaching. That comparison stops an honest cross-origin post and was never able to stop a dishonest one.Hostis now checked FIRST against the spellings a loopback bind legitimately answers to (127.0.0.1:<port>,localhost:<port>,[::1]:<port>), read off the live socket so it is right on an ephemeral port and before the owner is attached. All three are load-bearing:urlis always127.0.0.1, the secure-context guarantee this module rests on nameslocalhost, and a tethered headset reaches the page aslocalhostthrough anadb reversetunnel. The impact today is small, and that is precisely why it was worth fixing now -- the two routes write a settings file and clear a viewer flag, so the worst a rebound page could do was restyle a deliverable. It surfaced because a command route was being designed on top of this guard, and a route that can drive host-side tooling is a different privilege class entirely; inheriting a guard calibrated for "restyle a preview" would have shipped that escalation silently. Reads are guarded too, and that half is the bigger one: the first pass covered only POST, which would have left the manifest and the published GLB -- the user's actual deliverable -- readable by a rebound page, a worse outcome than the settings write the POST guard exists for.do_GETchecks first thing;do_POSTchecks only AFTER draining the request body, since a refused POST still arrived with one and leaving it in the socket desynchronises the next request on a keep-alive connection.test_preview_server+2. Proven by mutation rather than by reading: with the allow-list live the rebound POST is refused 403, and with_allowed_hostspatched to permit that host the identical request is accepted 200 -- so the refusal is demonstrably the allow-list's and not the Origin comparison's, which is the whole claim. -
2026-09-10 -- the preview page checked for a headset ONCE, at load, so one connected later was never seen (
net_utils/preview/viewer.html). The page is designed to be left open across pushes; the headset is not left on. So "page first, headset second" is the normal order, and it was the one order that could not work:isSessionSupported('immersive-vr')ran once at module scope, and a false answer stood for the life of the tab. The VR button never appeared, nothing was logged, and the page's own advice was to reload -- which is exactly what a person already wearing a headset cannot do, since there is no tab strip in there to find. Support is now re-checked on thedevicechangeevent the UA fires when the set of XR devices changes, and the button is created on the way in and REMOVED on the way out (a button whose device has gone throws when pressed, which reads as a broken page rather than an absent headset). The hint line no longer tells anyone to reload. Two hazards were designed out rather than discovered later: the button is never removed whilerenderer.xr.isPresenting, because that same button is the only way OUT of a session and removing it would strand whoever is wearing the headset; and the listener registration is feature-guarded, because everything below the XR block lives in the same module -- the model load, the poll loop -- so a throw there would turn "no VR button" into a blank page, which is a far worse failure than the one being fixed. Observed, not merely wired:test/temp_tests/_devicechange_probe.pyserves the real page to headless Edge withnavigator.xrreplaced by anEventTargetstand-in, then fires a syntheticdevicechange-- no button and the "connect a headset" hint at load, button plus "Headset detected" after arrival, button gone again after departure, no page errors. That stand-in is the point: the suite has no JS runtime and CI has no headset, so the event can only be watched by manufacturing it.test_preview_server+1 pins the listener at source level for the ordinary run, in the same spirit as the existing beacon-path check. What is still unobserved is real hardware -- the headset on hand was unauthorized for adb throughout, so no physical connect-after-load has been watched end to end. -
2026-09-10 --
optimize_mapcould return a BIGGER file than it was given (core_utils/engines/textures/map_optimizer.py). A pass called "optimize" that doubles a file is wrong whichever way you read it. Reported from a production export:..._DIFF.png 4096x4096, 24bit, 5.98 MB -> 11.23 MB (+88%)and..._NRML_OGL.png 7.40 MB -> 13.94 MB (+88%), while three other maps in the same set shrank correctly (-36%, -50%, -42%). A pure re-encode that comes out larger now keeps the ORIGINAL bytes -- the ruleMeshConvert.optimize_glb_texturesalready applies to the GLB half. The source bytes are held in memory before the write, because the write may land ON the source and there would otherwise be nothing to fall back to. PURE is the load-bearing word: the rule fires only when the pixels, the container and the MODE are all unchanged. A resize is meant to change the bytes; a container change was asked for; and a mode promotion is a CORRECTNESS step, not an optimisation -- it is how PNG palette-transparency is stopped from being read as alpha, and grayscale -> RGB measured +155.6% in the probe, which keeping the source would have silently undone. Honest limit: the reported 88% was never reproduced. Synthetically a pure PNG re-encode inflates by +0.0% to +0.1%, so if those production sources came from a stronger PNG encoder than PIL the guard now covers them, and if the inflation came with a mode or bit-depth change it is deliberate and the guard correctly does not. The backlog entry stays open on that question.test_map_optimizer+3, red first against a disabled rule. -
2026-09-10 --
prepare_mapswrote its derived maps over the CALLER'S OWN SOURCE FILES (core_utils/engines/textures/map_factory/processor.py).output_dirdefaults to the source folder and the output name is<base>_<map_type>.<ext>-- which for an already conventionally named map IS the source path -- so every re-encode landed on the input. Measured: an RGBABase_Color.pnghanded toprepare_mapscame back RGB, 187 -> 138 bytes, alpha gone, with nothing logged; the same design destroyed a production ambient-occlusion bake, which is how it was found. A pipeline may legitimately re-encode; what it may not do is use the inputs as its scratch space.TextureProcessor._guard_input_overwritenow redirects a write that would land on one of this run's inputs into aderived/subfolder under the SAME filename, and warns once naming the file. The filename is deliberately preserved: the texture manifests classify on it, so keeping it intact keeps every downstream consumer working while the source survives -- refusing the write instead would have silently dropped a conversion the caller asked for. Guarded at the single entry to the transform block, not at eachsave_imagecall, so a future branch cannot slip past it; the passthrough copy is left alone because it already no-ops when source and destination are the same file, and redirecting it would manufacture a pointless duplicate. Blast radius measured, not assumed: a map needing NO transform still returns its own source path and creates noderived/folder, so the change is confined to the path that previously destroyed data. Consumers traced across the ecosystem --mat_updaterandmap_compositorpass an explicitoutput_dirand are untouched; mayatk'sgame_shaderand the extapps converter default to the source folder and now receive the derived path instead of a corrupted source. What this does NOT settle, and is left in the backlog as the maintainer's call: what an RGBABase_ColorMEANS -- transparency in glTF/Unreal/Painter, smoothness under Unity's Standard-shader convention.test_map_factory+2, both red first: a full-matrix run leaves every input byte-identical, and the RGBA base colour keeps its alpha. -
2026-09-10 --
Polyline.transport_frames: minimal-rotation frames along a path (geo_utils/polyline.py). The sibling ofPolyline.frames, and the one to use when a frame has to stay coherent along the WHOLE path rather than agree with a fixed direction.framesbuilds its normal ascross(up, tangent)against a constantup, which is exact until the path RUNS along that axis and degenerate the moment it does: the cross product collapses and the substituted fallback is unrelated to the neighbouring frames, so the frame snaps. Transport has no reference direction to lose -- the first normal is seeded once, then each is the previous carried through the minimal rotation between consecutive tangents, so a vertical run is no more special than any other. Added for the tube rig, whose spline twist aims at world +Y and is therefore dead on a vertical tube, but it is a general path primitive and is placed as one. Theupargument is a hint for the FIRST frame only and a PREFERENCE, not a requirement: a caller passing the one direction that cannot work still gets a usable basis instead of a collapse. What it deliberately does NOT promise is recorded too -- transport is path-dependent, so a closed loop generally does not return its starting normal, and a caller closing one has to distribute the residual rather than assume it away; there is a test asserting the residual is merely well-formed rather than zero, because asserting zero would be asserting something false. Each tangent points at the next DISTINCT sample rather than differencing neighbours, which is what makes a duplicated point harmless at ANY position INCLUDING the head, where there is no previous tangent to fall back on -- differencing gave frame 0 of[A, A, B, C]a substituted direction unrelated to the path, which in the tube rig would mis-orient the ROOT joint.test_polyline_transport_frames+15: orthonormality across seven path shapes including a riser-hook and a helix, the vertical runframescannot serve, a planar arc keeping zero transported twist, duplicates at head/interior/tail, an entirely coincident path, and a 180 degree switchback -- where the minimal rotation is genuinely ambiguous -- neither raising nor denormalising. The duplicate case is swept over all three axes ON PURPOSE: the direction the bug substituted is(1,0,0), accidentally correct on a +X path, so an X-only test passes against the broken code and proves nothing (verified by reverting the fix -- it fails on the Y and Z leading-duplicate cases and nowhere else). -
2026-09-10 --
ProcessExit.hard_exitexits a DCC-hosted interpreter without runningDLL_PROCESS_DETACH(core_utils/process_exit.pyNEW). A DCC standalone interpreter does not shut down cleanly: Maya's own static destructors fault during detach, and there is no repo code on that stack.os._exitis not the exit that avoids it on Windows -- it routes to the CRT's_exit, which callsExitProcess, andExitProcessstill runs every loaded DLL's detach callback. Measured 2026-09-10 undermayapyon Maya 2025.3 with a scene loaded, exiting with status 7 both ways:os._exit(7)exits 7 and files a crash minidump AND anuntitled[Recovered-...].ma;TerminateProcess(7)exits 7 and files neither. So the damage is usually not a wrong answer -- it is that every ordinary green exit files a crash report. One day of test running left 28 minidumps and 6 recovery scenes in the system temp dir, which is indistinguishable from a real crash: a backlog entry was opened and carried for five weeks reading that litter as live-session crashes during viewport work. Resolving the dumps' own module lists and symbolizing off the on-disk export tables is what settled it -- 23 of 28 weremayapy, notmaya.exe, and all 28 ran throughRtlExitUserProcess -> LdrShutdownProcess -> _execute_onexit_tableintoTdependGraph::deregisterNodeType -> TsharedObject::unref -> free(). Under Qt the status does NOT always survive: uitk's runner measured green runs reported as failed with the status replaced by0xC0000005. Placed here rather than vendored because the vendoring exception is for layers that cannot import each other and these can --uitk,mayatk,blendertkandtentacleall already importpythontkat their entry points -- so this is the extract-the-primitive-upstream case, at the bottom of the stack for the same reasonTestSandboxis: the effect is process-wide and a second copy is a copy that drifts. It is also the write side of the note inscript_run.py, which judges a DCC run by its artifact because "the exit code is advisory only"; a script that exits through here makes its status trustworthy again. Two lessons uitk paid for are carried into the shared copy rather than left behind: the HANDLE types are declared, because ctypes' defaultc_intrestype truncatesGetCurrentProcess()'s -1 pseudo-handle to0x00000000FFFFFFFFand the kill then fails DETERMINISTICALLY into the silent fallback; and the post-kill park is ONE never-returningWaitForSingleObjectrather than a sleep loop, becauseTerminateProcessis asynchronous and re-entering Python bytecode inside a dying process is itself where an access violation clobbered a green run. A failed kill now announces itself instead of falling through quietly.atexithooks, finalizers and C++ static destructors do NOT run, so callers release what must outlive the process first; on POSIX there are no detach callbacks to skip and it is simplyos._exit. The exit is TOTAL by contract: the Windows route is an optimisation overos._exit, never a precondition for exiting, so a refused kill AND anything the ctypes path can raise both degrade to the portable exit -- a caller told "never returns" that instead gets an exception from its last statement reports a finished green run as a crash. For the same reason the module's source is ASCII-only and its one diagnostic is guarded: it goes tostderr, which a Windows console encodes in the ACTIVE CODE PAGE, and a stray em-dash on a non-cp1252 one raisesUnicodeEncodeError-- on the failed-kill branch that would skip the veryos._exitthe branch exists to reach (mayatk's suite driver carries a whole TeeStream to swallow this class of failure; here the cheaper answer is to not emit one).test_process_exit+14, run in child interpreters because the caller cannot survive to assert: the status survives, buffered pipe output is flushed, the call never returns,atexitdoes not run, a raising or refusing_terminate_selfstill exits, a stream that raises on flush does not block the exit, and the source carries no non-ASCII glyph. The two totality cases PIN the Windows branch rather than discovering it -- they swap the module'sosfor a stand-in reporting"nt"-- sincehard_exitonly consults_terminate_selfunder Windows and they would otherwise pass on a POSIX runner without entering the code they exist to cover; a third case proves the stand-in reaches it, and a"posix"stand-in provably does not. Three are non-vacuity anchors, each red first against the naive implementation it guards: a plainsys.exitbody, an unguarded_terminate_selfcall, and a re-inserted em-dash.