
    t\j(                    T   S r SSKJr  SSKrSSKrSSKrSSKrSSKrSSKJ	r	J
r
  SSKJr  \" \5      R                  5       R                  R                  r\S-  rSrS/r/ S	QrS
/r1 SkrSS jrSS jrSS jrSS jrS rSS jrSS jr " S S\	5      rSS jr\ S:X  a  \" 5         gg)u  serve.py — the one committed entrypoint for the Holtburger dev web server.

WHY THIS EXISTS
---------------
The 3D renderer's baked data (manifest/shards + per-landblock scenery/spawns/
events) used to be bound into the web-served tree by a fan-out of FOUR gitignored,
machine-local, cross-drive symlinks. They were absent on every fresh checkout, on
every git worktree, on a new host, or whenever someone forgot to hand-run
`setup-dist-symlinks.sh` — and because the wasm client treats a per-landblock 404
as "0 placements here" (correct for an empty LB), a whole unbound layer rendered
an emptier world with ZERO error signal. Outdoor scenery vanished this way
repeatedly. The dev server itself had the same disease: it was an uncommitted
`/tmp/nocache-server.py` that got wiped on reboot.

This script ends both failure modes:

  * The baked layers are now consolidated as REAL dirs under ONE canonical root
    (`$HOLTBURGER_DIST`), so only a single `external/holtburger/dist` symlink is
    needed — and this script (re)creates it automatically, so a fresh tree or
    worktree just works.
  * It VALIDATES every required layer is present + non-empty before serving and
    refuses to start (loud, exit 1) if one is missing — no more silent serving of
    a scenery-less world.
  * It writes `dist/_health.json` (per-layer file counts) that the page reads at
    boot to show a visible banner if a layer ever goes missing despite all this.

It is a committed `ThreadingHTTPServer` (the single-threaded `http.server` wedges
when a client pulls the 3.6 MB wasm over the reverse tunnel) that serves the
`external/holtburger/` tree from any cwd.

USAGE
  scripts/serve.py                 # validate, write _health.json, serve on :8765
  scripts/serve.py --check         # validate + write _health.json, then exit (CI/preflight)
  scripts/serve.py --allow-missing # serve even if a baked layer is absent (UI-only / worktree dev)
  scripts/serve.py --port 9000     # override port (default 8765, env PORT)

ENV
  HOLTBURGER_DIST   canonical baked-data root. Default /mnt/wbterminal2/holtburger-dist.
                    (Honours the legacy HOLTBURGER_DIST_V2 as a fallback alias.)
  PORT              listen port (default 8765 — the proxy.cjs / perf-worker contract).
    )annotationsN)SimpleHTTPRequestHandlerThreadingHTTPServer)Pathdistz /mnt/wbterminal2/holtburger-distmanifest.json)shardssceneryspawnsevents>   r   r   r
   c                     [         R                  R                  S5      =(       d,    [         R                  R                  S5      =(       d    [        n [	        U 5      $ )z-Resolve the single canonical baked-data root.HOLTBURGER_DISTHOLTBURGER_DIST_V2)osenvirongetDEFAULT_ROOTr   )roots    scripts/serve.pycanonical_rootr   K   s8    ::>>+,d

?S0TdXdD:    c                p     [        S [        R                  " U 5       5       5      $ ! [         a     gf = f)zBCheap non-recursive file count (layers are flat: one file per LB).c              3  R   #    U  H  oR                  5       (       d  M  S v   M     g7f)   N)is_file).0es     r   	<genexpr>count_files.<locals>.<genexpr>T   s     ;myy{11ms   '	'r   )sumr   scandirOSErrords    r   count_filesr%   Q   s2    ;bjjm;;; s   %( 
55c                b     [        [        R                  " U 5      5      $ ! [         a     gf = f)NF)anyr   r!   r"   r#   s    r   dir_nonemptyr(   Y   s,    2::a=!! s   ! 
..c                   U R                  5       (       d:  SU  3nU(       a  [        SU S3[        R                  S9  g[	        SU  S3/U 5        [
        R                  5       (       ay  [        [        R                  " [
        5      5      U :X  a  g[        R                  " [
        5        [        R                  " U [
        5        [        SU  3[        R                  S9  g[
        R                  5       (       a!  [        S	[
         S
3[        R                  S9  g[        R                  " U [
        5        [        SU  3[        R                  S9  g)zMake `external/holtburger/dist` point at `root`, creating/repairing as
needed. The whole point: a fresh checkout/worktree gets the binding for free.
Never clobbers a real directory someone may have baked directly into dist/.zbaked-data root not found: zWARN: u2    — serving without baked data (--allow-missing).fileNzroot 'z?' does not exist (is the /mnt drive mounted? has the bake run?)zrepaired dist symlink -> zNOTE: u4    is a real path, not a symlink — leaving it as-is.zcreated dist symlink -> )existsprintsysstderrdie_loud	DIST_LINK
is_symlinkr   r   readlinkremovesymlink)r   allow_missingmsgs      r   ensure_dist_symlinkr8   `   s     ;;==+D62F3%QRY\YcYcdF4& _`acghI&'4/
		)


4#)$0szzByk!UV]`]g]ghJJtY	$TF
+#**=r   c                 z   0 n / n[         S-  nUR                  5       nX3(       a  SOSS.U S'   U(       d  UR                  S5        [        [        -    H  n[         U-  nUR                  5       =(       a    [        U5      nU[        ;   a   UR                  5       (       a  [        U5      O
U(       a  SOSnX6S.X'   U(       a  Mr  U[        ;   d  M~  UR                  SU SU 35        M     [        R                  R                  5       R                  5       R                  SS	9[        [        5       5      U S
.nXq4$ )zJInspect every layer through the dist link; return (health_dict, failures).r   r   r   )presentfilesu0   manifest.json missing — run the dat-shard bakezlayer 'z/' missing or empty at seconds)timespec)generated_atr   layers)r1   r   appendREQUIRED_DIRSRECOMMENDED_DIRSis_dirr(   COUNTED_DIRSr%   datetimenow
astimezone	isoformatstrr   )r?   failuresfr:   namer$   r;   healths           r   build_healthrN   |   s    FHO#AiikG*1AaPF?JK 00((*0a#'<#7AHHJJASZa`a#*;w4=0OOgdV+B1#FG 1 !))--/::<FFPYFZN$%F r   c                    [         S-  n UR                  [        R                  " U SS9S-   5        g ! [         a(  n[        SU SU 3[        R                  S9   S nAg S nAff = f)Nz_health.json   )indent
zWARN: could not write z: r*   )r1   
write_textjsondumpsr"   r-   r.   r/   )rM   outr   s      r   write_healthrW      s\    
n
$CDtzz&3d:; D&se2aS1

CDs   '3 
A%A  A%c           	         SnSUSUSU 3S/nU  H  nUR                  SU 35        M     USSS	S
SSUS/-  n[        SR                  U5      [        R                  S9  [        R
                  " S5        g )NzH!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! uF   !!  HOLTBURGER DEV SERVER — REFUSING TO START: baked data is unboundz!!  canonical root: z9!!  the following required layer(s) are missing or empty:z
!!      - z!!zM!!  This is the bug that silently emptied the world. Fix it, don't ignore it:zE!!    * make sure /mnt/wbterminal2 (and /mnt/wbterminal1) are mounteduN   !!    * (re)run the bake — see external/holtburger/docs/emit-dynamic-site.mdzE!!    * point HOLTBURGER_DIST at the root if the bake lives elsewherezL!!    * for UI-only / worktree work with no baked data, pass --allow-missingrR   r*   r   )r@   r-   joinr.   r/   exit)rJ   r   barlinesfails        r   r0   r0      s    
C
P
tf%CE z$() 	WOXOV
	 	E 
$))E
,HHQKr   c                  D   ^  \ rS rSrSrU 4S jrU 4S jrU 4S jrSrU =r	$ )Handler   u  Serve the external/holtburger/ tree with dev no-cache headers (the reason
the old /tmp/nocache-server.py existed — Firefox/Chrome ES-module + wasm
caching makes inner-loop iteration confusing). Caching for production-shaped
runs is applied by proxy.cjs in front, not here.c                D   > [         TU ]  " US[        [        5      0UD6  g )N	directory)super__init__rI   	HOLT_ROOT)selfargskwargs	__class__s      r   re   Handler.__init__   s    $C#i.CFCr   c                   > U R                  SS5        U R                  SS5        U R                  SS5        [        TU ]	  5         g )NzCache-Controlz#no-cache, no-store, must-revalidatePragmazno-cacheExpires0)send_headerrd   end_headers)rg   rj   s    r   rq   Handler.end_headers   s?    *OP:.C(r   c                   >  [        US   5      nUS:  a  [        TU ]  " U/UQ76   g g ! [        [        4 a    Sn N-f = f)Nr   r   i  )int
IndexError
ValueErrorrd   log_message)rg   fmtrh   statusrj   s       r   rw   Handler.log_message   sP    	a\F S=G+d+  J' 	F	s   * ?? )
__name__
__module____qualname____firstlineno____doc__re   rq   rw   __static_attributes____classcell__)rj   s   @r   r`   r`      s    8
D, ,r   r`   c            
        [         R                  " SS9n U R                  SSSS9  U R                  SSSS9  U R                  S	[        [        [        R
                  R                  S
S5      5      S9  U R                  SSS9  U R                  5       n[        5       n[        X!R                  5        [        5       u  p4[        U5        SR                  S US   R                  5        5       5      n[        SU SU 3[         R"                  S9  U(       a  UR                  (       d  [%        XB5        U(       aI  [        S['        U5       S3[         R"                  S9  U H  n[        SU 3[         R"                  S9  M      UR(                  (       a%  [        SU(       a  SOS-   [         R"                  S9  g [+        UR,                  UR.                  4[0        5      nSUR,                   SUR.                   S3n[        S[2         S U 3[         R"                  S9   UR5                  5         g ! [6         a+    [        S![         R"                  S9  UR9                  5          g f = f)"Nz3Holtburger dev web server (single-root, validated).)descriptionz--check
store_truez4validate + write _health.json, then exit (no server))actionhelpz--allow-missingz%serve even if a baked layer is absentz--portPORT8765)typedefaultz--bindz	127.0.0.1)r   z  c              3  R   #    U  H  u  pU S US   (       a  US   OS 3v   M     g7f)=r:   r;   MISSINGNr{   )r   rL   infos      r   r   main.<locals>.<genexpr>   s1      2JD &4	?$w-	BC2s   %'r?   z[serve] root=z
[serve] layers: r*   z[serve] --allow-missing: z# required layer(s) absent, ignored:z[serve]   - z[serve] --check OKz (with --allow-missing)z: all required layers present.zhttp://:z/apps/holtburger-web/index.htmlz[serve] serving z# (threaded, no-cache)
[serve] open z
[serve] shutting down.)argparseArgumentParseradd_argumentrt   r   r   r   
parse_argsr   r8   r6   rN   rW   rZ   itemsr-   r.   r/   r0   lencheckr   bindportr`   rf   serve_foreverKeyboardInterruptshutdown)	aprh   r   rM   rJ   summaryr^   httpdurls	            r   mainr      s   		 	 -b	cBOOIl9oOpOO%lAhOiOOH3BJJNN664R0SOTOOHkO2==?DD001#~F ii  *002 G 
M$1'
;#**M** )#h-8[\cfcmcmnDL'cjj9  zz"8&?Qqry|  zD  zD  	EDII 6@EDII;a		{*I
JC	YK'KC5
QX[XbXbc (szz:s   H0 02I%$I%__main__)returnr   )r$   r   r   rt   )r$   r   r   bool)r   r   r6   r   r   None)rM   dictr   r   )rJ   z	list[str]r   r   r   r   )r   r   )!r   
__future__r   r   rE   rT   r   r.   http.serverr   r   pathlibr   __file__resolveparentrf   r1   r   REQUIRED_FILErA   rB   rD   r   r%   r(   r8   rN   rW   r0   r`   r   r|   r{   r   r   <module>r      s   (T #    	 
 E 
 N""$++22		1
 !!/: 
 />8:D4,& ,2'T zF r   