
    5/j:                       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	  \
" SS5      r\
" SS5      rS	r\R                  R                  S
5      =(       d$    \R                  R                  S5      =(       d    Sr\R"                  R%                  \S5      rSrSS jrSS jrSS jrSS jrSS jrSS jr    SS jr S      S!S jjrS"S jr\S:X  a  \R>                  " \" 5       5        gg)#u  
Phase D.1.a — stage the 13x13 ring's ACE landblock_instance spawn records.

Mirrors the Phase C.1 scenery-bake staging pattern (one JSONL per LB,
hex-named, plus a bake-source sha256 sidecar). Output layout:

  /mnt/wbterminal1/holtburger-dist-v2/spawns/
    source.sha256                    # sha256 of input JSONL
    0xA9B4.spawns.jsonl              # 106 records for Holtburg
    0xA9B0.spawns.jsonl              # 13 records for South Outpost
    ...                              # one per LB in the 13x13 ring
    README.md                        # schema notes

Empty JSONL files are emitted for LBs with zero ring matches — this
gives the runtime an unambiguous "this LB has been queried, zero
spawns" signal (vs 404 "this LB hasn't been baked yet").

Filter range (matches the Phase C ring driver):
  landblockId in {(x<<8)|y for x in 163..=175, y in 174..=186}

Run:
  python3 stage-ring-spawns.py
  python3 stage-ring-spawns.py --source /path/to/ace_spawn_records.jsonl
  python3 stage-ring-spawns.py --out /path/to/spawns

Determinism contract: byte-identical output across runs given the same
input. We sort records within each LB by (cell, x, y, z, wcid) so the
JSONL output doesn't drift on the JSONL line order in the source file.
    )annotationsN)Path            z=/home/wbterminal/projects/RetailSmoke/ace_spawn_records.jsonlHOLTBURGER_DISTHOLTBURGER_DIST_V2z /mnt/wbterminal2/holtburger-distspawnsz8/home/wbterminal/projects/RetailSmoke/weenie_index.jsonlc                    SU S 3$ )z5Render `landblockId` (e.g. 43444 = 0xA9B4) as 0xXXXX.0x04X )landblock_ids    n/home/wbterminal/WorldBuilder-ACME-Edition/external/holtburger/scripts/world-completeness/stage-ring-spawns.pylb_hexr   =   s    S!""    c                 |    [        5       n [         H'  n[         H  nU R                  US-  U-  5        M     M)     U $ )zBuild the 169-element ring set.   )setRING_X_RANGERING_Y_RANGEadd)outxys      r   ring_lb_setr   B   s8    ECAGGQ!VqL!   Jr   c                  ^ [         R                  " 5       nU R                  S5       m[        U4S jS5       H  nUR	                  U5        M     SSS5        UR                  5       $ ! , (       d  f       UR                  5       $ = f)z;SHA-256 of a file's bytes (used for source.sha256 sidecar).rbc                 &   > T R                  S5      $ )Ni   )read)fs   r   <lambda>sha256_file.<locals>.<lambda>O   s    !&&/r   r   N)hashlibsha256openiterupdate	hexdigest)pathhchunkr"   s      @r   sha256_filer.   K   sb    A	4A137EHHUO 8 
 ;;= 
 ;;=s   )A))
Bc                v   U S   U R                  SS5      U R                  SS5      U R                  SS5      U S   U R                  SS5      U S	   U S
   U S   [        U R                  SS5      5      [        U R                  S0 5      R                  SS5      5      S.nU R                  S5      nUb  U R                  SS5      nU R                  SS5      nU R                  SS5      n[        US-
  5      S:  a-  [        U5      S:  a  [        U5      S:  a  [        U5      S:  d  X!S'   X1S'   XAS'   XQS'   U$ )u  Project a source JSONL record to the wire-shape we stage.

Pulls only fields the renderer's synthetic-spawn injector reads.
Coordinates are LB-local metres (same convention as
LandblockInfo.objects + scenery bake).

Orientation: when the source carries explicit per-axis quaternion
components (`qw/qx/qy/qz` — added by the angle-merge from ACE
`landblock_instance.angles_*`), we emit them so the wasm
`EntitySpawnJsonRaw` parser (optional qw/qx/qy/qz, `#[serde(default)]`)
and `scene3d/spawns.js::buildUpd` render the REAL rotation. Identity
placements (and pre-merge sources that only ship `{isIdentity}`) omit
the quat — the parser defaults to qw=1 — keeping files lean and
backward-compatible.
wcidname category
weenieTyper   landblockIdcellr   r   zisServerManagedTorientation
isIdentityF)r0   r1   r3   r4   r5   r6   r   r   r7   r8   orientationIsIdentityqwqxg        qyqzg      ?gư>)getboolabs)recr   r<   r=   r>   r?   s         r   normalise_recordrD   T   s2   " F#GGJ+gglA.=)"XXX(94 @A!%cggmR&@&D&D\SX&Y!ZC 
B	~WWT3WWT3WWT3 BH$R4GdNs2w~IIIIJr   c           	        UR                  S S9  U [        U5       S3-  nUR                  S5       nU H8  nUR                  [        R
                  " USS95        UR                  S5        M:     SSS5        [        U5      nUR                  UR                   S	3-  R                  US-   5        g! , (       d  f       NH= f)
zWrite one LB's JSONL, sorted deterministically.

Sort key: (cell, x, y, z, wcid). Stable across re-runs given the
same source JSONL. The empty-file invariant: every LB in the ring
gets a file, even when records is [].
c                .    U S   U S   U S   U S   U S   4$ )Nr6   r   r   r7   r0   r   )rs    r   r#    write_lb_jsonl.<locals>.<lambda>   s#    &	1S61S61S61V9Mr   )keyz.spawns.jsonlwT)	sort_keys
Nz.sha256)
sortr   r'   writejsondumpsr.   parentr1   
write_text)out_dirlbrecordsr+   r"   rG   shas          r   write_lb_jsonlrW      s     LLMLNr
|=11D 
31AGGDJJqD12GGDM  
 d
C	[[dii[((44S4Z@ 
s   ?B44
Cc                0    SnU S-  R                  U5        g)uC   Schema doc — explains what the JSONL means and how to consume it.u5  # Phase D.1 — staged ACE spawn records for the 13x13 Holtburg ring

Each `0xXXXX.spawns.jsonl` file contains the ACE `landblock_instance`
spawn records for one landblock, filtered from the world-wide dump at
`/home/wbterminal/projects/RetailSmoke/ace_spawn_records.jsonl`.

## Ring

13 x 13 landblocks centred on Holtburg (LB 0xA9B4 = cell_x 0xA9,
cell_y 0xB4). Mirrors the Phase C.1 scenery bake's ring.

## Schema (per JSONL line)

```jsonc
{
  "wcid":          7978,              // ACE weenie classification ID
  "name":          "Scrawed Grievver",
  "category":      "Creature",         // Creature, Object, NPC, ...
  "weenieType":    10,                // ACE WeenieType enum
  "landblockId":   43444,             // (cell_x << 8) | cell_y
  "cell":          0,                 // intra-LB cell index (0 = outdoor)
  "x":             130.239,           // LB-local metres
  "y":             104.9,
  "z":             46.005,
  "isServerManaged": true,            // ACE manages lifecycle (vs DAT static)
  "orientationIsIdentity": false      // true => use identity quat
}
```

## Notes

- **Orientation is dropped.** The source JSONL has only
  `orientation: {isIdentity: bool}` — no per-axis quaternion
  components. The injector emits identity quat (qw=1). A future
  re-run of the dumper can include full quaternion fields without
  schema breakage.
- **Empty files are intentional.** 125 of 169 LBs in the ring have
  zero spawns. The runtime treats "empty body" as
  "queried, zero spawns" (not 404 "not yet baked").
- **wcid alone doesn't render an entity.** The renderer needs a
  `setupDid` (`csetup_id`) — we resolve it via the weenie_index at
  injection time, not in this stage.
- **The wire-position injector mirrors handleEntitySpawn(upd).** See
  `scene3d/spawns.js::ensureSpawnsForLandblock`.

## Reproducibility

```sh
python3 stage-ring-spawns.py
```

Deterministic given the same input JSONL (records sorted by
`(cell, x, y, z, wcid)` within each LB). The output `source.sha256`
covers the input JSONL so a manifest consumer can verify it has the
same spawn snapshot.
z	README.mdN)rR   )rS   contents     r   write_readmerZ      s    7Gp {&&w/r   c                   UR                  5       (       d  SS[        U5      S.$ 0 nUR                  5        nU Hc  nUR                  5       R	                  S5      nU(       d  M+   [
        R                  " U5      nUR                  S5      nU(       d  M\  XsUS   '   Me     SSS5        0 n/ n	[        U5       H;  n
UR                  U
5      nU(       a  Xx[        U
5      '   M*  U	R                  U
5        M=     U S-  R                  [
        R                  " USS	S
95        [        U5      U	SS.$ ! [
        R                   a     GM  f = f! , (       d  f       N= f)uv  Stage a `wcid_to_setup.json` mapping for the ring's wcids.

The renderer's synthetic injector reads this to resolve a SetupModel
DID (0x02xxxxxx) for each spawn record — the JSONL itself doesn't
carry the setup_id. Filtered to ring wcids only so the file stays
tiny (~4 KB for the 13x13 ring vs ~1 MB for all 43k weenies).

Returns a stats dict (entry counts, miss list).
Tr   )missing_inputentriesmissing_wcids   ﻿setupDidr0   Nzwcid_to_setup.json   )rK   indentF)r]   r^   r\   )existslistr'   striplstriprO   loadsJSONDecodeErrorr@   sortedstrappendrR   rP   len)rS   
ring_wcidsweenie_index_pathwcid_to_setupr"   linerC   setupout_mapmissingrJ   s              r   write_wcid_to_setuprt      s>    ##%%!%!d:FVWW$&M				!QD::<&&u-Djj& GGJ'Eu-2c&k*  
" !GGJ!!!$#CFONN1   ##//

7d15
 w<  ' ''  
"	!s5   .E
&D.<E
E
.EE
EE


Ec                0   U R                  5       (       d  [        SU  35      eUR                  SSS9  U(       a  SO	[        5       nU(       a  [        5        Vs0 s H  oU/ _M     snOU Vs0 s H  oU/ _M     snn[	        5       nSnSn	U R                  5        n
U
 H  nUR                  5       R                  S5      nU(       d  M+  US-  n [        R                  " U5      nUR                  S5      nUb  XT;  a  Mb  O)[        U[         5      (       d  Mz  UR#                  U/ 5        U	S-  n	Xe   R%                  ['        U5      5        UR)                  US   5        M     SSS5        SnSn[+        UR-                  5       5       H&  nXe   n[/        XU5        U(       a  US-  nM!  US-  nM(     [1        U 5      n[3        XU5      nUc  SOSnUc  [5        U5      O
[5        U5      nUc  SOLS[6        R8                   S[6        R:                  S-
   S[<        R8                   S[<        R:                  S-
   S3	nUS-  R?                  U R@                   SU SU S3U-   SU SU SU SU	 S[5        U5       SUS    S[5        US   5       S3-   5        [C        U5        UU	U[5        U5      UUUUS   US   S .	$ s  snf s  snf ! [        R                   a*  n[        SU S	U 3[        R                  S
9   SnAGMa  SnAff = f! , (       d  f       GN= f)!a  Read source JSONL, partition by LB into per-LB files, write sha256.

Ring mode (default): keep only the 13x13 Holtburg ring, pre-seeding an
empty file for every ring LB. World mode (`all_world=True`): keep EVERY
landblock present in the source, emitting a file only for LBs that have
spawns (unpopulated LBs 404 -> the loader fail-softs to "no spawns",
which is correct). Returns a stats dict for logging.
zFAIL: source missing: T)parentsexist_okNr   r_      zWARN: bad json on line z: )filer5   r0   worldringr2   zring-x-range	z..=z
ring-y-range	rL   zsource.sha256	z4
bake-tool-version	stage-ring-spawns.py/0.2.0
scope	z	lb-count	z
populated-lbs	z
empty-lbs	z
total-records	z
unique-wcids	z
wcid-to-setup-entries	r]   z
wcid-to-setup-missing	r^   )	
total_seen
total_kept	ring_sizerm   	populatedemptysource_sha256wcid_to_setup_entrieswcid_to_setup_missing)"rc   
SystemExitmkdirr   r   r'   re   rf   rO   rg   rh   printsysstderrr@   
isinstanceint
setdefaultrk   rD   r   ri   keysrW   r.   rt   rl   r   startstopr   rR   r1   rZ   )source_pathrS   rn   	all_worldr{   rT   per_lbrm   r}   r~   r"   rp   rC   er   r   recssrc_sha
wcid_statsscopelb_countrange_liness                         r   stage_spawnsr     s_    1+?@@MM$M.4+-D -6+-(-BR-(T;RTrFT;R  5JJJ				qD::<&&u-D!OJjj& 'B> " ""c**!!"b)!OJJ.s34NN3v;')  
0 IEV[[]#zwD)NIQJE $ +&G$W:KLJ|GE"ls6{D	H < 	 \//0L4E4E4I3J K)//0L4E4E4I3J"N	  **
Bwi (	 	 xj !# %W $ &Z) *"",Y"7!8 9""%j&A"B!C2G	
	G  ! *o !+I!6!+O!<
 
Q 	);R '' /
|2aSA

S 
	sC   J;-K 3LK$A>LLK>7L>LL
Lc            	     J   [         R                  " [        S9n U R                  S[        S[         S3S9  U R                  S[
        S[
         S3S9  U R                  S[        S	[         S3S9  U R                  S
SSS9  U R                  5       n[        UR                  5      n[        UR                  5      n[        UR                  5      n[        X#XAR                  S9n[        S5        [        S5        [        SU 35        [        SU 35        [        SUS    35        [        SUS    SUS    S35        [        SUS    35        [        SUS    35        [        SUS    35        [        SUS     S![        US"   5       S35        [        S#US$    35        g%)&N)descriptionz--sourcezInput JSONL path (default: ))defaulthelpz--outzOutput dir (default: z--weenie-indexzWeenie index JSONL (default: z--all-world
store_truezStage EVERY landblock present in the source (whole world), not just the 13x13 Holtburg ring. Emits a file only for LBs that have spawns.)actionr   )r   u$   Phase D.1.a — staged spawn recordsz!=================================zsource            : zout               : zrecords scanned   : r}   zrecords kept      : r~   z  (ring=r   z  populated LBs   : r   z  empty LBs       : r   zring unique wcids : rm   zwcid_to_setup ents: r   z  (missing=r   zsource.sha256     : r   r   )argparseArgumentParser__doc__add_argumentDEFAULT_SOURCEDEFAULT_OUTDEFAULT_WEENIE_INDEX
parse_argsr   sourcer   weenie_indexr   r   r   rl   )apargssrcr   r   statss         r   mainr   p  s   		 	 W	5BOOJ6~6FaH  JOOG[0Q?  AOO$.B89M8NaP  ROOM,?  @ ==?D
t{{
C
txx.C))*L<>>JE	02	-/	 
&'	 
&'	 |!4 5
67	 |!4 5XeK>P=QQR
ST	 {!3 4
56	 w 0
12	 |!4 5
67	 '>!? @ A% 789:!= >	 !7 8
9:r   __main__)r   r   returnrj   )r   set[int])r+   r   r   rj   )rC   dictr   r   )rS   r   rT   r   rU   z
list[dict]r   None)rS   r   r   r   )rS   r   rm   r   rn   r   r   r   )F)
r   r   rS   r   rn   r   r   rA   r   r   )r   r   ) r   
__future__r   r   r%   rO   osr   pathlibr   ranger   r   r   environr@   r	   r+   joinr   r   r   r   r.   rD   rW   rZ   rt   r   r   __name__exitr   r   r   <module>r      s  < #    	 
  S#S#P
 JJNN$% *	zz~~*+*) 
 ggll?H5Q #
*ZA2:0z-,0-59-b >Cf%)f6:fGKfRD zHHTV r   