@wildshard/shard-format 0.0.1 → 0.4.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (748) hide show
  1. package/corpus/brasscap-mesa/blobs/bin/02da0052111419cdff3a29f1c6d0ff0d223e0b53a956c16c65ed63d87928b3b9.bin +0 -0
  2. package/corpus/brasscap-mesa/blobs/bin/068a121cd780a2a9164416f6e4fde44834f2d2f3a5ed65c174e480b7a2d3f627.bin +0 -0
  3. package/corpus/brasscap-mesa/blobs/bin/07d5a061cd338618e528747d61eb2b17709510550ef0ba40a58b67761b200f3f.bin +0 -0
  4. package/corpus/brasscap-mesa/blobs/bin/086e3706070555baca863f6ecf9dbca09c084afa501d92d4246d896b09546f36.bin +0 -0
  5. package/corpus/brasscap-mesa/blobs/bin/08b1b9ab7c732d7dbe8f961f1fc40e84042d1fe42544f55c9ee89f04dffe3d5e.bin +0 -0
  6. package/corpus/brasscap-mesa/blobs/bin/18bdbdc704cb8aac93435d208dfe0e9a44374efb73afa3bc58c631650e0a2995.bin +0 -0
  7. package/corpus/brasscap-mesa/blobs/bin/1a210fcddcb9749ecb1c244837e8e37ea69b038faae191dee6bbbde228d31a18.bin +0 -0
  8. package/corpus/brasscap-mesa/blobs/bin/264ab9ede386504a5ff9814b0a620b879cb29f3f45a80bcfc0c80b8ee236e364.bin +0 -0
  9. package/corpus/brasscap-mesa/blobs/bin/2b6fbea4c846a23cf4802ebdb1804e128896c8dd87e5d23509db6729e5b1be4e.bin +0 -0
  10. package/corpus/brasscap-mesa/blobs/bin/2d755cc635dee90b0f3958272855a821e2dd605a189a5ffcd480a325155fe84f.bin +0 -0
  11. package/corpus/brasscap-mesa/blobs/bin/2d76c6829fe77fb55cc2541e4aca5ce5d18b68508dcdf3fa957fe4a83b9c887a.bin +0 -0
  12. package/corpus/brasscap-mesa/blobs/bin/32c15d55a4548501740593807c5007217c72dbc10a21f4613ef5e0cee0b90d1d.bin +0 -0
  13. package/corpus/brasscap-mesa/blobs/bin/3539b362cf3a61a79e92e113ebf8f255d0b25ec2d2edd816498879de831fa912.bin +0 -0
  14. package/corpus/brasscap-mesa/blobs/bin/3af38a2377bc89f6c6c82ee1e83df674efa2793db932b50a23bbce7ec9cd7941.bin +0 -0
  15. package/corpus/brasscap-mesa/blobs/bin/3c176a7a0d0850fdcee790ed2641216fc3d1cba025928de3d7784262bb9b6f4a.bin +0 -0
  16. package/corpus/brasscap-mesa/blobs/bin/3fb37e488aa81a64c997b3dd09794e988994c6ee3476c6d653ced1670888dd8e.bin +0 -0
  17. package/corpus/brasscap-mesa/blobs/bin/424dcc7b320317bf3f2c62c8f19bc80564035127eb38ab811e460934d57ea38c.bin +0 -0
  18. package/corpus/brasscap-mesa/blobs/bin/45cea35fab6bb06051a3286fca2307944850c7f9ed7aa89b7c24fafef28efda6.bin +0 -0
  19. package/corpus/brasscap-mesa/blobs/bin/499cc7863851f97d79dbfc64e76f9e6978894199184a0d80ebdca13066a8fa78.bin +0 -0
  20. package/corpus/brasscap-mesa/blobs/bin/4b7217644763b00c6ee6ae3f176c726914966fb791654a385c0638bb998b6dd2.bin +0 -0
  21. package/corpus/brasscap-mesa/blobs/bin/4cf11c7177bb082f335e5bc7a037ef00d09355c5ad8263e284a96a92630ff9ae.bin +0 -0
  22. package/corpus/brasscap-mesa/blobs/bin/536b37cab5a1edd6a68a71d122aab6898e44c68a5470a4ca8a9a72c57c02e034.bin +0 -0
  23. package/corpus/brasscap-mesa/blobs/bin/55d3628b27e0fcea383df86c434a69761439f51a013359f0829675b07ee2b616.bin +0 -0
  24. package/corpus/brasscap-mesa/blobs/bin/5652ba90fbd8146edaf42660329cc81798752f2f60b8d03956714460d5ba75ef.bin +0 -0
  25. package/corpus/brasscap-mesa/blobs/bin/57459ab5e2ac6cec65ef5e59789dd82b9666d8573df41c4a398df603f1fa456f.bin +0 -0
  26. package/corpus/brasscap-mesa/blobs/bin/5915b6aed21bba49b841b45574f44f60d26b44810bd68ac70b22f3f3b0dab072.bin +0 -0
  27. package/corpus/brasscap-mesa/blobs/bin/5c4a5c7a8e99253561105c5fa3faf14326cf0c23adc32bfe7f8aea4dbf4b05f9.bin +0 -0
  28. package/corpus/brasscap-mesa/blobs/bin/61822a412cfd461f4f7ae50bc13f242a0fca6c5c58fca72247b596ab037a1c85.bin +0 -0
  29. package/corpus/brasscap-mesa/blobs/bin/673b7712908966718341508469b39cd49e6461f690ca3c23d4621ea7eba0cc30.bin +0 -0
  30. package/corpus/brasscap-mesa/blobs/bin/69394a49520f84f6dccc1655f1c89eedc913ac53a7e48ac5dc1bff4141639bb4.bin +0 -0
  31. package/corpus/brasscap-mesa/blobs/bin/6e58a53a6c2acd50f9f80fd270ca034c54421df8a587e181101b4b75b952ac5d.bin +0 -0
  32. package/corpus/brasscap-mesa/blobs/bin/75dc9f23ee71e11a3a0c67eb77275d741a281c3398808fb42efd52b9973d5d04.bin +0 -0
  33. package/corpus/brasscap-mesa/blobs/bin/76c6a403293a68ed3d6ad14e9dd27b555c3a1ef4a3cae12a2bd2ab3e67c5ad02.bin +0 -0
  34. package/corpus/brasscap-mesa/blobs/bin/82b73ce3a18e47092840fc029177861887aa2867b007248ac79ffa20895a4491.bin +0 -0
  35. package/corpus/brasscap-mesa/blobs/bin/8430d9bbfb4e2b678c54595ad95555c3c73d0e1b144a4d6e29664ccb4371baf0.bin +0 -0
  36. package/corpus/brasscap-mesa/blobs/bin/848d4492508689f5c4a224079dca8809e872e33b8f2635c9f16da5dc21beed16.bin +0 -0
  37. package/corpus/brasscap-mesa/blobs/bin/84941ac387a0d245ea20bd663ff4ee78c5315bedfa058ada0961765aff61acaf.bin +0 -0
  38. package/corpus/brasscap-mesa/blobs/bin/859b7bbcf048a4525db0548b8f5afef83bfc4870914767153f44c05e83436ec5.bin +0 -0
  39. package/corpus/brasscap-mesa/blobs/bin/87b68b45680a57c796eed9183211b03335a6ad6737372b3ef476c2cf42efa631.bin +0 -0
  40. package/corpus/brasscap-mesa/blobs/bin/8cef94308af8d09670e4c99a3b6c3d9280b7a27d386653a3bd7d482b2cdf2b65.bin +0 -0
  41. package/corpus/brasscap-mesa/blobs/bin/8e564bce3f352c2186ae09ed7fbca9739c10abc46fd5f6893c68cb47178bd2b4.bin +0 -0
  42. package/corpus/brasscap-mesa/blobs/bin/8f89e79d0fb9b04d48c7b5719e1d024161c6a2119835b18dcb193ff51123a9be.bin +0 -0
  43. package/corpus/brasscap-mesa/blobs/bin/8fe511ad48656a0b10801100b9100edab1be605a8a182dc5c834d298bf0d9166.bin +0 -0
  44. package/corpus/brasscap-mesa/blobs/bin/900bda6562d06fafda54c495e325d57dbb7931813178ee207449c5519d89d537.bin +0 -0
  45. package/corpus/brasscap-mesa/blobs/bin/997b1b7d9210bfa77dc71f16a57c6bd66bc7c648686dc2a2666044c95042978a.bin +0 -0
  46. package/corpus/brasscap-mesa/blobs/bin/a111bb4c6cec536bcf57dddc744b871ac9f3d094ce459c16243adca19020db77.bin +0 -0
  47. package/corpus/brasscap-mesa/blobs/bin/a2208bf899e6c84942eef9fbab569d597f574551afae5864f1953c6a517af068.bin +0 -0
  48. package/corpus/brasscap-mesa/blobs/bin/a302ddb2598a72ece53780094b64ad861dc68e6913d8a1ea08f5d141736e2986.bin +0 -0
  49. package/corpus/brasscap-mesa/blobs/bin/afcbc671326d0dac14e460662c1f793335877ba53cc965f67c0306833b087e7c.bin +0 -0
  50. package/corpus/brasscap-mesa/blobs/bin/b657159e6a28c801a211024f96a7ef60e388ce928a7b41ae3d7555a6f8fd2e3e.bin +0 -0
  51. package/corpus/brasscap-mesa/blobs/bin/be6bbc6cd15ba525790075252ba0ed0b28dae5cfbfa118c00dfb9ea1ab95e668.bin +0 -0
  52. package/corpus/brasscap-mesa/blobs/bin/c1360e3cbe6b3dc8894bc66c359853854c2e5adeac9c7cd5ba63f5db448be601.bin +0 -0
  53. package/corpus/brasscap-mesa/blobs/bin/c7983018dbf8e384ddf73191f526075bd0b29907061ab17642ecdc18ea7b9897.bin +0 -0
  54. package/corpus/brasscap-mesa/blobs/bin/cea66f27ca60ea173ee0a2bcd91964dc2638eb5657250fe3aa5bd3e41faf8730.bin +0 -0
  55. package/corpus/brasscap-mesa/blobs/bin/d052be571a0f3ab69926951f3a4c9d63f05da2ca6d91e5963dd63e04ecb515de.bin +0 -0
  56. package/corpus/brasscap-mesa/blobs/bin/d162a993a60dd682c8c21dd111429d84d8d30413a000cf481cf98d06ca83e706.bin +0 -0
  57. package/corpus/brasscap-mesa/blobs/bin/d238c3d3d054911714ebe30dafc99e6ad13925b7a600ba9f8d532d0f515f3f5a.bin +0 -0
  58. package/corpus/brasscap-mesa/blobs/bin/d9a98cfe2865a9bb0e94f35c735a570b0625b58f890a6b1f6e14fab2401f0af2.bin +0 -0
  59. package/corpus/brasscap-mesa/blobs/bin/dc27ae954b7b8a13a9459d6ca6ef2997f662715ff5f5453ea0da169f141748b8.bin +0 -0
  60. package/corpus/brasscap-mesa/blobs/bin/e3ff1fd41cf76668b8e1927c0c2e4056b63a293ebbd300a8f1b1d27d87786271.bin +0 -0
  61. package/corpus/brasscap-mesa/blobs/bin/e45b7344d373b3ef4a94f560cf4e17b1eb3b8cd89d1e552fc9be11d6b7b546f0.bin +0 -0
  62. package/corpus/brasscap-mesa/blobs/bin/eb14eb898224d5d81d1012bec54f2b79f9377dc60fa1d01e49b4c3e148292d77.bin +0 -0
  63. package/corpus/brasscap-mesa/blobs/bin/ed0e0101a3d91a3a615f9e78ef36f7deb8bb0eab0f133241d621925d0b596599.bin +0 -0
  64. package/corpus/brasscap-mesa/blobs/bin/f722e3a3b1f0a3d7b9f7d7c66c46d8c87245de5f8eece055daf95c7086f8c267.bin +0 -0
  65. package/corpus/brasscap-mesa/blobs/bin/f76013aedfd810f136fa689a151024bcb942687ef42ae7edf1fd3513ac4e22c7.bin +0 -0
  66. package/corpus/brasscap-mesa/blobs/glb/0547e63693282614673b13bade4c08eb772020757b131b7c80f2077873aab70e.glb +0 -0
  67. package/corpus/brasscap-mesa/blobs/glb/0cf89e9b1824440d213c8e9e33fb357734b7241b4c7eedaba3df7f8306e05425.glb +0 -0
  68. package/corpus/brasscap-mesa/blobs/glb/0e01060e3a507ffff6461c50b5c565c71380a6a67d9760cdd7a09afd3e30b584.glb +0 -0
  69. package/corpus/brasscap-mesa/blobs/glb/0ef326c5cf5292cd15b48b851460ee8156f51e0a5b62457c8f1f3c53c41665a3.glb +0 -0
  70. package/corpus/brasscap-mesa/blobs/glb/1112ff4a3f69db3b62c6d2d6ae021e7c43ce0ca1feb1d3130376881decb3c6b0.glb +0 -0
  71. package/corpus/brasscap-mesa/blobs/glb/11357cb8a0670fdc9a03780dd79a31e0aa7a0537786cbf66491bae1722d6feb3.glb +0 -0
  72. package/corpus/brasscap-mesa/blobs/glb/11dbecc73bd343c375bd4b79db8dbda2272e76a6b8426ac29e90f2b1272ccfc2.glb +0 -0
  73. package/corpus/brasscap-mesa/blobs/glb/133f639cfa837bc5da7915121c71fd32281660c2aaa0248bd59bde47e7faf8bc.glb +0 -0
  74. package/corpus/brasscap-mesa/blobs/glb/16812a090136b6678633aa4db03e4187920ae0aca776ad6da373c600cd341972.glb +0 -0
  75. package/corpus/brasscap-mesa/blobs/glb/17e63980d7934cfe1d2823d78652af338fe9f0c2170106cd4506a2c4e7f4716f.glb +0 -0
  76. package/corpus/brasscap-mesa/blobs/glb/199224cafc86904bdefec8f01d131d7ca3e076a22b689bfc6a26dd20373cf286.glb +0 -0
  77. package/corpus/brasscap-mesa/blobs/glb/1a5c8cf223a2f64715ea5eaf14d97a0131f9ebdef4388dc4823dcfa57817724c.glb +0 -0
  78. package/corpus/brasscap-mesa/blobs/glb/1b0ec09ad6718149d7ca514136cee09b04c96425f03af0559fd097112aa0daa5.glb +0 -0
  79. package/corpus/brasscap-mesa/blobs/glb/1e5024bbb1284b2ed39bbca2d0ade3acb5be2a2c6ba643291f269b76098c7f79.glb +0 -0
  80. package/corpus/brasscap-mesa/blobs/glb/1e7635972e13468ffcbb1608f1d12559b066b4533912be2c35e3db07fd4a679d.glb +0 -0
  81. package/corpus/brasscap-mesa/blobs/glb/1f03db3a38a21b8d4d02f8ede30e1303cd8d4c426815326a88f45a268b17a492.glb +0 -0
  82. package/corpus/brasscap-mesa/blobs/glb/2624abb7bf45618be40f8c3221445179fca44f7321baff415fdf7ddd5e0afc6f.glb +0 -0
  83. package/corpus/brasscap-mesa/blobs/glb/27fc3bad6db6e2b5ade5d242867d882362482ddf19c647edef821afc27d52dc2.glb +0 -0
  84. package/corpus/brasscap-mesa/blobs/glb/29959bdb5b04a9180e88ec1ed976bae55424d3dc50985f13c0f85bae11ae79ee.glb +0 -0
  85. package/corpus/brasscap-mesa/blobs/glb/308cbce2b1b6360f9a2dc275d0bc28be1966fc3ff5e0a58844405ba867f3f668.glb +0 -0
  86. package/corpus/brasscap-mesa/blobs/glb/3674d7be969755aaffa368e57b8d1bb743bc98e766a57948859d54fdc7d2c066.glb +0 -0
  87. package/corpus/brasscap-mesa/blobs/glb/3736ca95c9c40b30e21302ae6c89148d8ac09264dfbb7e5764b08c265a73ba3c.glb +0 -0
  88. package/corpus/brasscap-mesa/blobs/glb/3bc90e1dc9411d249697e4ec352baa4886f91cacfe4940ac43aed879f60966c9.glb +0 -0
  89. package/corpus/brasscap-mesa/blobs/glb/3f519c7c7482307cae908b27602dcb7716e2da4b651eff39c4c37103998d18fa.glb +0 -0
  90. package/corpus/brasscap-mesa/blobs/glb/4767636d5554b60c94b447b87b77fc1456a2b9af42bfe08585389d27f306dd5e.glb +0 -0
  91. package/corpus/brasscap-mesa/blobs/glb/4a3242773b3a73a73ee746a0cdf30c8d6d904bb4406dfdf9822dcd535d6ae308.glb +0 -0
  92. package/corpus/brasscap-mesa/blobs/glb/4c7bf3788cba151442d85c3f76553c2e356edf8566ae8243d0a535c9bcde51d0.glb +0 -0
  93. package/corpus/brasscap-mesa/blobs/glb/4f3b9f4c818ed6d375947e9603dd058c539e14ff05aa193fb5054d899dbb82a2.glb +0 -0
  94. package/corpus/brasscap-mesa/blobs/glb/4fdf7ee67d12de1cd30c3c2bbd9409032dcab5feb3a7d3977b92c50b1d161afe.glb +0 -0
  95. package/corpus/brasscap-mesa/blobs/glb/5821f33aa00e9acd2f4c0a29129978ee7006a879d163ce1463ae476031bfabcf.glb +0 -0
  96. package/corpus/brasscap-mesa/blobs/glb/5a986c3e44fc686a2efe7648c3945bb7b51cf303918381383f21f898dd312cad.glb +0 -0
  97. package/corpus/brasscap-mesa/blobs/glb/5c32fc1de6d198ed606d3e8c3264319acdd950dab18e8b723fc6332bf2f56aea.glb +0 -0
  98. package/corpus/brasscap-mesa/blobs/glb/602ce4e8b00a0afbc1a894b65641e48d362ec834a559b9d97a4801473bb01da7.glb +0 -0
  99. package/corpus/brasscap-mesa/blobs/glb/6180f9f96167522d8cab4de1d52598cecee8d7ee042f48e41669c98cf61c19c3.glb +0 -0
  100. package/corpus/brasscap-mesa/blobs/glb/6205973ba63f7a554662ba05010d25131bed2d9656de422e2067ca54709d5b3c.glb +0 -0
  101. package/corpus/brasscap-mesa/blobs/glb/693e556f2165232e8a38b1b2d68676f235ece5705c564992cfa7731426f4d376.glb +0 -0
  102. package/corpus/brasscap-mesa/blobs/glb/6a46175438bec5527c281b577bd220b6badedf3c74266b997d39e596f83b8853.glb +0 -0
  103. package/corpus/brasscap-mesa/blobs/glb/6ca1bcad7ea053a259b7f9fc5a7ec388a4cd29d44391d93386ea8b1143d94c21.glb +0 -0
  104. package/corpus/brasscap-mesa/blobs/glb/6f9bc99656b611890d6acc76ee416f6222ca2d5f62167840b4216a21e020a4c8.glb +0 -0
  105. package/corpus/brasscap-mesa/blobs/glb/6fb1e7857996662233ab5314fc13f8aa30703f91c04542e352ec17f9dc4fc054.glb +0 -0
  106. package/corpus/brasscap-mesa/blobs/glb/7028ec39712143a7e9d09d2e1477282b654d0470102ca9d272ac82b23604cb21.glb +0 -0
  107. package/corpus/brasscap-mesa/blobs/glb/73af65ac86efa61099178f3e7075dcd26d389ee9b9aa3546c18dede10c3dfda1.glb +0 -0
  108. package/corpus/brasscap-mesa/blobs/glb/73b992b71bd3165ff7701ab1430c075cb5ba9b74e6363d24640377b1011ddc93.glb +0 -0
  109. package/corpus/brasscap-mesa/blobs/glb/7471261d4df59979ba41a622d93779b66ebecd50353ad433862e603ee65f8748.glb +0 -0
  110. package/corpus/brasscap-mesa/blobs/glb/76e9a3fcca962c2f900cf79dc1a0851840bd405dc11231f629f784adbcb69d23.glb +0 -0
  111. package/corpus/brasscap-mesa/blobs/glb/79d2d29e8cd8b642386b5f31429b39bc4d6f13e53107dda00ebc463abf80c79d.glb +0 -0
  112. package/corpus/brasscap-mesa/blobs/glb/7de66fcfffe4ff108510cbe6bebec44e28ab4b78e98e9c1a38466db5b81881f7.glb +0 -0
  113. package/corpus/brasscap-mesa/blobs/glb/811fa798cb81ea99488db98e3d3ddea815aac8f6e7493ad04d0176f603978678.glb +0 -0
  114. package/corpus/brasscap-mesa/blobs/glb/89b277f1636be220ff5658ff1f1c62efe14079d6e1f42efb373fbf0246801533.glb +0 -0
  115. package/corpus/brasscap-mesa/blobs/glb/905c5fe666d9f6174e987382e47d3f325c5696cf8792d7a4a1dd120a438b8270.glb +0 -0
  116. package/corpus/brasscap-mesa/blobs/glb/90efe3e05370ee853c0130b98ee82f00b86f7c7104e96b094715187fa49ac16f.glb +0 -0
  117. package/corpus/brasscap-mesa/blobs/glb/968a7c5cb13014bce00a29d43b344bd6943ff00ae2908abbc6b40a994f1172a1.glb +0 -0
  118. package/corpus/brasscap-mesa/blobs/glb/9a955b4e69dfa5b38f071099053c1a3684cf85ec2d346420de2853c61768f8cc.glb +0 -0
  119. package/corpus/brasscap-mesa/blobs/glb/9da2781216a2e482dd8a6ada5ebb43bcf48e0169430e787ac277559cb2ade19a.glb +0 -0
  120. package/corpus/brasscap-mesa/blobs/glb/9db02aab14a3b52463deadefbf4ba771809941e7fcb137c905955bdc80ec23bd.glb +0 -0
  121. package/corpus/brasscap-mesa/blobs/glb/a3fe1788cf5d7f260249c722f0e0695a4d55744d1b14be44122377147a296256.glb +0 -0
  122. package/corpus/brasscap-mesa/blobs/glb/a5c75af8f090d3c95fa46d2be49be5031138d7cd1bae234ae028edf717bf299e.glb +0 -0
  123. package/corpus/brasscap-mesa/blobs/glb/a771abd40bd337a464c1bd19f841b62e290124faa89b19ec50e85ab36bc740a5.glb +0 -0
  124. package/corpus/brasscap-mesa/blobs/glb/a94a2ad2ed463a47cdda117df25b06b3cf6be37ddac08594bc5f5d76c7237730.glb +0 -0
  125. package/corpus/brasscap-mesa/blobs/glb/ad434e4a6490e495994b25f7574a5847a9f7d9c2ca5a2b229a9dfdd2e5e2bb72.glb +0 -0
  126. package/corpus/brasscap-mesa/blobs/glb/aeae7b05404b852008a7897689e58aacef44a2c3bc0e87d2519ecdb4cb1e3ff5.glb +0 -0
  127. package/corpus/brasscap-mesa/blobs/glb/b0c0c8a4985f92be3b358a8e703c67957427501882aeee05b4ccd3c7880930d4.glb +0 -0
  128. package/corpus/brasscap-mesa/blobs/glb/b25174c3cd13eea6e1d9c3104ee5197dc6330129b2f0f7676e8a429900999d9b.glb +0 -0
  129. package/corpus/brasscap-mesa/blobs/glb/b3eecdec571df96338edf690443394ff859383b4c94f4cf33d42c240006f8543.glb +0 -0
  130. package/corpus/brasscap-mesa/blobs/glb/b7aa0c61ff9e7411535c7b7b8e587849a92bad00d431171ab16ad47d55db0a53.glb +0 -0
  131. package/corpus/brasscap-mesa/blobs/glb/b9deef944c22331bc05220d1d6e46412e3cb1246ef041a4405d0cbbd90c2d624.glb +0 -0
  132. package/corpus/brasscap-mesa/blobs/glb/ba9376e4ac39b809133ab717edb4c88b5219118766db492d7c97534ce119c2ec.glb +0 -0
  133. package/corpus/brasscap-mesa/blobs/glb/baf338e9c272158b905fb58a712e29410c657a45be2764f1c6c43ca87fd1db48.glb +0 -0
  134. package/corpus/brasscap-mesa/blobs/glb/bd442ac9fbe52b9f2e4ba586670017c3f2d9fedf1cbdec77ef24e9f8b3a183c8.glb +0 -0
  135. package/corpus/brasscap-mesa/blobs/glb/c142adcf253be43b66f23ede8cea1e8fcfa256ebf53054a1068ace04745718a6.glb +0 -0
  136. package/corpus/brasscap-mesa/blobs/glb/c6d7cbdc4b2ee9d416edf4d736a4f374f08aea98ca395ad371a50a1853744cfc.glb +0 -0
  137. package/corpus/brasscap-mesa/blobs/glb/cfb165a7675d15cee2c490b31095a58f73bace50ccc1e0fdf2c8dc4400f1df27.glb +0 -0
  138. package/corpus/brasscap-mesa/blobs/glb/d40f2ee59612473e9f687f17a0e203fdb4ba54c7185b05a8d81a433b04f24134.glb +0 -0
  139. package/corpus/brasscap-mesa/blobs/glb/d50ce95411156a6b6cf386aefa5ee58db659811813f74be74f323e7c5795056f.glb +0 -0
  140. package/corpus/brasscap-mesa/blobs/glb/d6af418bbde39b4fc7d22a61401d416be7a3fc1214ea2d3f42b4549c392d22ca.glb +0 -0
  141. package/corpus/brasscap-mesa/blobs/glb/de7222ceeb1f72693dfb7fd1f343798659e03772d77f2b498fd8ebece2d5550a.glb +0 -0
  142. package/corpus/brasscap-mesa/blobs/glb/e26975b198127ac3b6b4f5c4bc08ad47d6691398feee777743285391cd36d502.glb +0 -0
  143. package/corpus/brasscap-mesa/blobs/glb/ea428490cd5f2672676a2776a598b819b08246871e76de94b7196f9643144986.glb +0 -0
  144. package/corpus/brasscap-mesa/blobs/glb/eb96567a266d24be188cb5b08021112f2fad311cbead939d53c5565da11f6265.glb +0 -0
  145. package/corpus/brasscap-mesa/blobs/glb/ecf66230bf7b68050c0827c2e4caf3de90e46859d291b794faa0dd3816d9dac6.glb +0 -0
  146. package/corpus/brasscap-mesa/blobs/glb/ed8f067763a4a8adb83d80c8324b2a9668f1061f5f5352f21295e6cc5132386f.glb +0 -0
  147. package/corpus/brasscap-mesa/blobs/glb/ee507a38772865467736d370791fe73c6aa4d83fe3e7ff4baa156b9cb2894c1c.glb +0 -0
  148. package/corpus/brasscap-mesa/blobs/glb/eedc5b62dc6f27d4a089e9f5b282f626129236d3f1689b001e04f8748bf8859c.glb +0 -0
  149. package/corpus/brasscap-mesa/blobs/glb/f4119b9c6c23435619682b5d247743459e76584b9b0d6c5cb689d8e156906695.glb +0 -0
  150. package/corpus/brasscap-mesa/blobs/glb/f576047267a0bf9bd4bfc47072ea3d11066345b839953546292da678d1cb4722.glb +0 -0
  151. package/corpus/brasscap-mesa/blobs/glb/f8cdfb11997d4ccabea88bcdd3924cc8b621b33618b18fe5fbfd286edfc1d87e.glb +0 -0
  152. package/corpus/brasscap-mesa/blobs/glb/fce95ecc5db24c903bd46f338e366e2715411465a5efac180e2f0f46397e59de.glb +0 -0
  153. package/corpus/brasscap-mesa/blobs/image/0579557f9344df6f2c4b1225e6252e53ab86d417a4c9b504b5c714a08a388e47.jpg +0 -0
  154. package/corpus/brasscap-mesa/blobs/image/9e18816a2ec16fbbb1f00f4a39c73ac2cbf6a8cdc17b4c96b1eba4cc9a754dc7.jpg +0 -0
  155. package/corpus/brasscap-mesa/blobs/image/eb453c71cfb1f9441eb00b32281f98e226e49d9dcf75c231455575fcdc652222.jpg +0 -0
  156. package/corpus/brasscap-mesa/blobs/json/e0072ed82115c519b891f53a194ea3b28fff7d3826d8ddf23460d6f21b7728d9.json +1 -0
  157. package/corpus/brasscap-mesa/blobs/json/ffb0e08eaaa7188db1f55931c9b6330937316091cc7463ed2eff6767bbfa33de.json +1 -0
  158. package/corpus/brasscap-mesa/blobs/wasm/1d6ace79610c4d510b60ad0c52915e42b57797d91f1ef292f60d8a3daec03dd6.wasm +0 -0
  159. package/corpus/brasscap-mesa/blobs/wasm/24a4d4c1292677b1c984b5c0d3497ae2929528bf511df475f80cc20945bb08ec.wasm +0 -0
  160. package/corpus/brasscap-mesa/blobs/wasm/b86d8afcc269fb063ebf64768fc3787635b65a7409a571115b07be2e4da39733.wasm +0 -0
  161. package/corpus/brasscap-mesa/cook.json +1 -0
  162. package/corpus/brasscap-mesa/mounts/shardfiles/brasscap-mesa/discovery.json +1 -0
  163. package/corpus/brasscap-mesa/sections/actors/creatures.json +1 -0
  164. package/corpus/brasscap-mesa/sections/actors/crowds.json +1 -0
  165. package/corpus/brasscap-mesa/sections/actors/species.json +1 -0
  166. package/corpus/brasscap-mesa/sections/audio/audio.json +1 -0
  167. package/corpus/brasscap-mesa/sections/combat/encounters.json +1 -0
  168. package/corpus/brasscap-mesa/sections/combat/items.json +1 -0
  169. package/corpus/brasscap-mesa/sections/combat/strikes.json +1 -0
  170. package/corpus/brasscap-mesa/sections/economy/ledger.json +1 -0
  171. package/corpus/brasscap-mesa/sections/economy/loot.json +1 -0
  172. package/corpus/brasscap-mesa/sections/look/look.json +1 -0
  173. package/corpus/brasscap-mesa/sections/look/materials.json +1 -0
  174. package/corpus/brasscap-mesa/sections/quests/quests.json +1 -0
  175. package/corpus/brasscap-mesa/sections/sim/client-scripts.json +1 -0
  176. package/corpus/brasscap-mesa/sections/sim/hooks.json +1 -0
  177. package/corpus/brasscap-mesa/sections/sim/plumbing.json +1 -0
  178. package/corpus/brasscap-mesa/sections/sim/scripts.json +1 -0
  179. package/corpus/brasscap-mesa/sections/sim/sim.json +1 -0
  180. package/corpus/brasscap-mesa/sections/state/migrations.json +1 -0
  181. package/corpus/brasscap-mesa/sections/state/state.json +1 -0
  182. package/corpus/brasscap-mesa/sections/ui/card.json +1 -0
  183. package/corpus/brasscap-mesa/sections/ui/compendium.json +1 -0
  184. package/corpus/brasscap-mesa/sections/ui/level.json +1 -0
  185. package/corpus/brasscap-mesa/sections/ui/ui.json +1 -0
  186. package/corpus/brasscap-mesa/sections/world/collision.json +1 -0
  187. package/corpus/brasscap-mesa/sections/world/days.json +1 -0
  188. package/corpus/brasscap-mesa/sections/world/edge.json +1 -0
  189. package/corpus/brasscap-mesa/sections/world/movers.json +1 -0
  190. package/corpus/brasscap-mesa/sections/world/navigation.json +1 -0
  191. package/corpus/brasscap-mesa/sections/world/props.json +1 -0
  192. package/corpus/brasscap-mesa/sections/world/spawn.json +1 -0
  193. package/corpus/brasscap-mesa/sections/world/terrain.json +1 -0
  194. package/corpus/brasscap-mesa/sections/world/tiles.json +1 -0
  195. package/corpus/brasscap-mesa/sections/world/traversal.json +1 -0
  196. package/corpus/brasscap-mesa/sections/world/water.json +1 -0
  197. package/corpus/brasscap-mesa/sections/world/weather.json +1 -0
  198. package/corpus/brasscap-mesa/shard.json +1 -0
  199. package/corpus/brasscap-mesa/validation.json +1 -0
  200. package/corpus/handwritten/blobs/bin/027822f6ae9c374ce1ced851f2227cb9acb03077e5cc6b7fecb245c31f021abd.bin +0 -0
  201. package/corpus/handwritten/blobs/bin/0377ce182bd604bcf4d501f14bf954c434ea64ad856ad9d71c502d1045612703.bin +0 -0
  202. package/corpus/handwritten/blobs/bin/05ad93d239eeeb5d9afa09b00d90c56098d88e8850e5b5e691fcaa7520dec3cd.bin +0 -0
  203. package/corpus/handwritten/blobs/bin/0c8980141e6e394ea473df1eb11c347ba617a1a9a4b39b3fa1871b5d15b97d87.bin +0 -0
  204. package/corpus/handwritten/blobs/bin/14eb11f8d809320e0a68b1edea7837d7a5b4a3da71f1ce4f54068e0afe0b039b.bin +0 -0
  205. package/corpus/handwritten/blobs/bin/16590066c039ac71e5038a9866a53ccb95e21af13db29cb655b379c4fcfef7bb.bin +0 -0
  206. package/corpus/handwritten/blobs/bin/1795b5147ad52482445d2414ee22823b947c43a917b6e96fd9ec017f17000912.bin +0 -0
  207. package/corpus/handwritten/blobs/bin/18f2e24ecc1ed1b1d12ae1c89732a5a1a618ab6e973d4ff0332cf9ecc4e4f4a6.bin +0 -0
  208. package/corpus/handwritten/blobs/bin/1a18ade01095f1a9ebe75650127861ef27dda0bb17c2b91bde057ec88ef2e98a.bin +0 -0
  209. package/corpus/handwritten/blobs/bin/1dba017cddb874bc147efb00c55418846ffa344a696f4df702bbabeea55034d0.bin +0 -0
  210. package/corpus/handwritten/blobs/bin/248c703537e4687ddd2b69a40d1ef3936da718626289d6867ce0a6866f562ba4.bin +0 -0
  211. package/corpus/handwritten/blobs/bin/2612d9fd4727f8c349f2dbfb7b1650bb7a752d0798a4966ac22cf2b6cdd9fcef.bin +0 -0
  212. package/corpus/handwritten/blobs/bin/28c54c766e90e3b046a2377b9fa015b0ce45cf3da4420f2b10553251f86c06aa.bin +0 -0
  213. package/corpus/handwritten/blobs/bin/30866b4807b12579f50218f6525c3db00919fbd43c97921daf47b3244b25dca6.bin +0 -0
  214. package/corpus/handwritten/blobs/bin/3d14fae85d77406a9b8c59f2e973cdae9587d49108ea92fb460679ca860196b8.bin +0 -0
  215. package/corpus/handwritten/blobs/bin/3f2201a53c441abcfb52089fe9bbb01f26fac8744099d3fb6668452885c2fa11.bin +0 -0
  216. package/corpus/handwritten/blobs/bin/42f270a99367fc4cc901518ccaa344ab595e265f113be944510da745c11f69d1.bin +0 -0
  217. package/corpus/handwritten/blobs/bin/43fd4327fedd51a586684ac611bcad4a8b9d62e51e03967de71fb42c9bf0f6e1.bin +0 -0
  218. package/corpus/handwritten/blobs/bin/484adda0898a881b6f095dad447f6a619bf0933f65efe940b72f1ab7f3630c22.bin +0 -0
  219. package/corpus/handwritten/blobs/bin/4a38cd49e8797429ce76265c3d8972195d5b0a02a35fc05fc513eab11240f415.bin +0 -0
  220. package/corpus/handwritten/blobs/bin/4fe636237bbbd4f836b0816bc61702f4c224d4cb91fbd94f244c731e24a1b945.bin +0 -0
  221. package/corpus/handwritten/blobs/bin/51b1cff782dc7ebb382bc429ee965023d4cdb52c7b3e37d6ffff5ef55c1c2813.bin +0 -0
  222. package/corpus/handwritten/blobs/bin/52561f02071ecaa98432fab5e8ece407a8e8f733a01f8eb7c543d1efe02614da.bin +0 -0
  223. package/corpus/handwritten/blobs/bin/556d6333923afea9641c4738741a41d6a46ae28a508288b9a49a749a344c1398.bin +0 -0
  224. package/corpus/handwritten/blobs/bin/5caeb97f32c7f31e1029eebdf31131f879f781bb5e3a225206a646710d83fd75.bin +0 -0
  225. package/corpus/handwritten/blobs/bin/5d1131e42a045083225105b640ff72e02c7f353ee6285545ac264d01bea26b1b.bin +0 -0
  226. package/corpus/handwritten/blobs/bin/5d80b30eee9f8342a92f6756fd0d84fb9d2d2ce994d222627641e584ad45d2a8.bin +0 -0
  227. package/corpus/handwritten/blobs/bin/5e0f9f7f2a85abe11a0a61ede853c9b9942b4494c3eb9943785cf6ae221e40e3.bin +0 -0
  228. package/corpus/handwritten/blobs/bin/5fdc5485343bba8201185c9bae862ad8618498ab5ce63b53514e4e0226fa7bdb.bin +0 -0
  229. package/corpus/handwritten/blobs/bin/6623dcb0b31c9ca0be342f6dca6b9e607ec3c12ff82068fa6030c142c2df77f7.bin +0 -0
  230. package/corpus/handwritten/blobs/bin/6887131a04175edbd1989aa95e8749d1703e7f2d0b8464e85f6bad28d6964125.bin +0 -0
  231. package/corpus/handwritten/blobs/bin/6cf89771d998486cad9049e6f4fc177d742a18ad79fafa4596814ff9d3cf1952.bin +0 -0
  232. package/corpus/handwritten/blobs/bin/74bbb0fc219e8476cd5550014a357ee18abc981ff3fe787ae3e60d4593705ec5.bin +0 -0
  233. package/corpus/handwritten/blobs/bin/76784a7f519ac43ce113a0c9365881839b75f66f96aa009fe8d3c8ab539ef283.bin +0 -0
  234. package/corpus/handwritten/blobs/bin/79a2f693ee2da546bb551f8b641b9c3affd2b8bfb5632ed7f417daba9d900af9.bin +0 -0
  235. package/corpus/handwritten/blobs/bin/7b593b0f9eefb957fb9d58b316d9836971203038d7c8127284bba44ccb3090bb.bin +0 -0
  236. package/corpus/handwritten/blobs/bin/7bb8e518ca5606de78972a6c648b57623f8bc12d7e06b4bbf43c60ac419a9bbd.bin +0 -0
  237. package/corpus/handwritten/blobs/bin/7c6133f3350147257fa501f41269cb77cb11d8ed96d351e873333b079cee524a.bin +0 -0
  238. package/corpus/handwritten/blobs/bin/7f5acf1893828995256f5b2c54948f80ba21e53de72db26ea72ce2479f5b0682.bin +0 -0
  239. package/corpus/handwritten/blobs/bin/836516253cf0f3e6e79537609fdbe6331c8c5c974f9ec4d3fbe4a7db48cf123d.bin +0 -0
  240. package/corpus/handwritten/blobs/bin/89900344f82d5c1474d2a163f8d29903447bac64e9c9775123ef499be70c2380.bin +0 -0
  241. package/corpus/handwritten/blobs/bin/95d76a9273c3edd98b3643c54e2e7989800bdcaf6f815230ef09f601e760330f.bin +0 -0
  242. package/corpus/handwritten/blobs/bin/a7b5a5fe053a38c93eed899bde5051cddd80e0a3f3c82b6fd814869097f0944b.bin +0 -0
  243. package/corpus/handwritten/blobs/bin/b29be84df0bfd1bc97a1464fd5ea068130947bfed3328e167640c81326eb45d7.bin +0 -0
  244. package/corpus/handwritten/blobs/bin/b6518dc352be03332a256f1cd9d1149da262fe6829e87ba43364f19c687ca7c9.bin +0 -0
  245. package/corpus/handwritten/blobs/bin/b7edb8095beeb158adb61ae3caa1653773757afbcd8fb2764c94b975a43fe9e2.bin +0 -0
  246. package/corpus/handwritten/blobs/bin/b86d617ae7fa397df51ff7af87cd89fa113ac19abd3840ea8f3a086d6c82905c.bin +0 -0
  247. package/corpus/handwritten/blobs/bin/bb860b5c67ee7f2fafd67588822152228d6f6c9de1231123eeafa4844732b18b.bin +0 -0
  248. package/corpus/handwritten/blobs/bin/bf8c3971bbb224075d488ffa9cfc20e322c61f9ca0c59c356838415f6778014b.bin +0 -0
  249. package/corpus/handwritten/blobs/bin/c2edd52ee14ebdc9fcc01d38c15d6ab3379df7a20098c260c546551898959cd1.bin +0 -0
  250. package/corpus/handwritten/blobs/bin/c3de13c551c5b45270143dcbb9fc9a31651f6012355823c0709c2fe6cafda940.bin +0 -0
  251. package/corpus/handwritten/blobs/bin/c57a8d9da18b1289afd16de00dd1ec75027048b8dcf7dd218da36bc3276ff035.bin +0 -0
  252. package/corpus/handwritten/blobs/bin/cc2975c9dea4c31b1bf380623a8f44059423004653f9cae4f603d374174323b7.bin +0 -0
  253. package/corpus/handwritten/blobs/bin/d476abba2e1c63ad3c7527617559173484ee837a5be2744d968466d5527c286b.bin +0 -0
  254. package/corpus/handwritten/blobs/bin/db242f89115c878cf0c3b976a1647e9cdeb6a1a51813b1dacaf1188715603294.bin +0 -0
  255. package/corpus/handwritten/blobs/bin/dcc4b9b3bc4ca777d11c494ae5478f17ad7cf2efa18dcba30def872d55355bc3.bin +0 -0
  256. package/corpus/handwritten/blobs/bin/dd99da483f385327d6b8319d6e2e9000b580280069353718aa0fda2230707e8b.bin +0 -0
  257. package/corpus/handwritten/blobs/bin/dfc139dba26f1789e16f72602e654636f2d99bcd41e3f10454da74b3a6a64dda.bin +0 -0
  258. package/corpus/handwritten/blobs/bin/e15ab3cc9f60657321cfe8d9751e086f025734f4e7068271e0baf6066a686f18.bin +0 -0
  259. package/corpus/handwritten/blobs/bin/e16a83a71213db6f804ce1d53981a2308062752c85c440022d9eeb7605fd37c8.bin +0 -0
  260. package/corpus/handwritten/blobs/bin/e7355876487f660ac9f25c9d9dfcd66eaf432e1e1669eb89d12ce8267a8d3e86.bin +0 -0
  261. package/corpus/handwritten/blobs/bin/e81a632e2da0f6a4d801f18aadfe2c6e640401cc1178935a9099ebb8e3af6cc1.bin +0 -0
  262. package/corpus/handwritten/blobs/bin/f0721c60d0b1046579d8c587fac8482a70822f0b39a85f9acc0cfc31f82b7dd2.bin +0 -0
  263. package/corpus/handwritten/blobs/bin/f301cf574de6d84030f839098ca44705dca115f9d3e4ee848e0b35fff3deaf77.bin +0 -0
  264. package/corpus/handwritten/blobs/bin/f7797f295a7570c437bf7020a1ddf208fea2a7aab595333320a6396c1608d360.bin +0 -0
  265. package/corpus/handwritten/blobs/glb/08de0bb2439ac812adef528b4b073bdb954bbeada972a2f3afb1757bd0540396.glb +0 -0
  266. package/corpus/handwritten/blobs/glb/153681171c35d2898569324e21ebe734b8418d4122833618a1e64ea326be1e5e.glb +0 -0
  267. package/corpus/handwritten/blobs/glb/1df5ce0b6cae72ec4cf3c41510681ba70ebb474f9745d536ebd4f03e8fc82de9.glb +0 -0
  268. package/corpus/handwritten/blobs/glb/262031e2fa710bd8d586eafce1019ca1434a2170e407567741aac1d99fc9c5ab.glb +0 -0
  269. package/corpus/handwritten/blobs/glb/27466efb180fb5b5074b40e424bd0b8d78f7ff0f5f3663748e014daeeeb5bedd.glb +0 -0
  270. package/corpus/handwritten/blobs/glb/2d2f6d00a14c89a4efb7f679b371118f564f258797ed8b8b42baf4ae60902ea6.glb +0 -0
  271. package/corpus/handwritten/blobs/glb/31f327d215ed7ef422e2169172f3869769e9816c9da979616844081e74f2eb09.glb +0 -0
  272. package/corpus/handwritten/blobs/glb/3bfe0e9ca18077ffee9c284ef40fed9f2fc354f7495612c785a6f980b5966559.glb +0 -0
  273. package/corpus/handwritten/blobs/glb/3db43042b3eeb1dee7ce9de1cc00322cefc348aa18c37d168b0858e6af6ca7a4.glb +0 -0
  274. package/corpus/handwritten/blobs/glb/46460a50da44fb8e792b48a646ec9ce025bb309d315dbcf67d2e9598591750de.glb +0 -0
  275. package/corpus/handwritten/blobs/glb/4902213556fedaf269fba6f0f195e424b2e49973ab45dcb8b69e34d253d9d8ff.glb +0 -0
  276. package/corpus/handwritten/blobs/glb/4dda463c9cd4bf339e5611871d4ba2fe16fe1487389a88b2888e96bc55e45d93.glb +0 -0
  277. package/corpus/handwritten/blobs/glb/4ed23469bd2b6b73183564cfc80a1ebcd0ffe2666a90a556752af0ca147fd2f7.glb +0 -0
  278. package/corpus/handwritten/blobs/glb/4fe74a1bed6b67129885b90cfcae01b5ca82e23e6d40507e280f02fca7fda54f.glb +0 -0
  279. package/corpus/handwritten/blobs/glb/50521bb3a50cc7199d324a0b25bc167c2e9d69770cb500490428efefc81ce3ea.glb +0 -0
  280. package/corpus/handwritten/blobs/glb/5ecca36d1e503edc092af0ef42f139cdcad99b8c2008318eb6ad337023d57296.glb +0 -0
  281. package/corpus/handwritten/blobs/glb/60fc036925852568cdb1b9fb85218dc43a8bf2ed86d3971a221ad942ece203a7.glb +0 -0
  282. package/corpus/handwritten/blobs/glb/611633cee0e5822b744486e6dff5414ad5b159eaabffe1c0ba0bb68be794bfa1.glb +0 -0
  283. package/corpus/handwritten/blobs/glb/67c7f983a0bcb9f84425378acac121a53f30795dce3181643bc1b763d0ae80b5.glb +0 -0
  284. package/corpus/handwritten/blobs/glb/6ecc0d307030943727484b49b1872643479c55ccc92252fca06e9e3cbe98fa90.glb +0 -0
  285. package/corpus/handwritten/blobs/glb/774b366df4de56cb6f9e3c904f02ec98016ac2ec489dc9e214b09220fca2f8e5.glb +0 -0
  286. package/corpus/handwritten/blobs/glb/785bae514ffc029e611d602259db7d37cbef589409a0814b9d37175d8b6c0a45.glb +0 -0
  287. package/corpus/handwritten/blobs/glb/79e79524efb2830e90af3c945c5b36ef07c1a38f2da2958112203a99eac52a16.glb +0 -0
  288. package/corpus/handwritten/blobs/glb/7bebdcdaf0be30d9f8d21bde72af68c32d825d12dc668f48109223ed7ff71c9f.glb +0 -0
  289. package/corpus/handwritten/blobs/glb/7d8b5ec9326a11362b3f9fa3409cb59d11b99f549cc304319c63c1ec55cbf5ab.glb +0 -0
  290. package/corpus/handwritten/blobs/glb/82e886f826315e4bf962363165fadadd3d93f85132c1909095045e0a7ccc0b34.glb +0 -0
  291. package/corpus/handwritten/blobs/glb/84b4ef190c3994ae7ca1b1f7a936526d1d20ad7deac13f949e3fe6bb7ba139ed.glb +0 -0
  292. package/corpus/handwritten/blobs/glb/85b7153e943fbe8c659bd6e4b323511dc6f6e2c7a3a1185e7a9a29ea6ddfdfa6.glb +0 -0
  293. package/corpus/handwritten/blobs/glb/85cf2d2be91cedbaf454ca42ad53c0c17b311088528d274695dbcea490d6c684.glb +0 -0
  294. package/corpus/handwritten/blobs/glb/8662b4cdc626c61a45e63f71dc866746c083cd0d4644b817a373105f936a7047.glb +0 -0
  295. package/corpus/handwritten/blobs/glb/8a4d33bfbb17d865e5deb0a0bdf3de67fc2312950a7568ed2635c22fbe4b1ecf.glb +0 -0
  296. package/corpus/handwritten/blobs/glb/8b11171d41c9027411ca8b87797d9ec70355471bdd3a247c114c49b8b781bbd9.glb +0 -0
  297. package/corpus/handwritten/blobs/glb/9026e1c4f0250832b69a989e69fe3071e5cad72adfe9c22ce11c409b18bb3357.glb +0 -0
  298. package/corpus/handwritten/blobs/glb/945b946f17b6393ea040230641c3083678bb6ab42e1d7239aeb9c950d0e7d5bb.glb +0 -0
  299. package/corpus/handwritten/blobs/glb/9afab455d603f1f8a38074c5a6cbed26d7d7f2cfd8a531820bd165d4b37399ee.glb +0 -0
  300. package/corpus/handwritten/blobs/glb/a6d702fb1be1095517625bfb2a2383f5f9638775059fd7e0d093d2044651b848.glb +0 -0
  301. package/corpus/handwritten/blobs/glb/aae479c2f99848998a1b499bbcad815d971349c2442bc363786aef63baad4337.glb +0 -0
  302. package/corpus/handwritten/blobs/glb/b155bd41f260d5303e293d691b657e0fb9b9c8b2f8aa589a0187061baa033eb7.glb +0 -0
  303. package/corpus/handwritten/blobs/glb/b1e8b770d99e8ca4396f21fd512f632c587149fc13db69deba388f702a46982f.glb +0 -0
  304. package/corpus/handwritten/blobs/glb/b382c318a5baac35da59a7b99ebaad6992e30c4bac327e85851ce0b2f89aa58b.glb +0 -0
  305. package/corpus/handwritten/blobs/glb/ba4478198696802808262af9bbfc224fe37e24e3f8c13de91debc87d3e1a9d41.glb +0 -0
  306. package/corpus/handwritten/blobs/glb/baf02cd967d1bf3c6a2ae878f4ca4ed34d0c39302ca2b8b6e3560a4e87b5c16f.glb +0 -0
  307. package/corpus/handwritten/blobs/glb/bc14dd152815ea6ed39458dcf89f2d69173d61d6e2b57be99d31ce2da64ccf67.glb +0 -0
  308. package/corpus/handwritten/blobs/glb/bc4e4d3bcf7ace855183efec0fbf242d444a32714d8b2fd8a45fa7b539ef2c73.glb +0 -0
  309. package/corpus/handwritten/blobs/glb/bc53475e2aa2fc9c2c0574b36a449fd57259c462d771e2b360def5c338d37c26.glb +0 -0
  310. package/corpus/handwritten/blobs/glb/c35d9e50f392c29f2587e3a6eb4f21143ba08e48d76a53b9de62d3b5be46c1a5.glb +0 -0
  311. package/corpus/handwritten/blobs/glb/c6911e059d3ac6aea75bc0ad3dd8b220704bfbba8ba98b57705e7aa3ecbe0050.glb +0 -0
  312. package/corpus/handwritten/blobs/glb/c84d88b6ed878490bcf9fa985b5e6a7a683de8ee7808ee4151a3b6a898fbe4f2.glb +0 -0
  313. package/corpus/handwritten/blobs/glb/c9d5d02ef0e38ac2898d3e6ecbc2ea0a6d2bd04531118550ab54b5e83293d7ec.glb +0 -0
  314. package/corpus/handwritten/blobs/glb/cd9d04e361e4c3d0aeb82f8682d71114ff8318b8fc4980bccee3c5e5c16afc27.glb +0 -0
  315. package/corpus/handwritten/blobs/glb/d32499928422a0cd1a32b1afaeacc31d2d84d74876f6805b36218dc04402a921.glb +0 -0
  316. package/corpus/handwritten/blobs/glb/dc7ea629fcddeba445599d73a5d721f4d5004e056c3c140baa3e6817393c6462.glb +0 -0
  317. package/corpus/handwritten/blobs/glb/dce79b59c85ca5a7f9fc25e585d253b15558f8f5d8497a76923c9071601de8b1.glb +0 -0
  318. package/corpus/handwritten/blobs/glb/e7fa1b02bae6c8d19ace0e8db44810beab9a1be9f8172d5816324760267d6d11.glb +0 -0
  319. package/corpus/handwritten/blobs/glb/eb664feed0fdcc3ac73cecbe737109994fe5dfad8a33997de88598965fa936ba.glb +0 -0
  320. package/corpus/handwritten/blobs/glb/ec6a771ca852b0b69e55a12df9d347384d01e611c8c6151ea771910f1601605d.glb +0 -0
  321. package/corpus/handwritten/blobs/glb/ede05013f575b4216d2b4b3f8a14d611ef28756f952f702bb169529c8ab02eff.glb +0 -0
  322. package/corpus/handwritten/blobs/glb/fec21fa96950340a1a031b31b37407468361ab0a7645c683e0d7773c18591a26.glb +0 -0
  323. package/corpus/handwritten/blobs/glb/ff21ea082bb00b01006900e7dbbb85220f31f45c144405c840a70872f9e09cb9.glb +0 -0
  324. package/corpus/handwritten/blobs/image/248fe9601e921305ffb35421414bc5f03e9f1b3dba15bf9b5c3387d8f255da17.png +0 -0
  325. package/corpus/handwritten/blobs/image/4b47420aff1ded28c9c6cf1eb92d6e2d7b01d057f2654654405445ff8d562e55.webp +0 -0
  326. package/corpus/handwritten/blobs/image/5dbc818ed0be692e6ba3df87898fd623280a2686eed5a538333e1325764b238d.png +0 -0
  327. package/corpus/handwritten/blobs/image/d5ae692911bbc2b60b5fdde239b70aad4ce9be0d7665c4a91b438de6077e21e3.jpg +0 -0
  328. package/corpus/handwritten/blobs/ktx2/df0dd2e8adc1d9479148bb198cb750471df454680cf0c9ffd6dc85d6262ed676.ktx2 +0 -0
  329. package/corpus/handwritten/blobs/wasm/ecc886d8bbebcf190298ff9162b6bb364f2125606f5bdc8cc220ea28044f96ea.wasm +0 -0
  330. package/corpus/handwritten/cook.json +1 -0
  331. package/corpus/handwritten/mounts/shardfiles/blender-template/discovery.json +1 -0
  332. package/corpus/handwritten/sections/actors/creatures.json +1 -0
  333. package/corpus/handwritten/sections/actors/crowds.json +1 -0
  334. package/corpus/handwritten/sections/actors/species.json +1 -0
  335. package/corpus/handwritten/sections/audio/audio.json +1 -0
  336. package/corpus/handwritten/sections/client/plan.json +1 -0
  337. package/corpus/handwritten/sections/combat/encounters.json +1 -0
  338. package/corpus/handwritten/sections/combat/items.json +1 -0
  339. package/corpus/handwritten/sections/combat/strikes.json +1 -0
  340. package/corpus/handwritten/sections/economy/ledger.json +1 -0
  341. package/corpus/handwritten/sections/economy/loot.json +1 -0
  342. package/corpus/handwritten/sections/look/look.json +1 -0
  343. package/corpus/handwritten/sections/look/map.json +1 -0
  344. package/corpus/handwritten/sections/look/materials.json +1 -0
  345. package/corpus/handwritten/sections/quests/quests.json +1 -0
  346. package/corpus/handwritten/sections/sim/client-scripts.json +1 -0
  347. package/corpus/handwritten/sections/sim/hooks.json +1 -0
  348. package/corpus/handwritten/sections/sim/plumbing.json +1 -0
  349. package/corpus/handwritten/sections/sim/sim.json +1 -0
  350. package/corpus/handwritten/sections/state/migrations.json +1 -0
  351. package/corpus/handwritten/sections/state/state.json +1 -0
  352. package/corpus/handwritten/sections/ui/card.json +1 -0
  353. package/corpus/handwritten/sections/ui/compendium.json +1 -0
  354. package/corpus/handwritten/sections/ui/level.json +1 -0
  355. package/corpus/handwritten/sections/ui/ui.json +1 -0
  356. package/corpus/handwritten/sections/world/collision.json +1 -0
  357. package/corpus/handwritten/sections/world/days.json +1 -0
  358. package/corpus/handwritten/sections/world/edge.json +1 -0
  359. package/corpus/handwritten/sections/world/movers.json +1 -0
  360. package/corpus/handwritten/sections/world/navigation.json +1 -0
  361. package/corpus/handwritten/sections/world/props.json +1 -0
  362. package/corpus/handwritten/sections/world/spawn.json +1 -0
  363. package/corpus/handwritten/sections/world/terrain.json +1 -0
  364. package/corpus/handwritten/sections/world/tiles.json +1 -0
  365. package/corpus/handwritten/sections/world/traversal.json +1 -0
  366. package/corpus/handwritten/sections/world/water.json +1 -0
  367. package/corpus/handwritten/sections/world/weather.json +1 -0
  368. package/corpus/handwritten/shard.json +1 -0
  369. package/corpus/handwritten/validation.json +1 -0
  370. package/package.json +508 -2
  371. package/packages/wire/src/net-command.ts +150 -0
  372. package/src/cli/bin.mjs +9 -0
  373. package/src/cli/main.ts +148 -0
  374. package/src/cli/pack.ts +262 -0
  375. package/src/cli/validate.ts +210 -0
  376. package/src/cli/version.ts +13 -0
  377. package/src/format/aacIndex.ts +160 -0
  378. package/src/format/accent.ts +35 -0
  379. package/src/format/achievementCounters.ts +22 -0
  380. package/src/format/actorMoves.ts +615 -0
  381. package/src/format/actorProjectionV2.ts +93 -0
  382. package/src/format/admissionControl.ts +66 -0
  383. package/src/format/admissionLate.ts +22 -0
  384. package/src/format/admissionLimits.ts +11 -0
  385. package/src/format/admissionServer.ts +30 -0
  386. package/src/format/ambientLife.ts +123 -0
  387. package/src/format/arenaRoomDocument.ts +114 -0
  388. package/src/format/arenaScriptRows.ts +107 -0
  389. package/src/format/arenaView.ts +30 -0
  390. package/src/format/assetAdmission.ts +279 -0
  391. package/src/format/audio-measure.ts +184 -0
  392. package/src/format/audio-mp4.ts +234 -0
  393. package/src/format/audio-rules.ts +295 -0
  394. package/src/format/audio-waiver.ts +55 -0
  395. package/src/format/audio.ts +463 -0
  396. package/src/format/audioBank.ts +37 -0
  397. package/src/format/audioProfiles.ts +501 -0
  398. package/src/format/audioRouting.ts +67 -0
  399. package/src/format/audioScene.ts +342 -0
  400. package/src/format/bakedTerrainBytes.ts +35 -0
  401. package/src/format/bakedTerrainSampling.ts +66 -0
  402. package/src/format/bakedTiles.ts +407 -0
  403. package/src/format/binarySource.ts +32 -0
  404. package/src/format/bodySlots.ts +90 -0
  405. package/src/format/bossEncounter.ts +354 -0
  406. package/src/format/bossProfiles.ts +709 -0
  407. package/src/format/bossSocketRig.ts +92 -0
  408. package/src/format/bowVariantRows.ts +68 -0
  409. package/src/format/brains.ts +252 -0
  410. package/src/format/budget.ts +214 -0
  411. package/src/format/buildings.ts +496 -0
  412. package/src/format/builtIndexes.ts +26 -0
  413. package/src/format/catalogueEconomy.ts +65 -0
  414. package/src/format/caveInterior.ts +225 -0
  415. package/src/format/cellBounds.ts +47 -0
  416. package/src/format/cellField.ts +98 -0
  417. package/src/format/chapterDirector.ts +193 -0
  418. package/src/format/clientChannels.ts +450 -0
  419. package/src/format/clientCues.ts +34 -0
  420. package/src/format/clientNative.ts +358 -0
  421. package/src/format/clientOwnership.ts +43 -0
  422. package/src/format/clientPlan.ts +306 -0
  423. package/src/format/clientScripts.ts +116 -0
  424. package/src/format/clientState.ts +273 -0
  425. package/src/format/clientTrails.ts +56 -0
  426. package/src/format/collectionDisplay.ts +232 -0
  427. package/src/format/colourFunction.ts +44 -0
  428. package/src/format/colourLut.ts +4 -0
  429. package/src/format/commonsCosts.ts +28 -0
  430. package/src/format/compendiumRows.ts +151 -0
  431. package/src/format/compendiumSketches.ts +23 -0
  432. package/src/format/completion.ts +83 -0
  433. package/src/format/contentBounds.ts +76 -0
  434. package/src/format/contentCost.ts +50 -0
  435. package/src/format/contracts.ts +305 -0
  436. package/src/format/cook-data.ts +148 -0
  437. package/src/format/cook-preparation.ts +241 -0
  438. package/src/format/coverAppearance.ts +53 -0
  439. package/src/format/creatures.ts +175 -0
  440. package/src/format/crowdPresentation.ts +87 -0
  441. package/src/format/crowds.ts +46 -0
  442. package/src/format/cueDirector.ts +134 -0
  443. package/src/format/dayCycle.ts +28 -0
  444. package/src/format/dayLook.ts +44 -0
  445. package/src/format/decisionBindings.ts +80 -0
  446. package/src/format/decisionPublication.ts +35 -0
  447. package/src/format/decisionScripts.ts +327 -0
  448. package/src/format/declaredQuestPage.ts +101 -0
  449. package/src/format/deviceMemoryCost.ts +50 -0
  450. package/src/format/director.ts +52 -0
  451. package/src/format/directorProduct.ts +27 -0
  452. package/src/format/directorStages.ts +165 -0
  453. package/src/format/directorWhen.ts +78 -0
  454. package/src/format/discoveryValues.ts +416 -0
  455. package/src/format/easedFields.ts +48 -0
  456. package/src/format/edgeProfiles.ts +40 -0
  457. package/src/format/eliteContinuation.ts +110 -0
  458. package/src/format/eliteEntryRows.ts +351 -0
  459. package/src/format/eliteFeature.ts +605 -0
  460. package/src/format/eliteScripts.ts +60 -0
  461. package/src/format/encounterRows.ts +1030 -0
  462. package/src/format/encounters.ts +65 -0
  463. package/src/format/engine-music.ts +42 -0
  464. package/src/format/entryClearance.ts +86 -0
  465. package/src/format/entryCoverage.ts +61 -0
  466. package/src/format/entryGeometry.ts +56 -0
  467. package/src/format/entryLanding.ts +83 -0
  468. package/src/format/entryReads.ts +45 -0
  469. package/src/format/entrywayAssets.ts +33 -0
  470. package/src/format/entryways.ts +58 -0
  471. package/src/format/exchange.ts +25 -0
  472. package/src/format/explorerCatalogue.ts +312 -0
  473. package/src/format/feature-sides.ts +233 -0
  474. package/src/format/fieldPresentation.ts +62 -0
  475. package/src/format/figures.ts +437 -0
  476. package/src/format/findsCatalogue.ts +67 -0
  477. package/src/format/fixedDraws.ts +24 -0
  478. package/src/format/flyers.ts +226 -0
  479. package/src/format/forestNativeInputs.ts +37 -0
  480. package/src/format/forestPage.ts +285 -0
  481. package/src/format/forestWeather.ts +333 -0
  482. package/src/format/geometryPack.ts +136 -0
  483. package/src/format/glbBuffers.ts +77 -0
  484. package/src/format/glbCost.ts +212 -0
  485. package/src/format/glbImages.ts +95 -0
  486. package/src/format/glbTriangles.ts +95 -0
  487. package/src/format/graphArmsRows.ts +200 -0
  488. package/src/format/grapple.ts +123 -0
  489. package/src/format/grazers.ts +164 -0
  490. package/src/format/grid-catalogue.ts +247 -0
  491. package/src/format/gridReadiness.ts +49 -0
  492. package/src/format/groupBrains.ts +249 -0
  493. package/src/format/heldArms.ts +576 -0
  494. package/src/format/heldEquipment.ts +78 -0
  495. package/src/format/heldFinishes.ts +415 -0
  496. package/src/format/heldGear.ts +68 -0
  497. package/src/format/heldJianDocument.ts +75 -0
  498. package/src/format/heldMelee.ts +866 -0
  499. package/src/format/heldView.ts +26 -0
  500. package/src/format/heldWeaponDocuments.ts +36 -0
  501. package/src/format/herdShelter.ts +58 -0
  502. package/src/format/hooks.ts +45 -0
  503. package/src/format/immutable.ts +59 -0
  504. package/src/format/inputDeclarations.ts +157 -0
  505. package/src/format/islandLibrary.ts +51 -0
  506. package/src/format/issue-codes.ts +262 -0
  507. package/src/format/items.ts +229 -0
  508. package/src/format/json.ts +143 -0
  509. package/src/format/keyedSky.ts +314 -0
  510. package/src/format/kitFigure.ts +68 -0
  511. package/src/format/ktx2Cost.ts +47 -0
  512. package/src/format/ledger.ts +56 -0
  513. package/src/format/liftApproach.ts +75 -0
  514. package/src/format/liftRider.ts +10 -0
  515. package/src/format/lightEmitters.ts +105 -0
  516. package/src/format/lights.ts +135 -0
  517. package/src/format/limbCollision.ts +136 -0
  518. package/src/format/limbPopulation.ts +33 -0
  519. package/src/format/limbRig.ts +202 -0
  520. package/src/format/loadout.ts +156 -0
  521. package/src/format/look-light.ts +153 -0
  522. package/src/format/lootBag.ts +145 -0
  523. package/src/format/lootEconomy.ts +235 -0
  524. package/src/format/materialFamilies.ts +565 -0
  525. package/src/format/materialGraph.ts +1220 -0
  526. package/src/format/materials.ts +449 -0
  527. package/src/format/memoryAdmission.ts +84 -0
  528. package/src/format/memoryGates.json +7 -0
  529. package/src/format/meshCollision.ts +56 -0
  530. package/src/format/meshCollisionAssets.ts +19 -0
  531. package/src/format/meshCollisionBytes.ts +102 -0
  532. package/src/format/meshEntryways.ts +54 -0
  533. package/src/format/meshopt-decoder.wasm +0 -0
  534. package/src/format/meshopt-module.ts +177 -0
  535. package/src/format/meshopt-static.mjs +11 -0
  536. package/src/format/meshopt-wasm.ts +61 -0
  537. package/src/format/migrations.ts +209 -0
  538. package/src/format/minimap.ts +1052 -0
  539. package/src/format/mixedPopulation.ts +222 -0
  540. package/src/format/modes.ts +304 -0
  541. package/src/format/mountedSpecimenRows.ts +64 -0
  542. package/src/format/moverLifecycle.ts +34 -0
  543. package/src/format/moverMotion.ts +271 -0
  544. package/src/format/movers.ts +224 -0
  545. package/src/format/namedJsonContracts.ts +423 -0
  546. package/src/format/nativeActors.ts +45 -0
  547. package/src/format/nativeAssembly.ts +141 -0
  548. package/src/format/nativeGround.ts +37 -0
  549. package/src/format/nativeGroundAssets.ts +35 -0
  550. package/src/format/nativeItems.ts +184 -0
  551. package/src/format/nativePhysics.ts +140 -0
  552. package/src/format/nativeQuestComposition.ts +398 -0
  553. package/src/format/nativeRiding.ts +103 -0
  554. package/src/format/nativeRuntimePlan.ts +89 -0
  555. package/src/format/navigation.ts +99 -0
  556. package/src/format/navigationBytes.ts +111 -0
  557. package/src/format/nearPlaceholder.ts +111 -0
  558. package/src/format/nearPlaceholderAssets.ts +23 -0
  559. package/src/format/netCommand.ts +11 -0
  560. package/src/format/npcs.ts +546 -0
  561. package/src/format/outputAddress.ts +11 -0
  562. package/src/format/outputDownload.ts +34 -0
  563. package/src/format/outputFields.ts +123 -0
  564. package/src/format/outputIndex.ts +352 -0
  565. package/src/format/overdraw.ts +41 -0
  566. package/src/format/pageLife.ts +303 -0
  567. package/src/format/pagePreparation.ts +60 -0
  568. package/src/format/paintedLook.ts +55 -0
  569. package/src/format/paintedResources.ts +158 -0
  570. package/src/format/panelTemplate.ts +139 -0
  571. package/src/format/placeholderSource.ts +209 -0
  572. package/src/format/platformDataSections.ts +146 -0
  573. package/src/format/platformFileFamilies.ts +299 -0
  574. package/src/format/plumbing.ts +165 -0
  575. package/src/format/populationEnvironment.ts +210 -0
  576. package/src/format/populationKeeper.ts +163 -0
  577. package/src/format/populationProjection.ts +496 -0
  578. package/src/format/populationRespawn.ts +48 -0
  579. package/src/format/populationRoster.ts +158 -0
  580. package/src/format/portalFloor.ts +56 -0
  581. package/src/format/portalLink.ts +112 -0
  582. package/src/format/portalLinkAssets.ts +42 -0
  583. package/src/format/portalRide.ts +22 -0
  584. package/src/format/postGraphDocument.ts +36 -0
  585. package/src/format/postGraphLook.ts +477 -0
  586. package/src/format/postStack.ts +114 -0
  587. package/src/format/practiceCourseRows.ts +466 -0
  588. package/src/format/preflight.ts +155 -0
  589. package/src/format/presentation.ts +33 -0
  590. package/src/format/product-audio.ts +76 -0
  591. package/src/format/product-reads.ts +178 -0
  592. package/src/format/product-stream-admit.ts +114 -0
  593. package/src/format/product-stream-assets.ts +511 -0
  594. package/src/format/product-stream-facts.ts +208 -0
  595. package/src/format/product-stream-miss.ts +15 -0
  596. package/src/format/product-stream-node.ts +47 -0
  597. package/src/format/product-stream-packs.ts +247 -0
  598. package/src/format/product-stream-rows.ts +234 -0
  599. package/src/format/product-stream-tar.ts +204 -0
  600. package/src/format/product-stream.ts +297 -0
  601. package/src/format/product.ts +366 -0
  602. package/src/format/productValidation.ts +236 -0
  603. package/src/format/propMaterials.ts +254 -0
  604. package/src/format/props.ts +137 -0
  605. package/src/format/questContinuation.ts +42 -0
  606. package/src/format/questFigures.ts +227 -0
  607. package/src/format/questGraph.ts +30 -0
  608. package/src/format/questHost.ts +314 -0
  609. package/src/format/questInteractionSource.ts +57 -0
  610. package/src/format/questInteractions.ts +642 -0
  611. package/src/format/questPageComposition.ts +92 -0
  612. package/src/format/questPageFacets.ts +121 -0
  613. package/src/format/questRuntime.ts +118 -0
  614. package/src/format/questSteps.ts +182 -0
  615. package/src/format/questStory.ts +512 -0
  616. package/src/format/questStoryBindings.ts +43 -0
  617. package/src/format/questWorld.ts +36 -0
  618. package/src/format/quests.ts +333 -0
  619. package/src/format/rainPresentation.ts +131 -0
  620. package/src/format/rangedPage.ts +117 -0
  621. package/src/format/receipt-cost.ts +71 -0
  622. package/src/format/registeredJson.ts +530 -0
  623. package/src/format/returnProgramDocument.ts +40 -0
  624. package/src/format/returnProjectionV2.ts +84 -0
  625. package/src/format/riding.ts +377 -0
  626. package/src/format/riggedHulls.ts +160 -0
  627. package/src/format/roamingBossProjection.ts +178 -0
  628. package/src/format/roamingCombat.ts +22 -0
  629. package/src/format/ropeDeckSite.ts +148 -0
  630. package/src/format/rows.ts +86 -0
  631. package/src/format/runtime.ts +38 -0
  632. package/src/format/runtimeBinds.ts +49 -0
  633. package/src/format/runtimeCost.ts +80 -0
  634. package/src/format/runtimeSpawns.ts +73 -0
  635. package/src/format/scalarChannel.ts +38 -0
  636. package/src/format/scaledPost.ts +27 -0
  637. package/src/format/scaledPostContract.ts +51 -0
  638. package/src/format/sceneBuffers.ts +122 -0
  639. package/src/format/sceneDraws.ts +89 -0
  640. package/src/format/sceneDressing.ts +247 -0
  641. package/src/format/sceneInstances.ts +192 -0
  642. package/src/format/sceneMotion.ts +122 -0
  643. package/src/format/sceneRefs.ts +71 -0
  644. package/src/format/sceneSources.ts +730 -0
  645. package/src/format/script-side-grandfather.ts +62 -0
  646. package/src/format/script-sides.ts +289 -0
  647. package/src/format/scriptAbi.ts +14 -0
  648. package/src/format/scriptAbiV1.ts +56 -0
  649. package/src/format/scriptActionsV2.ts +101 -0
  650. package/src/format/scriptAdmission.ts +165 -0
  651. package/src/format/scriptAdmissionV2.ts +126 -0
  652. package/src/format/scriptContract.ts +170 -0
  653. package/src/format/scriptFuelV2.ts +124 -0
  654. package/src/format/scriptGroupBankV2.ts +54 -0
  655. package/src/format/scriptGroupStepV2.ts +165 -0
  656. package/src/format/scriptInputStateV2.ts +52 -0
  657. package/src/format/scriptLimits.ts +5 -0
  658. package/src/format/scriptMemoryV2.ts +17 -0
  659. package/src/format/scriptModuleV2.ts +116 -0
  660. package/src/format/scriptObservationColumnsV2.ts +226 -0
  661. package/src/format/scriptQueriesV2.ts +164 -0
  662. package/src/format/scriptQueryStateV2.ts +40 -0
  663. package/src/format/scriptRngV2.ts +52 -0
  664. package/src/format/scriptWireV2.ts +106 -0
  665. package/src/format/scripts.ts +31 -0
  666. package/src/format/sections.ts +140 -0
  667. package/src/format/shardSchema.ts +632 -0
  668. package/src/format/shardSections.ts +383 -0
  669. package/src/format/sketch.ts +79 -0
  670. package/src/format/skin-materials.ts +172 -0
  671. package/src/format/skinAssets.ts +107 -0
  672. package/src/format/skinData.ts +110 -0
  673. package/src/format/skinLayers.ts +90 -0
  674. package/src/format/skins.ts +37 -0
  675. package/src/format/socketLift.ts +58 -0
  676. package/src/format/socketLiftAssets.ts +81 -0
  677. package/src/format/socketRigCreature.ts +66 -0
  678. package/src/format/sourceResidency.ts +16 -0
  679. package/src/format/spawnCombat.ts +26 -0
  680. package/src/format/spawnScriptV2.ts +145 -0
  681. package/src/format/spawnScripts.ts +229 -0
  682. package/src/format/spawnTables.ts +46 -0
  683. package/src/format/spawnView.ts +26 -0
  684. package/src/format/speciesActorsFile.ts +86 -0
  685. package/src/format/speciesBookRows.ts +84 -0
  686. package/src/format/speciesBrain.ts +96 -0
  687. package/src/format/speciesLooks.ts +1021 -0
  688. package/src/format/speciesPrograms.ts +97 -0
  689. package/src/format/speciesScripts.ts +45 -0
  690. package/src/format/specimenCatalogue.ts +94 -0
  691. package/src/format/splatTerrain.ts +192 -0
  692. package/src/format/standalone-catalogue.ts +144 -0
  693. package/src/format/standaloneControl.ts +44 -0
  694. package/src/format/state.ts +61 -0
  695. package/src/format/stateLineage.ts +71 -0
  696. package/src/format/stealth.ts +106 -0
  697. package/src/format/stillImage.ts +135 -0
  698. package/src/format/streamedCover.ts +105 -0
  699. package/src/format/surfaceLooks.ts +50 -0
  700. package/src/format/tar-fields.ts +54 -0
  701. package/src/format/tar-paths.ts +104 -0
  702. package/src/format/tar-source.ts +13 -0
  703. package/src/format/tar-write.ts +152 -0
  704. package/src/format/tar.ts +204 -0
  705. package/src/format/targets.ts +217 -0
  706. package/src/format/terrain.ts +241 -0
  707. package/src/format/terrainTileBytes.ts +104 -0
  708. package/src/format/tierSelection.ts +195 -0
  709. package/src/format/toonLighting.ts +32 -0
  710. package/src/format/toonLook.ts +332 -0
  711. package/src/format/trail.ts +17 -0
  712. package/src/format/traversal.ts +13 -0
  713. package/src/format/treeSetRows.ts +93 -0
  714. package/src/format/ui.ts +90 -0
  715. package/src/format/uiStrings.ts +17 -0
  716. package/src/format/uniformRows.ts +31 -0
  717. package/src/format/upload-wire.ts +184 -0
  718. package/src/format/validationReceipt.ts +142 -0
  719. package/src/format/version.ts +9 -0
  720. package/src/format/vertexWear.ts +45 -0
  721. package/src/format/viewProgram.ts +329 -0
  722. package/src/format/voicePatches.ts +47 -0
  723. package/src/format/water.ts +247 -0
  724. package/src/format/waterWaves.ts +7 -0
  725. package/src/format/waterWorld.ts +34 -0
  726. package/src/format/weaponProfiles.ts +1315 -0
  727. package/src/format/weatherBundle.ts +36 -0
  728. package/src/format/weatherEffects.ts +78 -0
  729. package/src/format/weatherProgram.ts +422 -0
  730. package/src/format/wildlifePopulation.ts +210 -0
  731. package/src/format/wireLimits.ts +9 -0
  732. package/src/format/worldAssembly.ts +104 -0
  733. package/src/format/worldEntry.ts +151 -0
  734. package/src/format/worldPhysics.ts +292 -0
  735. package/src/format/worldSceneBatch.ts +106 -0
  736. package/src/format/worldSceneModels.ts +79 -0
  737. package/src/format/worldScenePages.ts +89 -0
  738. package/src/format/worldScenePlacement.ts +191 -0
  739. package/src/format/worldSceneSlabs.ts +43 -0
  740. package/src/format/worldSceneSurface.ts +113 -0
  741. package/src/format/worldSceneWeld.ts +702 -0
  742. package/src/format/worldScenes.ts +184 -0
  743. package/src/format/worldTargets.ts +111 -0
  744. package/src/format/worldUpdates.ts +229 -0
  745. package/src/hook/format-hook.d.mts +2 -0
  746. package/src/hook/format-hook.mjs +47 -0
  747. package/src/tools/runtime-hosts.json +497 -0
  748. package/README.md +0 -8
@@ -0,0 +1,1220 @@
1
+ /**
2
+ * The material graph IR (SHARD-PLATFORM SF59 step 3, G155; `docs/design/mmo/research/sf59-tsl-spike.md` §4): a typed,
3
+ * versioned graph of approved nodes that a material (`family: "graph"`) or a post pass points to. Content carries this
4
+ * data and never shader code: the compiler (`render/graph/glsl.ts`, loaded lazily through `render/graphBackend.ts`)
5
+ * lowers each node to GLSL in our own code (the MaterialXLoader pattern), so three's `NodeLoader`, `CodeNode` and
6
+ * `glslFn` stay out of reach of content (§2.9).
7
+ *
8
+ * The shape (version 1, the starter vocabulary; §4 lists the ≈ 60 the format grows to):
9
+ * - `nodes`: id → `{ op, in?, … }`; an input is another node's id or a literal number / 2–4 number array;
10
+ * - `stages`: `vertex.offset` (a displacement in the mesh's model space, added after an instance's own transform: an
11
+ * instanced mesh's `positionLocal` already carries it; `positionGeometry` is the vertex before it), `surface` (colour,
12
+ * alpha, cutoff, emissive, roughness, metalness, occlusion), `lighting` (the lighting model: see below), `outline` (the
13
+ * inverted-hull second draw: see below), `post` (a screen pass over `sceneColour`, with SF59 (2b)'s taps `sceneColourAt`,
14
+ * `sceneDepth` and `sceneNormal`, each input one sampler: `inputs` in the validation names those a pass reads); the engine epilogue (fog, the CSM
15
+ * cascade gate, the tent PCF, the shadow fade) is appended by the back-end; effectState.fog:false skips fog only, and
16
+ * effectState.fog:'<id>' swaps the engine fog for the shard's declared fog graph of that catalogue id (below);
17
+ * - a `kind: 'fog'` graph is a shard-declared fog (O11): one `stages.fog.colour` (vec3) that runs in the fog epilogue's
18
+ * place (`fog`) of every material selecting it, reading `fogInput` (the colour reaching the fog, after the output
19
+ * transform), `engineFogged` (the engine fog's result for it), `fogScale` (the material's `vertex.scale.fog`, else 1),
20
+ * `positionWorld` and `cameraPosition` (the fog's own frame), its params, textures and the safe maths; it is
21
+ * declared once in the look catalogue and costed under its own budget (it adds to each selecting program);
22
+ * - `outline` (SF59 step 7, the ink outline as a render state the format owns, not a free one): a second draw of the
23
+ * same geometry with a fixed render state: back faces only, depth tested and written, opaque, unlit, fogged by the
24
+ * epilogue, casting no shadow. `offset` (vec3, the vertex place, model space like `vertex.offset`) pushes the hull out
25
+ * (typically along `normalLocal`, scaled by the distance to the camera for a constant screen width) and `colour`
26
+ * (vec3, a fragment place without the light inputs) is its ink. It doubles the mesh's draws and vertex work and adds
27
+ * no per-pixel lighting, the cheapest true outline on the phone tier (a `sceneDepth` edge filter would cost a depth
28
+ * target plus ≈ 9 taps a pixel at 2× over the whole frame);
29
+ * - `lighting` (SF59 step 6, the approved lighting-model stage; a `standard` material only): `sun` is the radiance the
30
+ * sun adds, built per directional light (the sky rig's cascades are one sun) from the engine's light inputs
31
+ * (`sunDirection`, `sunColour` unshadowed, `sunShadow` the cast-shadow ratio, with `normalView`, `viewDirection`,
32
+ * `albedo`); `ambient` replaces the indirect diffuse (from `irradiance`); `sunSpecular` scales the physical model's own
33
+ * specular for the sun (absent: the model is diffuse only, no specular at all); `grade` maps the lit colour
34
+ * (`litColour`, emissive included) to the colour the output transform receives (a graded material is not tone mapped).
35
+ * Every other light keeps the physical model's diffuse. Same vocabulary, no code nodes, constant loops only, the same
36
+ * budget. The `sun` sub-graph runs once per directional light: its light-dependent nodes (those reading `sunDirection`,
37
+ * `sunColour` or `sunShadow`, directly or through their inputs) count `cascades` times in the instruction estimate
38
+ * (`SUN_CASCADES`, the sky rig's three, by default); its light-free part is built once and shared, so it counts once;
39
+ * - `params`: typed uniforms with ranges, each optionally bound to a day key or a declared public shard-state field.
40
+ * A binding moves a value, never the program (§2.6);
41
+ * - loops have a constant count (`loop`, at most `LOOP_MAX` iterations), so the cost is known before compile;
42
+ * - a budget per program (node count, samplers, an instruction estimate) that validation refuses past.
43
+ *
44
+ * Pure data and checks: this module imports no three.js, so the shardfile validator can use it on any machine.
45
+ */
46
+
47
+ /** A graph identifier matcher retains the original mutable RegExp contract. */
48
+ type GraphIdentifierPattern = RegExp;
49
+
50
+ /** a fog graph's catalogue id as a material's `effectState.fog` names it (the look catalogue's id pattern) */
51
+ export const GRAPH_FOG_ID: GraphIdentifierPattern = /^[a-z][a-z0-9.-]{0,127}$/u;
52
+ /** the IR version a graph declares; a graph of another version is refused (the lighting stage is additive: still version 1) */
53
+ export const GRAPH_IR_VERSION = 1;
54
+ /** the most iterations a `loop` node may run */
55
+ export const LOOP_MAX = 16;
56
+ /** Maximum elements in one fixed-capacity numeric uniform list. */
57
+ export const GRAPH_ARRAY_MAX = 64;
58
+ /** Total padded vector slots across a graph's uniform lists. */
59
+ export const GRAPH_ARRAY_SLOTS_MAX = 128;
60
+ /** the directional lights a `sun` sub-graph is built for by default: the sky rig's three CSM cascades */
61
+ export const SUN_CASCADES = 3;
62
+ /** the most cascades a validation may count (three's CSM tops out at four here) */
63
+ export const SUN_CASCADES_MAX = 4;
64
+
65
+ /** a value's type inside a graph */
66
+ export type GraphValueType = 'float' | 'vec2' | 'vec3' | 'vec4' | 'bool';
67
+ /** a literal input: a float or a 2–4 component vector */
68
+ export type GraphLiteral = number | readonly number[];
69
+ /** an input: another node's id, or a literal */
70
+ export type GraphRef = string | GraphLiteral;
71
+
72
+ /** one node of a graph */
73
+ export interface GraphNode {
74
+ readonly op: string;
75
+ readonly in?: readonly GraphRef[];
76
+ /** `const`: the value */
77
+ readonly value?: GraphLiteral;
78
+ /** `param`, `texture`: the param's name */
79
+ readonly param?: string;
80
+ /** `swizzle`: the components, e.g. `"xz"` */
81
+ readonly mask?: string;
82
+ /** `loop`: the iteration count (a constant, 1 … LOOP_MAX) */
83
+ readonly count?: number;
84
+ /** `loop`: the body, run `count` times; inside it `acc` is the running value and `index` the iteration (a float) */
85
+ readonly body?: GraphLoopBody;
86
+ /** `attribute`: the geometry attribute's name (`aSeed`: `a`, then a capital; three's own attributes are inputs);
87
+ * `meshMap`: `bump` reads its bump map; `colourOrVertex` reads its colour map or the linear vertex colour when no map is bound */
88
+ readonly name?: string;
89
+ /** `attribute`: the attribute's type */
90
+ readonly type?: GraphValueType;
91
+ /** attribute only: optional numeric default, exactly matching type, used when a mesh omits the channel. */
92
+ readonly default?: GraphLiteral;
93
+ /** varying only: use the primitive's provoking vertex instead of interpolating; absent retains ordinary perspective interpolation. */
94
+ readonly interpolation?: 'flat';
95
+ }
96
+ /** a loop's body: its own nodes (they may read the outer graph's nodes too) and the node that is the next `acc` */
97
+ export interface GraphLoopBody {
98
+ readonly nodes: Readonly<Record<string, GraphNode>>;
99
+ readonly out: GraphRef;
100
+ }
101
+
102
+ /** a param's type; a colour is written in sRGB and arrives linear, a texture names an admitted file */
103
+ export type GraphParamType = 'float' | 'vec2' | 'vec3' | 'vec4' | 'colour' | 'texture';
104
+ /** where a bound param's value comes from: a day-key channel or a declared public numeric shard-state field */
105
+ export type GraphBinding = { readonly day: string } | { readonly state: string };
106
+ /** a typed param: a uniform the graph reads */
107
+ export interface GraphParam {
108
+ readonly type: GraphParamType;
109
+ readonly value: GraphLiteral | string;
110
+ readonly min?: number;
111
+ readonly max?: number;
112
+ readonly bind?: GraphBinding;
113
+ /**
114
+ * Texture only: data bypasses sRGB conversion; absent retains the colour role. `lent`: no admitted file, a trusted
115
+ * runtime lends the sampled texture (`setTexture`, swapped without a program change; a 1×1 black stand-in until then)
116
+ * and `value` only names the slot; a texture it decodes at run time (a baked sky key) is sampled as it is.
117
+ */
118
+ readonly role?: 'colour' | 'data' | 'lent';
119
+ }
120
+
121
+ /** Fixed-capacity numeric uniform list. Each value has the declared element
122
+ * type; setArrayParam updates values without resizing or changing the program. */
123
+ export interface GraphArrayParam {
124
+ readonly type: 'float' | 'vec2' | 'vec3' | 'vec4';
125
+ readonly value: readonly GraphLiteral[];
126
+ }
127
+ /** Absolute post-instance model-space position, replacing the ordinary vertex.
128
+ * Mutually exclusive with vertex.offset; shadow:true emits matching depth. */
129
+ export interface GraphVertexPosition {
130
+ readonly position: GraphRef;
131
+ readonly shadow?: boolean;
132
+ }
133
+
134
+ /** the surface stage's outputs (each optional; an absent one keeps the lighting model's default) */
135
+ export interface GraphSurface {
136
+ readonly colour?: GraphRef;
137
+ readonly alpha?: GraphRef;
138
+ readonly alphaCutoff?: number;
139
+ /** float: what an opaque draw writes into the colour target's alpha channel (coverage stays whole, nothing blends), as a
140
+ * GLSL program's `gl_FragColor.a` did: a renderer that keeps the inverse view depth there (`cameraNear` over the view
141
+ * depth) reads it in its post. Opaque only: never with `alpha` or a blend */
142
+ readonly targetAlpha?: GraphRef;
143
+ readonly emissive?: GraphRef;
144
+ readonly roughness?: GraphRef;
145
+ readonly metalness?: GraphRef;
146
+ readonly occlusion?: GraphRef;
147
+ /** bool: the fragment is discarded where it is true (a dither or a clip; a fragment-place op, like `alphaCutoff`) */
148
+ readonly discard?: GraphRef;
149
+ /** vec3: the shading normal in world space (unit; a lit model only), in place of the interpolated one: a per-pixel
150
+ * ripple or detail normal the lighting, the environment's reflection and the lighting stage's `normalView` all use */
151
+ readonly normal?: GraphRef;
152
+ }
153
+ /** the vertex stage: an object-space offset; shadow:true also applies it and the surface mask to shadow depth */
154
+ export interface GraphVertexOffset {
155
+ readonly offset: GraphRef;
156
+ readonly shadow?: boolean;
157
+ }
158
+ /**
159
+ * The lighting-model stage (a `standard` material only): what the sun adds, the ambient term, the sun's share of the
160
+ * physical specular and the per-pixel grade after lighting (the module comment says what each reads).
161
+ */
162
+ export interface GraphLighting {
163
+ /** vec3: the radiance one directional light adds (built per cascade), added to the direct diffuse */
164
+ readonly sun: GraphRef;
165
+ /** float: the factor on the physical model's specular from the unshadowed sun (absent: no specular at all) */
166
+ readonly sunSpecular?: GraphRef;
167
+ /** vec3: the indirect diffuse radiance, from `irradiance` (absent: irradiance × albedo / π) */
168
+ readonly ambient?: GraphRef;
169
+ /** vec3: the lit colour (`litColour`) → the colour the output transform receives; a graded material is not tone mapped */
170
+ readonly grade?: GraphRef;
171
+ }
172
+ /**
173
+ * The outline stage: the inverted-hull second draw (back faces, depth tested and written, opaque, unlit, fogged, no
174
+ * shadow; the module comment says why this one render state and not a free one).
175
+ */
176
+ export interface GraphOutline {
177
+ /** vec3: the hull's displacement in model space (the vertex place, as `vertex.offset`) */
178
+ readonly offset: GraphRef;
179
+ /** vec3: the ink colour (linear; a fragment place without the light inputs) */
180
+ readonly colour: GraphRef;
181
+ }
182
+ /** Optional effect render state. Absent fields retain the existing material defaults. */
183
+ export interface GraphEffectState {
184
+ /** Unlit linear colour: apply the target's colour encoding, without tone mapping. Absent keeps legacy raw output. */
185
+ readonly output?: 'colour';
186
+ /** Alpha uses straight RGB; premultiplied expects RGB already multiplied by alpha. */
187
+ readonly blend?: 'alpha' | 'additive' | 'premultiplied';
188
+ /** false: bypass the engine fog epilogue for this effect only; a catalogue id: fog with that `kind: 'fog'` graph instead */
189
+ readonly fog?: false | string;
190
+ /** Whether fragments update depth; depth testing stays enabled. */
191
+ readonly depthWrite?: boolean;
192
+ /** Polygon offset factor and units, each finite and within -16 … 16. */
193
+ readonly depthBias?: { readonly factor: number; readonly units: number };
194
+ /** A double-sided effect draws both faces in one pass (unsorted), not three's back-then-front pair of draws. */
195
+ readonly singlePass?: true;
196
+ }
197
+ /** Typed graph stage outputs, including optional point size, surface, lighting and post colour. */
198
+ export interface GraphStages {
199
+ /** Points only: diameter in drawing-buffer pixels, clamped to 1 … 2048 and the device's point range. */
200
+ readonly points?: { readonly size: GraphRef };
201
+ readonly 'vertex.offset'?: GraphVertexOffset;
202
+ readonly 'vertex.position'?: GraphVertexPosition;
203
+ /** Raw homogeneous clip-space position; excludes model-space position/offset outputs. */
204
+ readonly 'vertex.clip'?: { readonly position: GraphRef };
205
+ /** Interpolated vertex factors, clamped to 0..1; omitted factors are neutral. */
206
+ readonly 'vertex.scale'?: { readonly sun?: GraphRef; readonly fog?: GraphRef };
207
+ /** Object-space vertex normal, replacing the input before lighting interpolation. */
208
+ readonly 'vertex.normal'?: { readonly normal: GraphRef };
209
+ /** World-space receiver lookup shift; leaves geometry and caster depth unchanged. Lit meshes only. */
210
+ readonly 'shadow.offset'?: { readonly offset: GraphRef };
211
+ readonly surface?: GraphSurface;
212
+ readonly lighting?: GraphLighting;
213
+ readonly outline?: GraphOutline;
214
+ readonly post?: { readonly colour: GraphRef; readonly alpha?: GraphRef };
215
+ /** a fog graph's only stage: the fogged colour (vec3) from `fogInput` / `engineFogged`; alpha passes through */
216
+ readonly fog?: { readonly colour: GraphRef; readonly transmission?: GraphRef };
217
+ }
218
+ /** a graph */
219
+ export interface GraphIr {
220
+ readonly version: typeof GRAPH_IR_VERSION;
221
+ /** Explicit per-program ceilings. Raising the defaults requires trusted runtime authority. */
222
+ readonly budget?: GraphBudget;
223
+ /** a material (vertex.offset + surface), a screen pass (post) or a shard's declared fog (fog, selected by materials) */
224
+ readonly kind: 'material' | 'post' | 'fog';
225
+ /** a material's lighting model: three's physical standard model, or unlit */
226
+ readonly model?: 'standard' | 'unlit' | 'toon';
227
+ /** Absent: mesh. Points require an unlit graph and a points.size vertex output. */
228
+ readonly primitive?: 'mesh' | 'points';
229
+ /** Optional material-only effect state; omission preserves every prior render default. */
230
+ readonly effectState?: GraphEffectState;
231
+ /** One named, admitted fog function for post-only fogSample nodes. Functions keep their own node ceiling; calls spend their full instruction cost. */
232
+ readonly postFog?: { readonly name: string; readonly graph: GraphIr };
233
+ readonly doubleSided?: boolean;
234
+ /** false: retain the front-face normal basis on back faces; culling is unchanged. */
235
+ readonly flipBackfaceNormals?: boolean;
236
+ /** false: omit underwater caustics for this toon material only. */
237
+ readonly toonCaustics?: boolean;
238
+ /** one normal per triangle (the toon family's faceted look): `normalView` is the face's */
239
+ readonly flatShading?: boolean;
240
+ readonly params?: Readonly<Record<string, GraphParam>>;
241
+ /** Numeric uniform arrays: 1–64 elements each, at most 128 padded vector slots in total. */
242
+ readonly arrays?: Readonly<Record<string, GraphArrayParam>>;
243
+ readonly nodes: Readonly<Record<string, GraphNode>>;
244
+ readonly stages: GraphStages;
245
+ }
246
+
247
+ /** the per-program ceilings validation refuses past */
248
+ export interface GraphBudget {
249
+ readonly nodes: number;
250
+ readonly samplers: number;
251
+ readonly instructions: number;
252
+ }
253
+ /** the default ceilings (a starter graph: the measure preset is ≈ 60 nodes, ≈ 170 instructions) */
254
+ export const DEFAULT_GRAPH_BUDGET: GraphBudget = { nodes: 160, samplers: 4, instructions: 480 };
255
+ /** Hard ceilings for a trusted declaration; loop trips remain charged to the estimated cost. */
256
+ export const MAX_GRAPH_BUDGET: GraphBudget = Object.freeze({ nodes: 4096, samplers: 16, instructions: 8192 });
257
+
258
+ /** Raw graph JSON bounds, enforced before typing or compiler work; trusted presets supply their own node budget. */
259
+ export const GRAPH_ADMISSION_LIMITS: Readonly<{ bytes: 96000; depth: 64; }> = Object.freeze({ bytes: 96_000, depth: 64 });
260
+ /** whether JSON writes `value` as itself in quotes: printable ASCII with no quote or backslash (a loop, not a regex: Node's
261
+ * type stripping rewrote the regex's escaped bracket and broke it) */
262
+ function plainJsonString(value: string): boolean {
263
+ for (let i = 0; i < value.length; i++) { const c = value.codePointAt(i) ?? 0; if (c < 32 || c > 126 || c === 34 || c === 92) return false; }
264
+ return true;
265
+ }
266
+ /** Check JSON bytes, all nested nodes and inert structure without invoking authored getters or toJSON methods. */
267
+ export function graphAdmissionErrors(input: unknown, nodeLimit: number = DEFAULT_GRAPH_BUDGET.nodes): string[] {
268
+ type Entry = { value: unknown; depth: number; nodes: boolean } | { leave: object };
269
+ const pending: Entry[] = [{ value: input, depth: 0, nodes: false }], active = new Set<object>(), encoder = new TextEncoder();
270
+ let bytes = 0, nodes = 0;
271
+ // printable ASCII with no quote or backslash serializes as itself in quotes: its byte count without encoding a copy
272
+ // (G639: the encode per string was most of each first-lookup graph validation, ~30 ms a Nalati entry frame)
273
+ // Node keys, ops and references repeat within a graph. Cache only their immutable encoded lengths for this call;
274
+ // every occurrence still spends its complete byte count, and no result or source survives into another admission.
275
+ const stringSizes = new Map<string, number>();
276
+ const stringBytes = (value: string): number => {
277
+ const known = stringSizes.get(value);
278
+ if (known !== undefined) return known;
279
+ const size = value.length > GRAPH_ADMISSION_LIMITS.bytes ? GRAPH_ADMISSION_LIMITS.bytes + 1
280
+ : plainJsonString(value) ? value.length + 2 : encoder.encode(JSON.stringify(value)).length;
281
+ stringSizes.set(value, size); return size;
282
+ };
283
+ while (pending.length > 0) {
284
+ const entry = pending.pop(); if (entry === undefined) break;
285
+ if ('leave' in entry) { active.delete(entry.leave); continue; }
286
+ const { value, depth } = entry;
287
+ if (depth > GRAPH_ADMISSION_LIMITS.depth) return ['graph: JSON depth exceeds admission cap'];
288
+ if (typeof value === 'string') bytes += stringBytes(value);
289
+ else if (value === null || typeof value === 'boolean') bytes += value === null ? 4 : value ? 4 : 5;
290
+ else if (typeof value === 'number') {
291
+ if (!Number.isFinite(value) || Object.is(value, -0)) return ['graph: finite JSON numbers required'];
292
+ bytes += JSON.stringify(value).length;
293
+ } else if (typeof value === 'object') {
294
+ if (active.has(value)) return ['graph: JSON cycle refused'];
295
+ const prototype: unknown = Object.getPrototypeOf(value), array = Array.isArray(value);
296
+ if ((array ? prototype !== Array.prototype : prototype !== Object.prototype && prototype !== null) || Object.getOwnPropertySymbols(value).length > 0) return ['graph: plain JSON data required'];
297
+ // Every own string key still participates, including hidden properties.
298
+ // Read its descriptor only as visited instead of allocating a second
299
+ // object holding descriptors for the entire container.
300
+ const keys = Object.getOwnPropertyNames(value);
301
+ // Array length is bookkeeping, never an entry in its JSON representation.
302
+ const count = keys.length - Number(array);
303
+ if (entry.nodes) { nodes += count; if (nodes > nodeLimit) return [`budget: ${nodes} nodes (at most ${nodeLimit}); all nested nodes counted`]; }
304
+ bytes += 2 + Math.max(0, count - 1);
305
+ if (array && value.length !== count) return ['graph: sparse JSON arrays refused'];
306
+ active.add(value); pending.push({ leave: value });
307
+ for (const key of keys) {
308
+ if (array && key === 'length') continue;
309
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
310
+ if (descriptor === undefined || descriptor.get !== undefined || descriptor.set !== undefined || !descriptor.enumerable) return ['graph: JSON accessors and hidden properties refused'];
311
+ if (!array) bytes += stringBytes(key) + 1;
312
+ const child: unknown = descriptor.value;
313
+ pending.push({ value: child, depth: depth + 1, nodes: key === 'nodes' });
314
+ }
315
+ } else return ['graph: plain JSON data required'];
316
+ if (bytes > GRAPH_ADMISSION_LIMITS.bytes) return ['graph: JSON byte size exceeds admission cap'];
317
+ }
318
+ return [];
319
+ }
320
+
321
+ /** what a valid graph costs: counted over every node a stage reaches, a loop's body times its count */
322
+ export interface GraphCost {
323
+ readonly nodes: number;
324
+ readonly samplers: number;
325
+ readonly instructions: number;
326
+ }
327
+
328
+ /**
329
+ * where a node may run: `vertex` (the offset stage), `fragment` (surface), `post` (a screen pass), the lighting
330
+ * stage's three: `sun` (per directional light), `ambient` (the indirect term) and `grade` (after lighting), and the
331
+ * outline's two: `outlineVertex` (its hull offset) and `outline` (its ink; its own program, so counted apart), and
332
+ * `fog` (a fog graph's stage, run in the fog epilogue of each material that selects it)
333
+ */
334
+ type GraphPlace = 'vertex' | 'fragment' | 'post' | 'sun' | 'ambient' | 'grade' | 'outlineVertex' | 'outline' | 'fog';
335
+
336
+ /** how an op types its inputs */
337
+ type TypeRule =
338
+ | 'input' | 'const' | 'param' | 'same' | 'unary' | 'vecUnary' | 'length' | 'dot' | 'mix' | 'compare' | 'logic' | 'not'
339
+ | 'cross' | 'gerstner' | 'select' | 'swizzle' | 'combine' | 'noise' | 'texture' | 'loop' | 'acc' | 'index' | 'point3' | 'pointPair' | 'dir3' | 'sceneTap'
340
+ | 'fogSample' | 'instanceTransform' | 'windField' | 'varying' | 'attribute' | 'arrayElement' | 'cameraBillboard';
341
+ /** one op of the vocabulary */
342
+ export interface GraphOpSpec {
343
+ /** the number of inputs (min, max) */
344
+ readonly arity: readonly [number, number];
345
+ readonly rule: TypeRule;
346
+ /** an estimate of the instructions it emits */
347
+ readonly cost: number;
348
+ /** an input op's type */
349
+ readonly type?: GraphValueType;
350
+ /** the places it may run (all when absent) */
351
+ readonly places?: readonly GraphPlace[];
352
+ }
353
+
354
+ /** `viewRay`'s estimate: two multiply-adds of the camera's basis and a normalise */
355
+ const VIEW_RAY_COST = 8;
356
+ /** An input op's arity, both ends */
357
+ const NO_INPUTS = 0;
358
+ /** the lighting stage's places (fragment work, like the surface) */
359
+ const LIT: readonly GraphPlace[] = ['sun', 'ambient', 'grade'];
360
+ const FRAG: readonly GraphPlace[] = ['fragment', 'outline', 'post', ...LIT];
361
+ const MESH: readonly GraphPlace[] = ['vertex', 'outlineVertex', 'fragment', 'outline', ...LIT];
362
+ /** the vertex places (an object's own transform is read there) */
363
+ const VERT: readonly GraphPlace[] = ['vertex', 'outlineVertex'];
364
+ /**
365
+ * The version 1 vocabulary (the starter set of §4): inputs, constants and params, arithmetic and safe maths, comparisons
366
+ * and a branch-light `select`, swizzles and vector construction, MaterialX noise, an admitted texture's sample and a
367
+ * constant-count loop. Nothing here can name a three.js node or carry source.
368
+ */
369
+ export const GRAPH_OPS: Readonly<Record<string, GraphOpSpec>> = {
370
+ // inputs
371
+ windTime: { arity: [0, 0], rule: 'input', cost: 0, type: 'float' },
372
+ windGust: { arity: [0, 0], rule: 'input', cost: 0, type: 'float' },
373
+ windDirection: { arity: [0, 0], rule: 'input', cost: 0, type: 'vec2' },
374
+ windGustAt: { arity: [1, 1], rule: 'windField', cost: 64, places: MESH },
375
+ frontFacing: { arity: [0, 0], rule: 'input', cost: 0, type: 'bool', places: FRAG },
376
+ uv: { arity: [0, 0], rule: 'input', cost: 0, type: 'vec2', places: MESH },
377
+ positionLocal: { arity: [0, 0], rule: 'input', cost: 0, type: 'vec3', places: MESH },
378
+ // SF59 step 7: the geometry's own vertex position, before an instance's transform (the painterly sway's height)
379
+ positionGeometry: { arity: [0, 0], rule: 'input', cost: 0, type: 'vec3', places: VERT },
380
+ // SF59 step 7: the world position of the object's origin, the instance's own for an instanced mesh (a per-object phase)
381
+ instanceTransform: { arity: [1, 1], rule: 'instanceTransform', cost: 16, type: 'vec4', places: VERT },
382
+ objectOrigin: { arity: [0, 0], rule: 'input', cost: 4, type: 'vec3', places: VERT },
383
+ positionWorld: { arity: [0, 0], rule: 'input', cost: 1, type: 'vec3', places: [...MESH, 'fog'] },
384
+ // a fog graph's inputs (O11): the colour reaching the fog (after the output transform), the engine fog's result for it,
385
+ // and the material's fog share (`vertex.scale.fog`, else 1)
386
+ fogInput: { arity: [0, 0], rule: 'input', cost: 0, type: 'vec3', places: ['fog'] },
387
+ engineFogged: { arity: [0, 0], rule: 'input', cost: 0, type: 'vec3', places: ['fog'] },
388
+ fogScale: { arity: [0, 0], rule: 'input', cost: 0, type: 'float', places: ['fog'] },
389
+ positionView: { arity: [0, 0], rule: 'input', cost: 1, type: 'vec3', places: MESH },
390
+ normalLocal: { arity: [0, 0], rule: 'input', cost: 0, type: 'vec3', places: MESH },
391
+ normalWorld: { arity: [0, 0], rule: 'input', cost: 1, type: 'vec3', places: MESH },
392
+ cameraPosition: { arity: [0, 0], rule: 'input', cost: 0, type: 'vec3' },
393
+ // the drawing camera's near plane (m): with the view depth, the inverse depth a renderer keeps in its target's alpha
394
+ cameraNear: { arity: [0, 0], rule: 'input', cost: 0, type: 'float' },
395
+ time: { arity: [0, 0], rule: 'input', cost: 0, type: 'float' },
396
+ // the day's sun as the engine's atmosphere holds it (Atmosphere.ts `fogUniforms`, read by reference like the fog
397
+ // epilogue): the unit world direction toward the sun and its colour (linear), so an unlit graph shades with the clock
398
+ // The active material/page colour ramp's live zenith, else the scene fog colour (black if unfogged).
399
+ fogZenithColour: { arity: [0, 0], rule: 'input', cost: 0, type: 'vec3' },
400
+ fogColour: { arity: [0, 0], rule: 'input', cost: 0, type: 'vec3' },
401
+ atmosphereSunDirection: { arity: [0, 0], rule: 'input', cost: 0, type: 'vec3' },
402
+ atmosphereSunColour: { arity: [0, 0], rule: 'input', cost: 0, type: 'vec3' },
403
+ skyLightDirection: { arity: [0, 0], rule: 'input', cost: 0, type: 'vec3', places: MESH },
404
+ skyLightColour: { arity: [0, 0], rule: 'input', cost: 0, type: 'vec3', places: MESH },
405
+ instanceHash: { arity: [0, 0], rule: 'input', cost: 4, type: 'float', places: MESH },
406
+ vertexColour: { arity: [0, 0], rule: 'input', cost: 0, type: 'vec3', places: MESH }, // the geometry's `color` attribute (rgb, linear); white where it has none
407
+ vertexColourAlpha: { arity: [0, 0], rule: 'input', cost: 0, type: 'float', places: MESH }, // original vertex alpha; 1 for RGB or absent colour
408
+ // the material's own colour and emissive × intensity (linear): the ports a runtime drives per material (a creature's
409
+ // per-animal tint, its hit flash), read live like three's own material colour
410
+ materialColour: { arity: [0, 0], rule: 'input', cost: 0, type: 'vec3', places: FRAG },
411
+ materialEmissive: { arity: [0, 0], rule: 'input', cost: 1, type: 'vec3', places: FRAG },
412
+ fogSample: { arity: [5, 5], rule: 'fogSample', cost: 1, type: 'vec4', places: ['post'] },
413
+ screenUV: { arity: [0, 0], rule: 'input', cost: 0, type: 'vec2', places: ['post'] },
414
+ // The world-space unit direction from the scene camera through this pixel (LF2-3): a post graph's sky detail (clouds, a
415
+ // sun glow against `atmosphereSunDirection`) keyed to it holds still in the world as the camera turns, where `screenUV` swims
416
+ viewRay: { arity: [NO_INPUTS, NO_INPUTS], rule: 'input', cost: VIEW_RAY_COST, type: 'vec3', places: ['post'] },
417
+ // the pixel's window coordinate in pixels (`gl_FragCoord.xy`, from the top left as three's node back-end gives it): a
418
+ // screen-space dither or clip on a mesh, never a full-screen pass (that is `screenUV`)
419
+ pointCoord: { arity: [0, 0], rule: 'input', cost: 0, type: 'vec2', places: ['fragment'] },
420
+ // Absolute model-space billboard position. Geometry coordinates are world metres; object/instance origins stay fixed.
421
+ viewBillboard: { arity: [0, 0], rule: 'input', cost: 32, type: 'vec3', places: VERT },
422
+ // Absolute model-space Y-axis billboard, retaining the model's X/Y scale; subtract positionLocal for vertex.offset.
423
+ // Model-space position of a camera-relative card. Inputs: world direction,
424
+ // distance, card-local vec2 (already sized/rotated by graph arithmetic).
425
+ cameraRelativeBillboard: { arity: [3, 3], rule: 'cameraBillboard', cost: 80, type: 'vec3', places: VERT },
426
+ cylindricalBillboard: { arity: [0, 0], rule: 'input', cost: 48, type: 'vec3', places: VERT },
427
+ // World-space lengths of the model matrix's X, Y and Z axes (positive, including parent transforms).
428
+ modelScale: { arity: [0, 0], rule: 'input', cost: 9, type: 'vec3', places: VERT },
429
+ // Camera-space forward distance and projected motion; vary the direction into a point fragment.
430
+ viewDepth: { arity: [1, 1], rule: 'point3', cost: 16, type: 'float', places: VERT },
431
+ screenDirection: { arity: [2, 2], rule: 'pointPair', cost: 72, type: 'vec2', places: VERT },
432
+ fragCoord: { arity: [0, 0], rule: 'input', cost: 0, type: 'vec2', places: ['fragment', 'outline'] },
433
+ // a declared geometry attribute (`name`, `type`): a shard's own per-vertex or per-instance data
434
+ attribute: { arity: [0, 0], rule: 'attribute', cost: 0, places: MESH },
435
+ // a value computed per vertex and interpolated to the fragment: its input is typed (and costed) in the vertex place
436
+ varying: { arity: [1, 1], rule: 'varying', cost: 1, places: ['fragment', 'outline', ...LIT] },
437
+ // the lighting stage's inputs (SF59 step 6): view space, unit vectors; the engine supplies them from its own lights
438
+ normalView: { arity: [0, 0], rule: 'input', cost: 0, type: 'vec3', places: LIT }, // the shading normal (the face's under flatShading)
439
+ viewDirection: { arity: [0, 0], rule: 'input', cost: 0, type: 'vec3', places: LIT }, // toward the camera
440
+ albedo: { arity: [0, 0], rule: 'input', cost: 0, type: 'vec3', places: LIT }, // the surface colour × (1 − metalness)
441
+ sunDirection: { arity: [0, 0], rule: 'input', cost: 0, type: 'vec3', places: ['sun'] }, // toward the light
442
+ sunColour: { arity: [0, 0], rule: 'input', cost: 0, type: 'vec3', places: ['sun'] }, // the light's colour × intensity, unshadowed
443
+ sunShadow: { arity: [0, 0], rule: 'input', cost: 4, type: 'float', places: ['sun'] }, // 1 lit … 0 in cast shadow (the families' ratio)
444
+ irradiance: { arity: [0, 0], rule: 'input', cost: 0, type: 'vec3', places: ['ambient'] }, // the indirect irradiance (hemisphere, ambient)
445
+ // the environment's own light at the shading point, beside `irradiance`: its prefiltered radiance along the reflected view
446
+ // ray at the surface's roughness, and its irradiance (× π, as three keeps it) for the shading normal. Reading either
447
+ // makes `ambient` own the whole indirect term: the physical model adds no environment diffuse or specular of its own
448
+ radiance: { arity: [0, 0], rule: 'input', cost: 0, type: 'vec3', places: ['ambient'] },
449
+ environmentIrradiance: { arity: [0, 0], rule: 'input', cost: 0, type: 'vec3', places: ['ambient'] },
450
+ // Diffuse PMREM at a normalized world direction (same intensity and × π as environmentIrradiance).
451
+ environmentIrradianceAt: { arity: [1, 1], rule: 'dir3', cost: 32, places: ['ambient'] },
452
+ litColour: { arity: [0, 0], rule: 'input', cost: 0, type: 'vec3', places: ['grade'] }, // the lit colour, emissive included
453
+ sceneColour: { arity: [0, 0], rule: 'input', cost: 2, type: 'vec4', places: ['post'] },
454
+ // SF59 (2b), the post inputs, each its own sampler once a graph reads it (however many taps): the scene colour at a uv
455
+ // (`sceneColourAt`, a tap: an edge or blur kernel), the scene depth (linear view distance in metres, from the engine's
456
+ // depth target) and the scene normal (view space, unit, from the normal pre-pass the engine draws only for a post stack
457
+ // that reads it: `postInputs` names it so the stack's cost carries it). Each samples at `screenUV` without an input.
458
+ sceneColourAt: { arity: [1, 1], rule: 'sceneTap', cost: 4, type: 'vec4', places: ['post'] },
459
+ sceneDepth: { arity: [0, 1], rule: 'sceneTap', cost: 6, type: 'float', places: ['post'] },
460
+ sceneNormal: { arity: [0, 1], rule: 'sceneTap', cost: 6, type: 'vec3', places: ['post'] },
461
+ const: { arity: [0, 0], rule: 'const', cost: 0 },
462
+ param: { arity: [0, 0], rule: 'param', cost: 0 },
463
+ // Float index is floored and clamped to the declared fixed-capacity list.
464
+ arrayElement: { arity: [1, 1], rule: 'arrayElement', cost: 4 },
465
+ // Fixed CPU ocean coefficients: rest XZ, seconds, damping.
466
+ gerstnerNormal: { arity: [3, 3], rule: 'gerstner', cost: 200, type: 'vec3' },
467
+ gerstner: { arity: [3, 3], rule: 'gerstner', cost: 120, type: 'vec3' },
468
+ // arithmetic (a float broadcasts to a vector)
469
+ add: { arity: [2, 2], rule: 'same', cost: 1 },
470
+ sub: { arity: [2, 2], rule: 'same', cost: 1 },
471
+ mul: { arity: [2, 2], rule: 'same', cost: 1 },
472
+ div: { arity: [2, 2], rule: 'same', cost: 2 }, // safe: a non-literal divisor is kept off zero
473
+ min: { arity: [2, 2], rule: 'same', cost: 1 },
474
+ max: { arity: [2, 2], rule: 'same', cost: 1 },
475
+ mod: { arity: [2, 2], rule: 'same', cost: 3 }, // a − b·floor(a / b)
476
+ pow: { arity: [2, 2], rule: 'same', cost: 4 }, // safe: the base is clamped at 0
477
+ step: { arity: [2, 2], rule: 'same', cost: 1 },
478
+ abs: { arity: [1, 1], rule: 'unary', cost: 1 },
479
+ floor: { arity: [1, 1], rule: 'unary', cost: 1 },
480
+ // Quantize numeric post values with native half packing; render-target store rounding may differ.
481
+ float16: { arity: [1, 1], rule: 'unary', cost: 4, places: ['post'] },
482
+ fract: { arity: [1, 1], rule: 'unary', cost: 1 },
483
+ sin: { arity: [1, 1], rule: 'unary', cost: 4 },
484
+ cos: { arity: [1, 1], rule: 'unary', cost: 4 },
485
+ exp: { arity: [1, 1], rule: 'unary', cost: 4 }, // e^x (the emissive family's halo falloff)
486
+ atan: { arity: [2, 2], rule: 'same', cost: 8 }, // atan(y, x): the angle of (x, y), −π … π (GLSL's two-argument atan)
487
+ negate: { arity: [1, 1], rule: 'unary', cost: 1 },
488
+ oneMinus: { arity: [1, 1], rule: 'unary', cost: 1 },
489
+ saturate: { arity: [1, 1], rule: 'unary', cost: 1 },
490
+ // Screen derivatives are fragment-only, including lighting/post stages.
491
+ dFdx: { arity: [1, 1], rule: 'unary', cost: 1, places: FRAG },
492
+ dFdy: { arity: [1, 1], rule: 'unary', cost: 1, places: FRAG },
493
+ cross: { arity: [2, 2], rule: 'cross', cost: 3 },
494
+ fwidth: { arity: [1, 1], rule: 'unary', cost: 3, places: FRAG },
495
+ normalize: { arity: [1, 1], rule: 'vecUnary', cost: 3 },
496
+ // SF59 step 7: space changes for a vec3 direction. `viewToWorld` turns a view-space direction (`normalView`, faceted
497
+ // under flatShading) into a unit world one; `worldToLocal` turns a world direction into the model space
498
+ // `vertex.offset` is added in (the transpose of the model's 3 × 3 world matrix, unnormalised, as the families do it)
499
+ viewToWorld: { arity: [1, 1], rule: 'dir3', cost: 6, places: MESH },
500
+ // Full local direction through object and instance scale/rotation (no translation).
501
+ // A custom local direction through the mesh's current skeleton; rigid draws retain it.
502
+ skinDirection: { arity: [1, 1], rule: 'dir3', cost: 64, places: VERT },
503
+ localToWorldVector: { arity: [1, 1], rule: 'dir3', cost: 8, places: VERT },
504
+ // The renderer's instance tint, or white for an ordinary/uncoloured instance.
505
+ instanceOrigin: { arity: [0, 0], rule: 'input', cost: 4, type: 'vec3', places: VERT },
506
+ instanceColour: { arity: [0, 0], rule: 'input', cost: 0, type: 'vec3', places: VERT },
507
+ worldToLocal: { arity: [1, 1], rule: 'dir3', cost: 3, places: VERT },
508
+ length: { arity: [1, 1], rule: 'length', cost: 3 },
509
+ dot: { arity: [2, 2], rule: 'dot', cost: 2 },
510
+ mix: { arity: [3, 3], rule: 'mix', cost: 2 },
511
+ clamp: { arity: [3, 3], rule: 'same', cost: 2 },
512
+ smoothstep: { arity: [3, 3], rule: 'same', cost: 5 },
513
+ // comparisons and the branch-light select
514
+ lt: { arity: [2, 2], rule: 'compare', cost: 1 },
515
+ lte: { arity: [2, 2], rule: 'compare', cost: 1 },
516
+ gt: { arity: [2, 2], rule: 'compare', cost: 1 },
517
+ gte: { arity: [2, 2], rule: 'compare', cost: 1 },
518
+ eq: { arity: [2, 2], rule: 'compare', cost: 1 },
519
+ and: { arity: [2, 2], rule: 'logic', cost: 1 },
520
+ or: { arity: [2, 2], rule: 'logic', cost: 1 },
521
+ not: { arity: [1, 1], rule: 'not', cost: 1 },
522
+ select: { arity: [3, 3], rule: 'select', cost: 2 },
523
+ // vectors
524
+ swizzle: { arity: [1, 1], rule: 'swizzle', cost: 0 },
525
+ combine: { arity: [2, 4], rule: 'combine', cost: 0 },
526
+ // MaterialX noise and textures
527
+ noise: { arity: [1, 1], rule: 'noise', cost: 40 },
528
+ meshMap: { arity: [0, 0], rule: 'input', cost: 4, type: 'vec4', places: ['fragment'] },
529
+ texture: { arity: [1, 1], rule: 'texture', cost: 4 },
530
+ textureArray: { arity: [2, 2], rule: 'texture', cost: 6 },
531
+ // Explicit mip and screen gradients keep painted panoramas continuous at a wrap.
532
+ // Trilinear volume read; a lent Data3DTexture, coordinates in [0,1].
533
+ texture3D: { arity: [1, 1], rule: 'texture', cost: 4, places: [...MESH, 'post'] },
534
+ textureLod: { arity: [2, 2], rule: 'texture', cost: 4, places: MESH },
535
+ textureGrad: { arity: [3, 3], rule: 'texture', cost: 4, places: FRAG },
536
+ // the constant-count loop
537
+ loop: { arity: [1, 1], rule: 'loop', cost: 1 },
538
+ acc: { arity: [0, 0], rule: 'acc', cost: 0 },
539
+ index: { arity: [0, 0], rule: 'index', cost: 0 },
540
+ };
541
+
542
+ /** the ops whose value is one directional light's (a node reading one is built per light, and counted per cascade) */
543
+ export const SUN_OPS: ReadonlySet<string> = new Set(['sunDirection', 'sunColour', 'sunShadow']);
544
+
545
+ /**
546
+ * the scene inputs a post graph reads (SF59 (2b)): each is one sampler in its cost; `normal` also needs the engine's
547
+ * normal pre-pass, which a post stack pays once (`POST_NORMAL_PREPASS_COST`) whichever of its passes read it
548
+ */
549
+ export interface PostInputs { readonly colour: boolean; readonly depth: boolean; readonly normal: boolean }
550
+ /** the outcome of validation: the graph (typed) and its cost, or every reason it is refused */
551
+ export type GraphValidation =
552
+ | { readonly ok: true; readonly graph: GraphIr; readonly cost: GraphCost; readonly types: ReadonlyMap<string, GraphValueType>; readonly inputs: PostInputs }
553
+ | { readonly ok: false; readonly errors: readonly string[] };
554
+
555
+ /** what validation checks bindings and the budget against */
556
+ export interface GraphValidationOptions {
557
+ /** the day-key channels a param may bind to (any name when absent) */
558
+ readonly dayKeys?: readonly string[];
559
+ /** the shard's declared public numeric state fields a param may bind to (any name when absent) */
560
+ readonly stateFields?: readonly string[];
561
+ readonly budget?: GraphBudget;
562
+ /** Host authority to use an explicit declaration; content cannot grant this. */
563
+ readonly trustedBudget?: boolean;
564
+ /** the directional lights a `sun` sub-graph is built for (1 … SUN_CASCADES_MAX; SUN_CASCADES when absent) */
565
+ readonly cascades?: number;
566
+ }
567
+
568
+ const VEC_LEN: Readonly<Record<GraphValueType, number>> = { float: 1, vec2: 2, vec3: 3, vec4: 4, bool: 1 };
569
+ const vecOf = (n: number): GraphValueType | null => (n === 1 ? 'float' : n === 2 ? 'vec2' : n === 3 ? 'vec3' : n === 4 ? 'vec4' : null);
570
+ const isNumeric = (t: GraphValueType): boolean => t !== 'bool';
571
+ const ID = /^[A-Za-z_][A-Za-z0-9_]{0,63}$/;
572
+ const SURFACE_TYPES: Readonly<Record<string, GraphValueType>> = { colour: 'vec3', alpha: 'float', targetAlpha: 'float', emissive: 'vec3', roughness: 'float', metalness: 'float', occlusion: 'float', discard: 'bool', normal: 'vec3' };
573
+ /** an `attribute` node's name: `a` and a capital (`aSeed`), so it never names one of three's own attributes */
574
+ export const GRAPH_ATTRIBUTE_NAME: GraphIdentifierPattern = /^a[A-Z][A-Za-z0-9]{0,31}$/;
575
+ /** where a `varying`'s input runs: the vertex program of the place reading it */
576
+ const VARYING_SOURCE: Readonly<Partial<Record<GraphPlace, GraphPlace>>> = { fragment: 'vertex', sun: 'vertex', ambient: 'vertex', grade: 'vertex', outline: 'outlineVertex' };
577
+
578
+ /** an object read from content, with the fields this module looks at named (each still unknown) */
579
+ interface Raw {
580
+ readonly interpolation?: unknown;
581
+ readonly [k: string]: unknown;
582
+ readonly version?: unknown; readonly kind?: unknown; readonly model?: unknown; readonly doubleSided?: unknown; readonly flipBackfaceNormals?: unknown; readonly toonCaustics?: unknown; readonly flatShading?: unknown;
583
+ readonly primitive?: unknown; readonly effectState?: unknown; readonly blend?: unknown; readonly fog?: unknown; readonly output?: unknown;
584
+ readonly depthWrite?: unknown; readonly singlePass?: unknown; readonly depthBias?: unknown; readonly factor?: unknown; readonly units?: unknown; readonly size?: unknown; readonly points?: unknown;
585
+ readonly budget?: unknown; readonly arrays?: unknown; readonly params?: unknown; readonly nodes?: unknown; readonly stages?: unknown;
586
+ readonly type?: unknown; readonly value?: unknown; readonly min?: unknown; readonly max?: unknown; readonly bind?: unknown; readonly role?: unknown;
587
+ readonly day?: unknown; readonly state?: unknown;
588
+ readonly op?: unknown; readonly in?: unknown; readonly param?: unknown; readonly mask?: unknown; readonly count?: unknown;
589
+ readonly body?: unknown; readonly out?: unknown; readonly name?: unknown; readonly discard?: unknown;
590
+ readonly position?: unknown; readonly offset?: unknown; readonly shadow?: unknown; readonly surface?: unknown; readonly post?: unknown;
591
+ readonly lighting?: unknown; readonly colour?: unknown; readonly outline?: unknown;
592
+ readonly sun?: unknown; readonly sunSpecular?: unknown; readonly ambient?: unknown; readonly grade?: unknown;
593
+ }
594
+ const isRecord = (v: unknown): v is Raw => typeof v === 'object' && v !== null && !Array.isArray(v);
595
+ const isFiniteNumber = (v: unknown): v is number => typeof v === 'number' && Number.isFinite(v);
596
+ function literalType(v: unknown): GraphValueType | null {
597
+ if (isFiniteNumber(v)) return 'float';
598
+ if (Array.isArray(v) && v.length >= 2 && v.length <= 4 && v.every(isFiniteNumber)) return vecOf(v.length);
599
+ return null;
600
+ }
601
+ /** the common type of numeric inputs where a float broadcasts, or null when two vectors differ */
602
+ function broadcast(types: readonly GraphValueType[]): GraphValueType | null {
603
+ let out: GraphValueType = 'float';
604
+ for (const t of types) {
605
+ if (!isNumeric(t)) return null;
606
+ if (t === 'float') continue;
607
+ if (out !== 'float' && out !== t) return null;
608
+ out = t;
609
+ }
610
+ return out;
611
+ }
612
+
613
+ /** one scope of nodes: the graph's own, or a loop body's (which reads its parent's) */
614
+ interface Scope {
615
+ readonly nodes: Readonly<Record<string, unknown>>;
616
+ readonly parent: Scope | null;
617
+ readonly loop: { readonly acc: GraphValueType; readonly accDep: boolean } | null;
618
+ readonly path: string;
619
+ }
620
+
621
+ /**
622
+ * Validate a graph read from content (`unknown`): its shape, every op against the vocabulary, every input's type, no
623
+ * cycles, params and bindings, stage placement, and the budget. A valid graph comes back typed with its cost; otherwise
624
+ * every reason it is refused.
625
+ */
626
+ export function validateGraph(input: unknown, opts: GraphValidationOptions = {}): GraphValidation {
627
+ const rawErrors = graphAdmissionErrors(input, opts.trustedBudget === true ? MAX_GRAPH_BUDGET.nodes : opts.budget?.nodes ?? DEFAULT_GRAPH_BUDGET.nodes);
628
+ if (rawErrors.length > 0) return { ok: false, errors: rawErrors };
629
+ const errors: string[] = [];
630
+ const fail = (msg: string): null => { errors.push(msg); return null; };
631
+ let budget = opts.budget ?? DEFAULT_GRAPH_BUDGET;
632
+ const cascades = opts.cascades ?? SUN_CASCADES;
633
+ if (!Number.isInteger(cascades) || cascades < 1 || cascades > SUN_CASCADES_MAX) return { ok: false, errors: [`graph: cascades ${cascades} (1–${SUN_CASCADES_MAX})`] };
634
+ if (!isRecord(input)) return { ok: false, errors: ['graph: not an object'] };
635
+ if (input.budget !== undefined) {
636
+ const declared = input.budget;
637
+ if (!isRecord(declared) || Object.keys(declared).some(key => !['nodes', 'samplers', 'instructions'].includes(key))) return { ok: false, errors: ['graph: budget requires only nodes, samplers and instructions'] };
638
+ for (const key of ['nodes', 'samplers', 'instructions'] as const) {
639
+ const value = declared[key];
640
+ if (typeof value !== 'number' || !Number.isSafeInteger(value) || value < 1 || value > MAX_GRAPH_BUDGET[key]) return { ok: false, errors: [`graph: declared budget ${key} must be 1–${MAX_GRAPH_BUDGET[key]}`] };
641
+ }
642
+ if (opts.trustedBudget !== true) return { ok: false, errors: ['graph: declared budget requires trusted runtime authority'] };
643
+ budget = { nodes: Number(declared.nodes), samplers: Number(declared['samplers']), instructions: Number(declared['instructions']) };
644
+ }
645
+ if (input.version !== GRAPH_IR_VERSION) fail(`graph: version ${String(input.version)} (this engine reads ${GRAPH_IR_VERSION})`);
646
+ const kind = input.kind, primitive = input.primitive;
647
+ if (kind !== 'material' && kind !== 'post' && kind !== 'fog') fail(`graph: kind ${String(kind)} (material, post or fog)`);
648
+ if (kind === 'fog') for (const k of ['model', 'primitive', 'effectState', 'doubleSided', 'flipBackfaceNormals', 'toonCaustics', 'flatShading'] as const) if (input[k] !== undefined) fail(`graph: a fog graph has no ${k}`);
649
+ let postFog: GraphIr['postFog'], postFogCost: GraphCost | undefined;
650
+ const dependency = input['postFog'];
651
+ if (dependency !== undefined) {
652
+ if (kind !== 'post') fail('graph: postFog belongs to a post graph only');
653
+ else if (!isRecord(dependency) || Object.keys(dependency).some(key => key !== 'name' && key !== 'graph')
654
+ || typeof dependency.name !== 'string' || !GRAPH_FOG_ID.test(dependency.name)
655
+ || !isRecord(dependency['graph']) || dependency['graph'].kind !== 'fog') fail('graph: postFog needs a named fog graph');
656
+ else {
657
+ const checked = validateGraph(dependency['graph'], opts);
658
+ if (!checked.ok) for (const error of checked.errors) fail(`postFog: ${error}`);
659
+ else if (checked.graph.stages.fog?.transmission === undefined) fail('postFog: explicit transmission is required');
660
+ else {
661
+ postFog = { name: dependency.name, graph: checked.graph }; postFogCost = checked.cost;
662
+ const own = new Set([...Object.keys(isRecord(input.params) ? input.params : {}), ...Object.keys(isRecord(input.arrays) ? input.arrays : {})]);
663
+ for (const name of [...Object.keys(checked.graph.params ?? {}), ...Object.keys(checked.graph.arrays ?? {})]) if (own.has(name)) fail(`postFog: duplicate parameter ${name}`);
664
+ }
665
+ }
666
+ }
667
+ if (input.model !== undefined && input.model !== 'standard' && input.model !== 'unlit' && input.model !== 'toon') fail(`graph: model ${JSON.stringify(input.model)} (standard, unlit or toon)`);
668
+ if (input.model === 'toon' && input.kind !== 'material') fail('graph: toon is a material lighting model');
669
+ if (input.doubleSided !== undefined && typeof input.doubleSided !== 'boolean') fail('graph: doubleSided is not a boolean');
670
+ if (input.flipBackfaceNormals !== undefined && typeof input.flipBackfaceNormals !== 'boolean') fail('graph: flipBackfaceNormals is not a boolean');
671
+ if (input.flipBackfaceNormals !== undefined && (kind !== 'material' || primitive === 'points')) fail('graph: flipBackfaceNormals requires a mesh material');
672
+ if (input.toonCaustics !== undefined && typeof input.toonCaustics !== 'boolean') fail('graph: toonCaustics is not a boolean');
673
+ if (input.toonCaustics !== undefined && input.model !== 'toon') fail('graph: toonCaustics requires the toon model');
674
+ if (input.flatShading !== undefined && typeof input.flatShading !== 'boolean') fail('graph: flatShading is not a boolean');
675
+ for (const k of Object.keys(input)) if (!['version', 'kind', 'model', 'primitive', 'effectState', 'doubleSided', 'flipBackfaceNormals', 'toonCaustics', 'flatShading', 'budget', 'params', 'arrays', 'nodes', 'stages', 'postFog'].includes(k)) fail(`graph: unknown field ${k}`);
676
+
677
+ if (input.primitive !== undefined && input.primitive !== 'mesh' && input.primitive !== 'points') fail('graph: primitive must be mesh or points');
678
+ if (kind === 'post' && (input.primitive !== undefined || input.effectState !== undefined)) fail('graph: post graphs have no effect state or primitive');
679
+ const effect = input.effectState;
680
+ if (effect !== undefined) {
681
+ if (!isRecord(effect)) fail('graph: effectState is not an object');
682
+ else {
683
+ for (const k of Object.keys(effect)) if (!['blend', 'fog', 'depthWrite', 'depthBias', 'singlePass', 'output'].includes(k)) fail(`effectState: unknown field ${k}`);
684
+ if (effect.output !== undefined && (effect.output !== 'colour' || kind !== 'material' || input.model !== 'unlit' || primitive === 'points' || effect.blend !== undefined || effect.depthWrite === false)) fail('effectState: colour output requires an opaque depth-writing unlit mesh');
685
+ if (effect.blend !== undefined && effect.blend !== 'alpha' && effect.blend !== 'additive' && effect.blend !== 'premultiplied') fail('effectState: unsupported blend');
686
+ if (effect.fog !== undefined && effect.fog !== false && !(typeof effect.fog === 'string' && GRAPH_FOG_ID.test(effect.fog))) fail('effectState: fog must be false or a fog graph\'s catalogue id');
687
+ if (effect.depthWrite !== undefined && typeof effect.depthWrite !== 'boolean') fail('effectState: depthWrite must be boolean');
688
+ if (effect.singlePass !== undefined && effect.singlePass !== true) fail('effectState: singlePass must be true');
689
+ const bias = effect.depthBias;
690
+ if (bias !== undefined) {
691
+ if (!isRecord(bias)) fail('effectState: depthBias is not an object');
692
+ else {
693
+ for (const k of Object.keys(bias)) if (k !== 'factor' && k !== 'units') fail(`effectState.depthBias: unknown field ${k}`);
694
+ for (const k of ['factor', 'units']) if (!isFiniteNumber(bias[k]) || Math.abs(bias[k]) > 16) fail(`effectState.depthBias.${k}: needs a finite number within -16 … 16`);
695
+ }
696
+ }
697
+ }
698
+ }
699
+
700
+ const arrayTypes = new Map<string, GraphArrayParam['type']>();
701
+ const rawArrays = input.arrays ?? {};
702
+ let arraySlots = 0;
703
+ if (!isRecord(rawArrays)) fail('graph: arrays is not an object');
704
+ else for (const [name, row] of Object.entries(rawArrays)) {
705
+ const at = `arrays.${name}`;
706
+ if (!ID.test(name) || !isRecord(row)) { fail(`${at}: a named uniform list is required`); continue; }
707
+ for (const key of Object.keys(row)) if (key !== 'type' && key !== 'value') fail(`${at}: unknown field ${key}`);
708
+ const type = row.type;
709
+ if (type !== 'float' && type !== 'vec2' && type !== 'vec3' && type !== 'vec4') { fail(`${at}: numeric element type required`); continue; }
710
+ const values = row.value;
711
+ if (!Array.isArray(values) || values.length === 0 || values.length > GRAPH_ARRAY_MAX) fail(`${at}: needs 1–${GRAPH_ARRAY_MAX} elements`);
712
+ else {
713
+ arraySlots += values.length;
714
+ if (values.some(value => literalType(value) !== type)) fail(`${at}: every element must be a ${type}`);
715
+ }
716
+ arrayTypes.set(name, type);
717
+ }
718
+ arraySlots += Object.values(postFog?.graph.arrays ?? {}).reduce((sum, row) => sum + row.value.length, 0);
719
+ if (arraySlots > GRAPH_ARRAY_SLOTS_MAX) fail(`arrays: exceeds ${GRAPH_ARRAY_SLOTS_MAX} padded vector slots`);
720
+
721
+ // params
722
+ const paramTypes = new Map<string, GraphParamType>();
723
+ const rawParams = input.params ?? {};
724
+ if (!isRecord(rawParams)) fail('graph: params is not an object');
725
+ else for (const [name, p] of Object.entries(rawParams)) {
726
+ const at = `params.${name}`;
727
+ if (!ID.test(name)) { fail(`${at}: not an identifier`); continue; }
728
+ if (!isRecord(p)) { fail(`${at}: not an object`); continue; }
729
+ const type = p.type;
730
+ if (type !== 'float' && type !== 'vec2' && type !== 'vec3' && type !== 'vec4' && type !== 'colour' && type !== 'texture') { fail(`${at}: type ${String(type)}`); continue; }
731
+ if (p.role !== undefined && (type !== 'texture' || (p.role !== 'colour' && p.role !== 'data' && p.role !== 'lent'))) fail(`${at}: role is colour, data or lent on a texture only`);
732
+ if (type === 'texture') {
733
+ if (typeof p.value !== 'string' || p.value.length === 0) fail(`${at}: a texture names an admitted file`);
734
+ if (p.bind !== undefined) fail(`${at}: a texture cannot be bound (a binding moves a uniform value)`);
735
+ } else {
736
+ const lt = literalType(p.value), want = type === 'colour' ? 'vec3' : type;
737
+ if (lt !== want) fail(`${at}: value is not a ${want}`);
738
+ for (const bound of ['min', 'max'] as const) if (p[bound] !== undefined && !isFiniteNumber(p[bound])) fail(`${at}: ${bound} is not a number`);
739
+ const vals = isFiniteNumber(p.value) ? [p.value] : Array.isArray(p.value) ? p.value.filter(isFiniteNumber) : [];
740
+ const lo = p.min, hi = p.max;
741
+ if (isFiniteNumber(lo) && vals.some((v) => v < lo)) fail(`${at}: value below min`);
742
+ if (isFiniteNumber(hi) && vals.some((v) => v > hi)) fail(`${at}: value above max`);
743
+ if (p.bind !== undefined) {
744
+ const b = p.bind;
745
+ if (!isRecord(b) || Object.keys(b).length !== 1) fail(`${at}: bind is { day } or { state }`);
746
+ else if (typeof b.day === 'string') { if (opts.dayKeys && !opts.dayKeys.includes(b.day)) fail(`${at}: unknown day key ${b.day}`); }
747
+ else if (typeof b.state === 'string') { if (opts.stateFields && !opts.stateFields.includes(b.state)) fail(`${at}: undeclared state field ${b.state}`); }
748
+ else fail(`${at}: bind is { day } or { state }`);
749
+ }
750
+ }
751
+ paramTypes.set(name, type);
752
+ }
753
+
754
+ if (!isRecord(input.nodes)) return { ok: false, errors: [...errors, 'graph: nodes is not an object'] };
755
+ const root: Scope = { nodes: input.nodes, parent: null, loop: null, path: 'nodes' };
756
+ const types = new Map<string, GraphValueType>(); // `<scope path>/<id>` → type
757
+ const visiting = new Set<string>();
758
+ const textures = new Set<string>();
759
+ const textureDimensions = new Map<string, '2d' | 'array' | '3d'>();
760
+ const sampled = { scene: false, depth: false, normal: false, mesh: false }; // set while typing (a post graph reads a scene input)
761
+ const counted = new Set<string>();
762
+ /** `<scope path>/<id>` → whether the node reads a sun input (directly, through its inputs or a loop body) */
763
+ const lightDep = new Map<string, boolean>();
764
+ let loopDep = false; // set by the loop rule for the loop node being typed
765
+ let nodeCount = 0, instructions = 0;
766
+
767
+ const find = (scope: Scope, id: string): { scope: Scope; node: unknown } | null => {
768
+ for (let s: Scope | null = scope; s !== null; s = s.parent) if (Object.hasOwn(s.nodes, id)) return { scope: s, node: s.nodes[id] };
769
+ return null;
770
+ };
771
+
772
+ /** type a reference from `scope`, at `place`, adding its cost once (times `mult` inside loops) */
773
+ /** whether a typed reference reads a sun input */
774
+ const refDep = (ref: unknown, scope: Scope): boolean => {
775
+ if (typeof ref !== 'string') return false;
776
+ const hit = find(scope, ref);
777
+ return hit !== null && lightDep.get(`${hit.scope.path}/${ref}`) === true;
778
+ };
779
+
780
+ function typeRef(ref: unknown, scope: Scope, place: GraphPlace, at: string, mult: number): GraphValueType | null {
781
+ if (typeof ref !== 'string') return literalType(ref) ?? fail(`${at}: not a node id or a 1–4 number literal`);
782
+ const hit = find(scope, ref);
783
+ if (hit === null) return fail(`${at}: unknown node ${ref}`);
784
+ return typeNode(ref, hit.node, hit.scope, place, mult);
785
+ }
786
+
787
+ function typeNode(id: string, raw: unknown, scope: Scope, place: GraphPlace, mult: number): GraphValueType | null {
788
+ const key = `${scope.path}/${id}`, at = `${scope.path}.${id}`;
789
+ const known = types.get(key);
790
+ if (known !== undefined && counted.has(`${key}@${place}`)) return known;
791
+ if (visiting.has(key)) return fail(`${at}: cycle`);
792
+ if (!isRecord(raw)) return fail(`${at}: not an object`);
793
+ const op = raw.op;
794
+ if (typeof op !== 'string' || !Object.hasOwn(GRAPH_OPS, op)) return fail(`${at}: unknown op ${String(op)}`);
795
+ const spec = GRAPH_OPS[op];
796
+ if (spec === undefined) return fail(`${at}: unknown op ${op}`);
797
+ if (raw['default'] !== undefined && op !== 'attribute') return fail(`${at}: default belongs to an attribute only`);
798
+ if (raw.interpolation !== undefined && (op !== 'varying' || raw.interpolation !== 'flat')) return fail(`${at}: interpolation is flat on a varying only`);
799
+ if (spec.places && !spec.places.includes(place)) return fail(`${at}: ${op} cannot run in the ${place} stage`);
800
+ if (op === 'pointCoord' && primitive !== 'points') return fail(`${at}: pointCoord needs a points primitive`);
801
+ const ins = raw.in ?? [];
802
+ if (!Array.isArray(ins)) return fail(`${at}: in is not a list`);
803
+ if (ins.length < spec.arity[0] || ins.length > spec.arity[1]) return fail(`${at}: ${op} takes ${spec.arity[0] === spec.arity[1] ? spec.arity[0] : spec.arity.join('–')} inputs, got ${ins.length}`);
804
+ visiting.add(key);
805
+ const inputPlace = spec.rule === 'varying' ? VARYING_SOURCE[place] ?? place : place;
806
+ const inner = spec.rule === 'loop' ? [] : ins.map((r, i) => typeRef(r, scope, inputPlace, `${at}.in[${i}]`, mult));
807
+ const t = inner.some((x) => x === null) ? null : resolve(op, spec, raw, inner.filter((x): x is GraphValueType => x !== null), scope, place, at, ins, mult);
808
+ visiting.delete(key);
809
+ if (t !== null) {
810
+ types.set(key, t);
811
+ const dep = spec.rule === 'input' ? SUN_OPS.has(op)
812
+ : spec.rule === 'acc' ? scope.loop?.accDep === true
813
+ : spec.rule === 'loop' ? loopDep
814
+ : ins.some((r) => refDep(r, scope));
815
+ lightDep.set(key, dep);
816
+ if (!counted.has(`${key}@${place}`)) {
817
+ counted.add(`${key}@${place}`);
818
+ nodeCount += mult;
819
+ // a component-wise op costs once per component; a light-dependent node of the sun sub-graph once per cascade
820
+ const lanes = spec.rule === 'same' || spec.rule === 'unary' ? VEC_LEN[t] : 1;
821
+ instructions += (spec.cost + (op === 'fogSample' ? postFogCost?.instructions ?? 0 : 0)) * mult * lanes * (place === 'sun' && dep ? cascades : 1);
822
+ }
823
+ }
824
+ return t;
825
+ }
826
+
827
+ function resolve(op: string, spec: GraphOpSpec, raw: Raw, ins: readonly GraphValueType[], scope: Scope, place: GraphPlace, at: string, rawIns: readonly unknown[], mult: number): GraphValueType | null {
828
+ switch (spec.rule) {
829
+ case 'fogSample':
830
+ if (postFog === undefined || raw.name !== postFog.name) return fail(`${at}: fogSample needs its declared postFog name`);
831
+ return ins.length === 5 && ins.slice(0, 4).every(type => type === 'vec3') && ins[4] === 'float' ? 'vec4' : fail(`${at}: fogSample takes four vec3 values and a float`);
832
+ case 'input':
833
+ if (op === 'sceneColour') sampled.scene = true;
834
+ if (op === 'meshMap') {
835
+ if (kind !== 'material' || primitive === 'points') return fail(`${at}: meshMap requires a mesh material`);
836
+ if (raw.name !== undefined && raw.name !== 'bump' && raw.name !== 'colourOrVertex') return fail(`${at}: meshMap name ${JSON.stringify(raw.name)} (bump, colourOrVertex or none)`);
837
+ sampled.mesh = true;
838
+ }
839
+ return spec.type ?? null;
840
+ case 'const': return literalType(raw.value) ?? fail(`${at}: const value is not a 1–4 number literal`);
841
+ case 'param': {
842
+ const name = raw.param;
843
+ if (typeof name !== 'string') return fail(`${at}: param names a param`);
844
+ const pt = paramTypes.get(name);
845
+ if (pt === undefined) return fail(`${at}: unknown param ${name}`);
846
+ if (pt === 'texture') return fail(`${at}: a texture param is read by a texture node`);
847
+ return pt === 'colour' ? 'vec3' : pt;
848
+ }
849
+ case 'arrayElement': {
850
+ const type = typeof raw.param === 'string' ? arrayTypes.get(raw.param) : undefined;
851
+ if (type === undefined) return fail(`${at}: arrayElement names a numeric uniform list`);
852
+ return ins[0] === 'float' ? type : fail(`${at}: arrayElement index must be a float`);
853
+ }
854
+ case 'same': return broadcast(ins) ?? fail(`${at}: ${op} inputs ${ins.join(', ')} do not match`);
855
+ case 'unary': return isNumeric(ins[0] ?? 'bool') ? (ins[0] ?? null) : fail(`${at}: ${op} takes a number`);
856
+ case 'vecUnary': return ins[0] !== undefined && VEC_LEN[ins[0]] > 1 && isNumeric(ins[0]) ? ins[0] : fail(`${at}: ${op} takes a vector`);
857
+ case 'length': return ins[0] !== undefined && isNumeric(ins[0]) ? 'float' : fail(`${at}: length takes a number`);
858
+ case 'cross': return ins[0] === 'vec3' && ins[1] === 'vec3' ? 'vec3' : fail(`${at}: cross takes two vec3 values`);
859
+ case 'windField': return ins[0] === 'vec2' ? 'float' : fail(`${at}: windGustAt takes world xz (vec2)`);
860
+ case 'instanceTransform': return ins[0] === 'vec4' ? 'vec4' : fail(`${at}: instanceTransform takes a vec4`);
861
+ case 'gerstner': return ins[0] === 'vec2' && ins[1] === 'float' && ins[2] === 'float' ? 'vec3' : fail(`${at}: gerstner takes vec2, float, float`);
862
+ case 'dot': return ins[0] !== undefined && ins[0] === ins[1] && VEC_LEN[ins[0]] > 1 && isNumeric(ins[0]) ? 'float' : fail(`${at}: dot takes two vectors of one size`);
863
+ case 'mix': {
864
+ const t = broadcast([ins[0] ?? 'bool', ins[1] ?? 'bool']);
865
+ if (t === null) return fail(`${at}: mix inputs ${ins.join(', ')} do not match`);
866
+ return ins[2] === 'float' || ins[2] === t ? t : fail(`${at}: mix factor is ${String(ins[2])}`);
867
+ }
868
+ case 'compare': return ins[0] === 'float' && ins[1] === 'float' ? 'bool' : fail(`${at}: ${op} compares two floats`);
869
+ case 'logic': return ins[0] === 'bool' && ins[1] === 'bool' ? 'bool' : fail(`${at}: ${op} takes two bools`);
870
+ case 'not': return ins[0] === 'bool' ? 'bool' : fail(`${at}: not takes a bool`);
871
+ case 'select': {
872
+ if (ins[0] !== 'bool') return fail(`${at}: select's condition is ${String(ins[0])}, not a bool`);
873
+ if (ins[1] === 'bool' && ins[2] === 'bool') return 'bool';
874
+ return broadcast([ins[1] ?? 'bool', ins[2] ?? 'bool']) ?? fail(`${at}: select sides ${String(ins[1])}, ${String(ins[2])} do not match`);
875
+ }
876
+ case 'swizzle': {
877
+ const mask = raw.mask, src = ins[0];
878
+ if (src === undefined || !isNumeric(src)) return fail(`${at}: swizzle takes a number`);
879
+ if (typeof mask !== 'string' || !/^[xyzw]{1,4}$/.test(mask)) return fail(`${at}: swizzle mask ${String(mask)}`);
880
+ if (mask.split('').some((c) => 'xyzw'.indexOf(c) >= VEC_LEN[src])) return fail(`${at}: mask ${mask} reads past a ${src}`);
881
+ return vecOf(mask.length);
882
+ }
883
+ case 'combine': {
884
+ if (ins.some((t) => !isNumeric(t))) return fail(`${at}: combine takes numbers`);
885
+ const n = ins.reduce((s, t) => s + VEC_LEN[t], 0);
886
+ return n >= 2 && n <= 4 ? vecOf(n) : fail(`${at}: combine makes ${n} components (2–4)`);
887
+ }
888
+ case 'noise': return ins[0] === 'vec2' || ins[0] === 'vec3' ? 'float' : fail(`${at}: noise takes a vec2 or vec3`);
889
+ case 'cameraBillboard': return ins[0] === 'vec3' && ins[1] === 'float' && ins[2] === 'vec2'
890
+ ? 'vec3' : fail(`${at}: ${op} needs world direction vec3, distance float and card-local vec2`);
891
+ case 'pointPair': return ins.every(t => t === 'vec3') ? spec.type ?? null : fail(`${at}: ${op} takes two vec3 points`);
892
+ case 'point3': return ins[0] === 'vec3' ? spec.type ?? null : fail(`${at}: ${op} takes a vec3 point`);
893
+ case 'dir3': return ins[0] === 'vec3' ? 'vec3' : fail(`${at}: ${op} takes a vec3 direction`);
894
+ case 'varying': return ins[0] !== undefined && isNumeric(ins[0]) ? ins[0] : fail(`${at}: a varying carries a number`);
895
+ case 'attribute': {
896
+ const name = raw.name, type = raw.type;
897
+ if (typeof name !== 'string' || !GRAPH_ATTRIBUTE_NAME.test(name)) return fail(`${at}: attribute name ${String(name)} (a, then a capital: aSeed)`);
898
+ if (type !== 'float' && type !== 'vec2' && type !== 'vec3' && type !== 'vec4') return fail(`${at}: attribute type ${String(type)} (float, vec2, vec3 or vec4)`);
899
+ if (raw['default'] !== undefined && literalType(raw['default']) !== type) return fail(`${at}: attribute default must be a finite ${type}`);
900
+ return type;
901
+ }
902
+ case 'sceneTap':
903
+ if (ins.length === 1 && ins[0] !== 'vec2') return fail(`${at}: ${op} samples at a vec2`);
904
+ if (op === 'sceneDepth') sampled.depth = true; else if (op === 'sceneNormal') sampled.normal = true; else sampled.scene = true;
905
+ return spec.type ?? null;
906
+ case 'texture': {
907
+ const name = raw.param;
908
+ if (typeof name !== 'string' || paramTypes.get(name) !== 'texture') return fail(`${at}: texture names a texture param`);
909
+ if (ins[0] !== (op === 'texture3D' ? 'vec3' : 'vec2')) return fail(`${at}: ${op} samples at a ${op === 'texture3D' ? 'vec3' : 'vec2'}`);
910
+ if (op === 'textureLod' && ins[1] !== 'float') return fail(`${at}: textureLod needs a numeric mip`);
911
+ if (op === 'textureGrad' && (ins[1] !== 'vec2' || ins[2] !== 'vec2')) return fail(`${at}: textureGrad needs two vec2 gradients`);
912
+ const dimension = op === 'textureArray' ? 'array' : op === 'texture3D' ? '3d' : '2d';
913
+ if (dimension === '3d' && (!isRecord(rawParams) || !isRecord(rawParams[name]) || rawParams[name].role !== 'lent')) return fail(`${at}: texture3D needs a lent texture param`);
914
+ if (dimension === 'array') {
915
+ const param = isRecord(rawParams) ? rawParams[name] : undefined;
916
+ if (ins[1] !== 'float' || !isRecord(param) || param.role !== 'lent') return fail(`${at}: textureArray needs a numeric layer and a lent texture param`);
917
+ }
918
+ const previous = textureDimensions.get(name);
919
+ if (previous !== undefined && previous !== dimension) return fail(`${at}: a texture param cannot mix sample dimensions`);
920
+ textureDimensions.set(name, dimension);
921
+ textures.add(name);
922
+ return 'vec4';
923
+ }
924
+ case 'acc': return scope.loop?.acc ?? fail(`${at}: acc outside a loop body`);
925
+ case 'index': return scope.loop !== null ? 'float' : fail(`${at}: index outside a loop body`);
926
+ case 'loop': {
927
+ const count = raw.count;
928
+ if (!Number.isInteger(count) || typeof count !== 'number' || count < 1 || count > LOOP_MAX) return fail(`${at}: loop count ${String(count)} (a constant, 1–${LOOP_MAX})`);
929
+ const init = typeRef(rawIns[0], scope, place, `${at}.in[0]`, mult);
930
+ if (init === null) return null;
931
+ if (init === 'bool') return fail(`${at}: a loop accumulates a number`);
932
+ const body = raw.body;
933
+ if (!isRecord(body) || !isRecord(body.nodes)) return fail(`${at}: loop body is { nodes, out }`);
934
+ const initDep = refDep(rawIns[0], scope);
935
+ const bodyScope: Scope = { nodes: body.nodes, parent: scope, loop: { acc: init, accDep: initDep }, path: `${at}.body` };
936
+ const out = typeRef(body.out, bodyScope, place, `${at}.body.out`, mult * count);
937
+ if (out === null) return null;
938
+ loopDep = initDep || refDep(body.out, bodyScope);
939
+ return out === init ? init : fail(`${at}: the body makes a ${out}, the accumulator is a ${init}`);
940
+ }
941
+ default: return fail(`${at}: op ${op} has no type rule`);
942
+ }
943
+ }
944
+
945
+ // stages
946
+ const stages = input.stages;
947
+ if (!isRecord(stages)) return { ok: false, errors: [...errors, 'graph: stages is not an object'] };
948
+ for (const k of Object.keys(stages)) if (!['vertex.offset', 'vertex.position', 'vertex.clip', 'vertex.normal', 'vertex.scale', 'shadow.offset', 'surface', 'lighting', 'outline', 'points', 'post', 'fog'].includes(k)) fail(`stages: unknown stage ${k}`);
949
+ if (kind !== 'fog' && stages.fog !== undefined) fail('stages.fog: only a fog graph has a fog stage');
950
+ const want = (ref: unknown, place: GraphPlace, type: GraphValueType, at: string): void => {
951
+ const t = typeRef(ref, root, place, at, 1);
952
+ if (t !== null && t !== type && !(t === 'float' && type !== 'bool')) fail(`${at}: is a ${t}, wants a ${type}`);
953
+ };
954
+ if (kind === 'material') {
955
+ if (stages.post !== undefined) fail('stages.post: a material graph has no post stage');
956
+ const points = stages.points;
957
+ if (input.primitive === 'points') {
958
+ if (input.model !== 'unlit') fail('graph: points require an unlit model');
959
+ if (stages.outline !== undefined) fail('graph: points cannot have a mesh outline');
960
+ if (!isRecord(points)) fail('stages.points: points need { size }');
961
+ else {
962
+ for (const k of Object.keys(points)) if (k !== 'size') fail(`stages.points: unknown output ${k}`);
963
+ want(points.size, 'vertex', 'float', 'stages.points.size');
964
+ }
965
+ } else if (points !== undefined) fail('stages.points: needs a points primitive');
966
+ const scale = stages['vertex.scale'];
967
+ if (scale !== undefined) {
968
+ if (!isRecord(scale)) fail('stages.vertex.scale: not an object');
969
+ else {
970
+ if (Object.keys(scale).length === 0) fail('stages.vertex.scale: needs a factor');
971
+ for (const key of Object.keys(scale)) if (key !== 'sun' && key !== 'fog') fail(`stages.vertex.scale: unknown output ${key}`);
972
+ for (const key of ['sun', 'fog'] as const) if (scale[key] !== undefined) {
973
+ if (key === 'sun' && input.model === 'unlit') fail('stages.vertex.scale.sun: needs a lit model');
974
+ want(scale[key], 'vertex', 'float', `stages.vertex.scale.${key}`);
975
+ }
976
+ }
977
+ }
978
+ const vn = stages['vertex.normal'];
979
+ if (vn !== undefined) {
980
+ if (input.primitive === 'points') fail('stages.vertex.normal: mesh normals only');
981
+ if (!isRecord(vn)) fail('stages.vertex.normal: not an object');
982
+ else {
983
+ for (const key of Object.keys(vn)) if (key !== 'normal') fail(`stages.vertex.normal: unknown output ${key}`);
984
+ want(vn['normal'], 'vertex', 'vec3', 'stages.vertex.normal.normal');
985
+ }
986
+ }
987
+ const clip = stages['vertex.clip'];
988
+ if (clip !== undefined) {
989
+ if (stages['vertex.position'] !== undefined || stages['vertex.offset'] !== undefined) fail('stages: vertex.clip excludes model-space position and offset');
990
+ if (!isRecord(clip)) fail('stages.vertex.clip: not an object');
991
+ else {
992
+ for (const key of Object.keys(clip)) if (key !== 'position') fail(`stages.vertex.clip: unknown output ${key}`);
993
+ const type = typeRef(clip.position, root, 'vertex', 'stages.vertex.clip.position', 1);
994
+ if (type !== null && type !== 'vec4') fail('stages.vertex.clip.position: wants a vec4');
995
+ }
996
+ }
997
+ const vp = stages['vertex.position'];
998
+ if (vp !== undefined) {
999
+ if (stages['vertex.offset'] !== undefined) fail('stages: vertex.position and vertex.offset are exclusive');
1000
+ if (!isRecord(vp)) fail('stages.vertex.position: not an object');
1001
+ else {
1002
+ for (const key of Object.keys(vp)) if (key !== 'position' && key !== 'shadow') fail(`stages.vertex.position: unknown output ${key}`);
1003
+ if (vp.shadow !== undefined && typeof vp.shadow !== 'boolean') fail('stages.vertex.position.shadow: a boolean');
1004
+ if (vp.shadow === true && input.primitive === 'points') fail('stages.vertex.position.shadow: mesh casters only');
1005
+ if (vp.shadow === true && stages['vertex.clip'] !== undefined) fail('stages.vertex.position.shadow: clip-position has no matching shadow camera');
1006
+ want(vp.position, 'vertex', 'vec3', 'stages.vertex.position.position');
1007
+ }
1008
+ }
1009
+ const vo = stages['vertex.offset'];
1010
+ if (vo !== undefined) {
1011
+ if (!isRecord(vo)) fail('stages.vertex.offset: not an object');
1012
+ else {
1013
+ want(vo.offset, 'vertex', 'vec3', 'stages.vertex.offset.offset');
1014
+ if (vo.shadow !== undefined && typeof vo.shadow !== 'boolean') fail('stages.vertex.offset.shadow: a boolean');
1015
+ if (vo.shadow === true && input.primitive === 'points') fail('stages.vertex.offset.shadow: mesh casters only');
1016
+ if (vo.shadow === true && stages['vertex.clip'] !== undefined) fail('stages.vertex.offset.shadow: clip-position has no matching shadow camera');
1017
+ }
1018
+ }
1019
+ const shadowOffset = stages['shadow.offset'];
1020
+ if (shadowOffset !== undefined) {
1021
+ if (input.primitive === 'points' || input.model === 'unlit') fail('stages.shadow.offset: lit mesh only');
1022
+ if (!isRecord(shadowOffset)) fail('stages.shadow.offset: not an object');
1023
+ else {
1024
+ for (const key of Object.keys(shadowOffset)) if (key !== 'offset') fail(`stages.shadow.offset: unknown output ${key}`);
1025
+ // per vertex, as both back-ends evaluate it: the classic program moves three's per-vertex shadow coordinates by it
1026
+ // (no varying, derivative or facing reads), and TSL's received-shadow position interpolates the same value
1027
+ want(shadowOffset.offset, 'vertex', 'vec3', 'stages.shadow.offset.offset');
1028
+ }
1029
+ }
1030
+ const s = stages.surface;
1031
+ if (s === undefined) fail('stages.surface: a material graph needs a surface');
1032
+ else if (!isRecord(s)) fail('stages.surface: not an object');
1033
+ else for (const [k, v] of Object.entries(s)) {
1034
+ if (k === 'alphaCutoff') { if (!isFiniteNumber(v) || v < 0 || v > 1) fail('stages.surface.alphaCutoff: a number 0–1'); continue; }
1035
+ const t = SURFACE_TYPES[k];
1036
+ if (t === undefined) { fail(`stages.surface: unknown output ${k}`); continue; }
1037
+ if (k === 'normal' && input.model === 'unlit') fail('stages.surface.normal: an unlit graph has no shading normal');
1038
+ want(v, 'fragment', t, `stages.surface.${k}`);
1039
+ }
1040
+ if (isRecord(s) && s['targetAlpha'] !== undefined && (s['alpha'] !== undefined || (isRecord(input.effectState) && input.effectState.blend !== undefined))) fail('stages.surface.targetAlpha: an opaque draw only (no alpha, no blend)');
1041
+ if (isRecord(s) && isRecord(input.effectState) && input.effectState.output === 'colour' && (s['targetAlpha'] !== undefined || s['alpha'] !== undefined)) fail('stages.surface: colour output owns the opaque alpha channel');
1042
+ const lit = stages.lighting;
1043
+ if (lit !== undefined) {
1044
+ if (input.model === 'toon') fail('stages.lighting: toon supplies its family lighting; use standard for an authored lighting stage');
1045
+ if (input.model === 'unlit') fail('stages.lighting: an unlit graph has no lighting model');
1046
+ if (!isRecord(lit)) fail('stages.lighting: not an object');
1047
+ else {
1048
+ for (const k of Object.keys(lit)) if (!['sun', 'sunSpecular', 'ambient', 'grade'].includes(k)) fail(`stages.lighting: unknown output ${k}`);
1049
+ if (lit.sun === undefined) fail('stages.lighting: a lighting model needs sun');
1050
+ else want(lit.sun, 'sun', 'vec3', 'stages.lighting.sun');
1051
+ if (lit.sunSpecular !== undefined) want(lit.sunSpecular, 'sun', 'float', 'stages.lighting.sunSpecular');
1052
+ if (lit.ambient !== undefined) want(lit.ambient, 'ambient', 'vec3', 'stages.lighting.ambient');
1053
+ if (lit.grade !== undefined) want(lit.grade, 'grade', 'vec3', 'stages.lighting.grade');
1054
+ }
1055
+ }
1056
+ const ol = stages.outline;
1057
+ if (ol !== undefined) {
1058
+ if (!isRecord(ol)) fail('stages.outline: not an object');
1059
+ else {
1060
+ for (const k of Object.keys(ol)) if (k !== 'offset' && k !== 'colour') fail(`stages.outline: unknown field ${k} (the render state is fixed: back faces, depth on, opaque, unlit)`);
1061
+ if (ol.offset === undefined || ol.colour === undefined) fail('stages.outline: an outline is { offset, colour }');
1062
+ else {
1063
+ want(ol.offset, 'outlineVertex', 'vec3', 'stages.outline.offset');
1064
+ want(ol.colour, 'outline', 'vec3', 'stages.outline.colour');
1065
+ }
1066
+ }
1067
+ }
1068
+ } else if (kind === 'post') {
1069
+ for (const k of ['vertex.offset', 'vertex.position', 'vertex.clip', 'vertex.normal', 'vertex.scale', 'shadow.offset', 'surface', 'lighting', 'outline', 'points'] as const) if (stages[k] !== undefined) fail(`stages.${k}: a post graph has only a post stage`);
1070
+ const p = stages.post;
1071
+ if (!isRecord(p)) fail('stages.post: a post graph needs { colour }');
1072
+ else {
1073
+ want(p.colour, 'post', 'vec3', 'stages.post.colour');
1074
+ if (p['alpha'] !== undefined) want(p['alpha'], 'post', 'float', 'stages.post.alpha');
1075
+ }
1076
+ } else if (kind === 'fog') {
1077
+ for (const k of Object.keys(stages)) if (k !== 'fog') fail(`stages.${k}: a fog graph has only a fog stage`);
1078
+ const f = stages.fog;
1079
+ if (!isRecord(f) || Object.keys(f).some((k) => k !== 'colour' && k !== 'transmission')) fail('stages.fog: a fog graph needs { colour, transmission? }');
1080
+ else {
1081
+ want(f.colour, 'fog', 'vec3', 'stages.fog.colour');
1082
+ if (f['transmission'] !== undefined) want(f['transmission'], 'fog', 'float', 'stages.fog.transmission');
1083
+ }
1084
+ }
1085
+
1086
+ const cost: GraphCost = { nodes: nodeCount, samplers: textures.size + [sampled.scene, sampled.depth, sampled.normal, sampled.mesh].filter(Boolean).length + (postFogCost?.samplers ?? 0), instructions };
1087
+ if (cost.nodes > budget.nodes) fail(`budget: ${cost.nodes} nodes (at most ${budget.nodes})`);
1088
+ if (cost.samplers > budget.samplers) fail(`budget: ${cost.samplers} samplers (at most ${budget.samplers})`);
1089
+ if (cost.instructions > budget.instructions) fail(`budget: ≈ ${cost.instructions} instructions (at most ${budget.instructions})`);
1090
+ if (errors.length > 0) return { ok: false, errors };
1091
+ const rootTypes = new Map<string, GraphValueType>();
1092
+ for (const [key, t] of types) if (key.startsWith('nodes/')) rootTypes.set(key.slice(6), t);
1093
+ // the shape has been checked field by field above, so the input is the graph
1094
+ return { ok: true, graph: asGraph(input, postFog), cost, types: rootTypes, inputs: { colour: sampled.scene, depth: sampled.depth, normal: sampled.normal } };
1095
+ }
1096
+
1097
+ /** a checked record as the typed graph (called only once validateGraph found no error) */
1098
+ function asGraph(input: Raw, postFog?: GraphIr['postFog']): GraphIr {
1099
+ const graph: GraphIr = {
1100
+ version: GRAPH_IR_VERSION,
1101
+ ...(postFog === undefined ? {} : { postFog }),
1102
+ ...(isRecord(input.budget) ? { budget: { nodes: Number(input.budget.nodes), samplers: Number(input.budget['samplers']), instructions: Number(input.budget['instructions']) } } : {}),
1103
+ kind: input.kind === 'post' ? 'post' : input.kind === 'fog' ? 'fog' : 'material',
1104
+ nodes: isRecord(input.nodes) ? toNodes(input.nodes) : {},
1105
+ stages: isRecord(input.stages) ? toStages(input.stages) : {},
1106
+ ...(input.primitive === 'mesh' || input.primitive === 'points' ? { primitive: input.primitive } : {}),
1107
+ ...(isRecord(input.effectState) ? { effectState: toEffectState(input.effectState) } : {}),
1108
+ ...(input.model === 'standard' || input.model === 'unlit' || input.model === 'toon' ? { model: input.model } : {}),
1109
+ ...(typeof input.doubleSided === 'boolean' ? { doubleSided: input.doubleSided } : {}),
1110
+ ...(typeof input.flipBackfaceNormals === 'boolean' ? { flipBackfaceNormals: input.flipBackfaceNormals } : {}),
1111
+ ...(typeof input.toonCaustics === 'boolean' ? { toonCaustics: input.toonCaustics } : {}),
1112
+ ...(typeof input.flatShading === 'boolean' ? { flatShading: input.flatShading } : {}),
1113
+ ...(isRecord(input.params) ? { params: toParams(input.params) } : {}),
1114
+ ...(isRecord(input.arrays) ? { arrays: toArrays(input.arrays) } : {}),
1115
+ };
1116
+ return graph;
1117
+ }
1118
+ function toEffectState(raw: Raw): GraphEffectState {
1119
+ const bias = raw.depthBias;
1120
+ return {
1121
+ ...(raw.output === 'colour' ? { output: raw.output } : {}),
1122
+ ...(raw.blend === 'alpha' || raw.blend === 'additive' || raw.blend === 'premultiplied' ? { blend: raw.blend } : {}),
1123
+ ...(raw.fog === false || typeof raw.fog === 'string' ? { fog: raw.fog } : {}),
1124
+ ...(typeof raw.depthWrite === 'boolean' ? { depthWrite: raw.depthWrite } : {}),
1125
+ ...(raw.singlePass === true ? { singlePass: true } : {}),
1126
+ ...(isRecord(bias) && isFiniteNumber(bias.factor) && isFiniteNumber(bias.units) ? { depthBias: { factor: bias.factor, units: bias.units } } : {}),
1127
+ };
1128
+ }
1129
+ const toRef = (v: unknown): GraphRef => (typeof v === 'string' ? v : isFiniteNumber(v) ? v : Array.isArray(v) ? v.filter(isFiniteNumber) : 0);
1130
+ function toNodes(raw: Raw): Record<string, GraphNode> {
1131
+ const out: Record<string, GraphNode> = {};
1132
+ for (const [id, n] of Object.entries(raw)) {
1133
+ if (!isRecord(n) || typeof n.op !== 'string') continue;
1134
+ out[id] = {
1135
+ op: n.op,
1136
+ ...(Array.isArray(n.in) ? { in: n.in.map(toRef) } : {}),
1137
+ ...(n.value !== undefined ? { value: isFiniteNumber(n.value) ? n.value : Array.isArray(n.value) ? n.value.filter(isFiniteNumber) : 0 } : {}),
1138
+ ...(typeof n.param === 'string' ? { param: n.param } : {}),
1139
+ ...(typeof n.mask === 'string' ? { mask: n.mask } : {}),
1140
+ ...(n['default'] !== undefined ? { default: isFiniteNumber(n['default']) ? n['default'] : Array.isArray(n['default']) ? n['default'].filter(isFiniteNumber) : 0 } : {}),
1141
+ ...(n.interpolation === 'flat' ? { interpolation: 'flat' } : {}),
1142
+ ...(typeof n.count === 'number' ? { count: n.count } : {}),
1143
+ ...(typeof n.name === 'string' ? { name: n.name } : {}),
1144
+ ...(n.type === 'float' || n.type === 'vec2' || n.type === 'vec3' || n.type === 'vec4' ? { type: n.type } : {}),
1145
+ ...(isRecord(n.body) && isRecord(n.body.nodes) ? { body: { nodes: toNodes(n.body.nodes), out: toRef(n.body.out) } } : {}),
1146
+ };
1147
+ }
1148
+ return out;
1149
+ }
1150
+ function toStages(raw: Raw): GraphStages {
1151
+ const scale = raw['vertex.scale'];
1152
+ const vn = raw['vertex.normal'];
1153
+ const clip = raw['vertex.clip'];
1154
+ const vp = raw['vertex.position'], vo = raw['vertex.offset'], s = raw.surface, p = raw.post, l = raw.lighting, o = raw.outline;
1155
+ const surface: Record<string, GraphRef | number> = {};
1156
+ if (isRecord(s)) for (const [k, v] of Object.entries(s)) surface[k] = k === 'alphaCutoff' && isFiniteNumber(v) ? v : toRef(v);
1157
+ return {
1158
+ ...(isRecord(raw['shadow.offset']) ? { 'shadow.offset': { offset: toRef(raw['shadow.offset'].offset) } } : {}),
1159
+ ...(isRecord(raw.points) ? { points: { size: toRef(raw.points.size) } } : {}),
1160
+ ...(isRecord(scale) ? { 'vertex.scale': { ...(scale.sun !== undefined ? { sun: toRef(scale.sun) } : {}), ...(scale.fog !== undefined ? { fog: toRef(scale.fog) } : {}) } } : {}),
1161
+ ...(isRecord(vn) ? { 'vertex.normal': { normal: toRef(vn['normal']) } } : {}),
1162
+ ...(isRecord(clip) ? { 'vertex.clip': { position: toRef(clip.position) } } : {}),
1163
+ ...(isRecord(vp) ? { 'vertex.position': { position: toRef(vp.position), ...(typeof vp.shadow === 'boolean' ? { shadow: vp.shadow } : {}) } } : {}),
1164
+ ...(isRecord(vo) ? { 'vertex.offset': { offset: toRef(vo.offset), ...(typeof vo.shadow === 'boolean' ? { shadow: vo.shadow } : {}) } } : {}),
1165
+ ...(isRecord(s) ? { surface } : {}),
1166
+ ...(isRecord(p) ? { post: { colour: toRef(p.colour), ...(p['alpha'] === undefined ? {} : { alpha: toRef(p['alpha']) }) } } : {}),
1167
+ ...(isRecord(raw.fog) ? { fog: { colour: toRef(raw.fog.colour), ...(raw.fog['transmission'] === undefined ? {} : { transmission: toRef(raw.fog['transmission']) }) } } : {}),
1168
+ ...(isRecord(o) ? { outline: { offset: toRef(o.offset), colour: toRef(o.colour) } } : {}),
1169
+ ...(isRecord(l) ? { lighting: {
1170
+ sun: toRef(l.sun),
1171
+ ...(l.sunSpecular !== undefined ? { sunSpecular: toRef(l.sunSpecular) } : {}),
1172
+ ...(l.ambient !== undefined ? { ambient: toRef(l.ambient) } : {}),
1173
+ ...(l.grade !== undefined ? { grade: toRef(l.grade) } : {}),
1174
+ } } : {}),
1175
+ };
1176
+ }
1177
+ function toParams(raw: Raw): Record<string, GraphParam> {
1178
+ const out: Record<string, GraphParam> = {};
1179
+ for (const [name, p] of Object.entries(raw)) {
1180
+ if (!isRecord(p)) continue;
1181
+ const t = p.type;
1182
+ if (t !== 'float' && t !== 'vec2' && t !== 'vec3' && t !== 'vec4' && t !== 'colour' && t !== 'texture') continue;
1183
+ const b = p.bind;
1184
+ out[name] = {
1185
+ type: t,
1186
+ ...(p.role === 'colour' || p.role === 'data' || p.role === 'lent' ? { role: p.role } : {}),
1187
+ value: typeof p.value === 'string' ? p.value : toRef(p.value),
1188
+ ...(isFiniteNumber(p.min) ? { min: p.min } : {}),
1189
+ ...(isFiniteNumber(p.max) ? { max: p.max } : {}),
1190
+ ...(isRecord(b) && typeof b.day === 'string' ? { bind: { day: b.day } } : isRecord(b) && typeof b.state === 'string' ? { bind: { state: b.state } } : {}),
1191
+ };
1192
+ }
1193
+ return out;
1194
+ }
1195
+
1196
+ /** Does an admitted graph read the original mesh colour map, including nested bounded loop bodies? */
1197
+ export function graphUsesMeshMap(graph: Pick<GraphIr, 'nodes'>): boolean {
1198
+ const groups = [graph.nodes];
1199
+ for (const nodes of groups) for (const node of Object.values(nodes)) {
1200
+ if (node.op === 'meshMap') return true;
1201
+ if (node.body !== undefined) groups.push(node.body.nodes);
1202
+ }
1203
+ return false;
1204
+ }
1205
+
1206
+ /** Copy admitted lists so caller mutations cannot resize compiled uniforms. */
1207
+ function toArrays(raw: Raw): Record<string, GraphArrayParam> {
1208
+ const out: Record<string, GraphArrayParam> = {};
1209
+ for (const [name, row] of Object.entries(raw)) {
1210
+ if (!isRecord(row) || !Array.isArray(row.value)) continue;
1211
+ const type = row.type;
1212
+ if (type !== 'float' && type !== 'vec2' && type !== 'vec3' && type !== 'vec4') continue;
1213
+ out[name] = { type, value: row.value.map(value => {
1214
+ const literal = toRef(value);
1215
+ if (typeof literal === 'string') throw new Error('Uniform lists require literal values');
1216
+ return literal;
1217
+ }) };
1218
+ }
1219
+ return out;
1220
+ }