Expose the runtime block/item registry through compact material search and single-material descriptions. Preserve private block-entity data through checked edits and durable undo without sending payloads to model context. Include the completed terrain tools, isolated world/map plugin, station lift, ACP streaming and guidance fixes, local camera auto-connect, construction scripts, and their public documentation, references and verification records. Active station decoration and private runtime data remain outside this commit. Validation: 145 Maven tests, 47 Bridge tests, successful camera Gradle build, and isolated Paper verification of all 1,196 block defaults plus 5,392 independent property cases for placement, same-material edits and restoration.
181 lines
9.5 KiB
Python
181 lines
9.5 KiB
Python
"""Render the proposed station layout against the saved, block-exact foundation.
|
|
|
|
Read-only design utility; it never connects to or writes the Minecraft world.
|
|
All X/Z boxes are inclusive block coordinates. Pillow is required.
|
|
"""
|
|
import json
|
|
from pathlib import Path
|
|
from collections import Counter
|
|
from PIL import Image, ImageDraw, ImageFont
|
|
|
|
OUT = Path(__file__).resolve().parent
|
|
ROOT = OUT.parents[2]
|
|
SOURCE = ROOT / '.runtime/foundations-stage02/foundations-final.geometry.json'
|
|
geometry = json.loads(SOURCE.read_text())
|
|
foot = {(c['x'], c['z']) for c in geometry['cells'] if c['group'] == 'clock-station'}
|
|
inner = {p for p in foot if all((p[0]+dx,p[1]+dz) in foot for dx in range(-2,3) for dz in range(-2,3))}
|
|
walls = foot-inner
|
|
|
|
def cells(box):
|
|
a,b,c,d = box
|
|
return {(x,z) for x in range(a,b+1) for z in range(c,d+1)}
|
|
|
|
core = [-10,-2,-124,-116]
|
|
cabin = [-8,-4,-122,-118]
|
|
bays = [[x-3,x+3,-140,-136] for x in [-42,-32,-22,-12,-2,8]]
|
|
display = [-42,-24,-124,-110]
|
|
pillars = [[x,x+1,z,z+1] for x in [-69,-49,-19,17,31] for z in [-127,-104]]
|
|
benches = [[x,x+7,z,z+1] for x in [-67,24] for z in [-132,-108]]
|
|
features = {'lift housing':core,'lift cabin':cabin,'display':display}
|
|
features.update({f'arena bay {i+1}':b for i,b in enumerate(bays)})
|
|
features.update({f'column {i+1}':b for i,b in enumerate(pillars)})
|
|
features.update({f'bench {i+1}':b for i,b in enumerate(benches)})
|
|
for name,box in features.items():
|
|
assert cells(box) <= inner, (name,'outside two-block perimeter setback')
|
|
solid_features = [core,display,*bays,*pillars,*benches]
|
|
for i,a in enumerate(solid_features):
|
|
for b in solid_features[i+1:]:
|
|
assert not cells(a)&cells(b), ('furniture overlap',a,b)
|
|
route = cells([-9,-3,-115,-85])
|
|
assert route <= foot
|
|
assert not route & set().union(*(cells(b) for b in solid_features))
|
|
bay_aisle = cells([-46,11,-135,-129])
|
|
assert bay_aisle <= inner
|
|
assert not bay_aisle & set().union(*(cells(b) for b in solid_features))
|
|
map_path = ROOT / '.runtime/server/plugins/ShacraftTerrain/maps/zone01-complete.json'
|
|
surface=json.loads(map_path.read_text())
|
|
hist=Counter(surface['surface_y'][(z-surface['min_z'])*surface['width']+x-surface['min_x']] for x,z in foot)
|
|
design = {
|
|
'status':'PROPOSAL ONLY. Foundation is surveyed; walls, interiors and upper levels are not built.',
|
|
'world':'shacraft_lobby_v2','coordinate_convention':'X/Z boxes include both endpoints. Floor Y is block Y; walk Y is top of that floor.',
|
|
'foundation_source':str(SOURCE.relative_to(ROOT)), 'surface_capture_finished_at':surface['capture_finished_at'],
|
|
'foundation_columns':len(foot),'foundation_bounds':[-74,40,-145,-83], 'foundation_surface_height_counts':dict(hist),
|
|
'floors':[{'name':'1 Vestibule','floor_block_y':98,'walk_y':99,'ceiling_underside_y':111,'clear_height':12},
|
|
{'name':'2 Smash','floor_block_y':112,'walk_y':113,'ceiling_underside_y':124,'clear_height':11}],
|
|
'intermediate_deck_block_y':[111,112], 'perimeter_wall_thickness':2,
|
|
'main_roof_eaves_y':126,'main_roof_ridge_y':140,'clocktower_peak_y':158,
|
|
'entrance_axis_x':-6,'entrance_opening_x':[-9,-3],'entrance_threshold_z':[-84,-83],
|
|
'lift':{'housing':core,'clear_cabin':cabin,'door_clear_x':[-8,-4],'door_z':[-117,-116],'opens':'south','same_location_on_both_floors':True},
|
|
'smash_display':display,'smash_selection_bays':bays,'aligned_columns':pillars,'wing_benches':benches,
|
|
'reserved_clear_aisles':{'entry':[-9,-3,-115,-85],'bay_front':[-46,11,-135,-129]},
|
|
'checks':{'features_inside_two_block_setback':True,'solid_features_do_not_overlap':True,'entry_route_clear_width':7,'bay_front_clear_depth':7},
|
|
'limitations':['Saved surface export is not a fresh live scan.', 'Containment and overlap checks cover the proposed plan, not constructed voxels.',
|
|
'Generated perspectives are artistic references; this coordinate specification governs placement.', 'Arena sign slots are unassigned, not working destinations.',
|
|
'Future floors need an explicit roof/height design before construction. Only the lift alignment is reserved.']}
|
|
(OUT/'layout.json').write_text(json.dumps(design,indent=2)+'\n')
|
|
|
|
W,H=2000,1530
|
|
im=Image.new('RGB',(W,H),'#f5f1e7'); d=ImageDraw.Draw(im)
|
|
REG='/usr/share/fonts/truetype/ibm-plex/IBMPlexSans-Regular.ttf'
|
|
BOLD='/usr/share/fonts/truetype/ibm-plex/IBMPlexSans-SemiBold.ttf'
|
|
SERIF='/usr/share/fonts/truetype/ibm-plex/IBMPlexSerif-Regular.ttf'
|
|
def text(x,y,s,size=22,color='#203e36',bold=False,anchor=None):
|
|
d.text((x,y),s,font=ImageFont.truetype(BOLD if bold else REG,size),fill=color,anchor=anchor)
|
|
text(65,35,'SHACRAFT / CLOCK STATION',44,bold=True)
|
|
text(65,93,'01 VESTIBULE + 02 SMASH / COORDINATE PLAN / DESIGN v1',22)
|
|
text(65,133,'Fixed foundation: 115 x 63 blocks overall. North is up. Both plans use the same 1-block grid.',21)
|
|
green='#376956';orange='#bd633b';wall='#58635c';floor='#e8dfcc';gold='#d0aa54'
|
|
|
|
def plan(ox,title,upper=False):
|
|
oy=255; sc=7
|
|
def xy(x,z): return ox+(x+74)*sc,oy+(z+145)*sc
|
|
def box(b,fill,outline=None):
|
|
a,c=xy(b[0],b[2]);bb,dd=xy(b[1]+1,b[3]+1)
|
|
d.rectangle((a,c,bb-1,dd-1),fill=fill,outline=outline)
|
|
def label(x,z,s,size=17,col='#203e36'):
|
|
a,b=xy(x,z);text(a,b,s,size,col,anchor='mm')
|
|
text(ox,196,title,28,bold=True)
|
|
for x,z in foot: box([x,x,z,z], wall if (x,z) in walls else floor)
|
|
# Discrete occupied columns avoid polygon boundary ambiguity.
|
|
for x,z in inner:
|
|
a,b=xy(x,z)
|
|
d.line((a,b,a+sc,b),fill='#ddd5c4')
|
|
d.line((a,b,a,b+sc),fill='#ddd5c4')
|
|
for b in pillars: box(b,'#7b8177')
|
|
for b in benches: box(b,'#957753')
|
|
box(core,green);box(cabin,'#cbdcd0')
|
|
box([-8,-4,-117,-116],'#cbdcd0')
|
|
label(-5.5,-120,'L',21)
|
|
if upper:
|
|
box(display,orange)
|
|
label(-32.5,-118,'DISPLAY',16,'#ffffff')
|
|
label(-32.5,-114,'19 x 15',16,'#ffffff')
|
|
for i,b in enumerate(bays):
|
|
box(b,gold);label((b[0]+b[1]+1)/2,-137.5,f'{i+1:02}',16)
|
|
label(-17,-132,'7-BLOCK CLEAR AISLE',17)
|
|
label(-60,-118,'RULES',17)
|
|
label(28,-118,'WAIT',17)
|
|
label(-5,-94,'SOUTH GALLERY',17)
|
|
else:
|
|
label(-58,-118,'WAITING',17)
|
|
label(28,-118,'WAITING',17)
|
|
label(-34,-117,'VESTIBULE',19)
|
|
box([1,7,-113,-111],gold)
|
|
label(4,-107,'DIRECTORY',15)
|
|
# South doorway through both perimeter wall blocks.
|
|
box([-9,-3,-84,-83],floor)
|
|
a,b=xy(-5.5,-88);c,e=xy(-5.5,-111)
|
|
d.line((a,b,c,e),fill=green,width=4)
|
|
d.polygon([(c,e),(c-8,e+14),(c+8,e+14)],fill=green)
|
|
label(-5.5,-97,'7 wide',15)
|
|
# Outside dimensions and orientation.
|
|
text(ox+402,oy-21,'115 blocks',19,anchor='mm')
|
|
d.line((ox,oy-8,ox+805,oy-8),fill=wall,width=2)
|
|
for x in [ox,ox+805]: d.line((x,oy-14,x,oy-2),fill=wall,width=2)
|
|
text(ox+842,oy+213,'63',21,anchor='mm');text(ox+842,oy+240,'blocks',16,anchor='mm')
|
|
d.line((ox+817,oy,ox+817,oy+441),fill=wall,width=2)
|
|
for z in [-145,-125,-105,-83]:
|
|
a,b=xy(-74,z);text(a-12,b,str(z),15,anchor='rm')
|
|
for x in [-74,-50,-25,0,40]:
|
|
a,b=xy(x,-82);text(a,b+18,str(x),16,anchor='mm')
|
|
text(ox+23,oy+398,'N',23,bold=True)
|
|
d.line((ox+31,oy+397,ox+31,oy+358),fill=green,width=3)
|
|
d.polygon([(ox+31,oy+352),(ox+23,oy+366),(ox+39,oy+366)],fill=green)
|
|
text(ox,oy+491,'X: east-west / Z: north-south / unit: one block',18)
|
|
|
|
plan(105,'01 / VESTIBULE • WALK Y99')
|
|
plan(1100,'02 / SMASH • WALK Y113',True)
|
|
d.line((65,792,1935,792),fill='#bdb8aa',width=2)
|
|
text(65,825,'SECTION / ENTRANCE AXIS X = -6',27,bold=True)
|
|
text(1060,825,'DESIGN RULES',27,bold=True)
|
|
# North at left, south at right; Y shown as world surface elevations.
|
|
sx,sy,scale=105,1200,11
|
|
def section_box(z0,z1,y0,y1,fill):
|
|
d.rectangle((sx+(z0+145)*scale,sy-(y1-98)*scale,sx+(z1+145)*scale-1,sy-(y0-98)*scale-1),fill=fill)
|
|
section_box(-145,-82,98,99,wall)
|
|
section_box(-145,-82,111,113,wall)
|
|
section_box(-145,-82,124,126,wall)
|
|
section_box(-145,-143,99,124,wall)
|
|
section_box(-84,-82,99,124,wall)
|
|
section_box(-84,-82,99,105,'#f5f1e7')
|
|
for y in [99,113]:
|
|
top=111 if y==99 else 124
|
|
section_box(-124,-122,y,top,green)
|
|
section_box(-118,-116,y+6,top,green)
|
|
section_box(-122,-118,y,y+0.25,gold)
|
|
for y,lab in [(99,'Y99 / level 1'),(111,'Y111 / ceiling'),(113,'Y113 / level 2'),(124,'Y124 / ceiling')]:
|
|
yy=sy-(y-98)*scale
|
|
d.line((sx-12,yy,sx+720,yy),fill='#b4afa2',width=1)
|
|
text(sx+735,yy,lab,18,anchor='lm')
|
|
text(455,1072,'12 clear',23,anchor='mm')
|
|
text(455,924,'11 clear',23,anchor='mm')
|
|
text(105,1234,'NORTH',18);text(735,1234,'SOUTH / ENTRY',18)
|
|
notes=[
|
|
'Fixed footprint; 2-block perimeter walls.',
|
|
'Lift: 9 x 9 housing; 5 x 5 clear cabin.',
|
|
'Same shaft and south-facing door on both floors.',
|
|
'Six 7 x 5 sign bays; destinations remain unassigned.',
|
|
'SMASH display is scenery on a solid, enclosed floor.',
|
|
'Gameplay takes place in separate arena worlds.',
|
|
'Future stories are not furnished or dimensioned here.',
|
|
'Concept images illustrate style; layout.json governs placement.'
|
|
]
|
|
for i,s in enumerate(notes): text(1060,878+i*43,s,21)
|
|
text(65,1320,'VERIFIED AGAINST THE SAVED FOUNDATION',22,bold=True)
|
|
text(65,1358,f'{len(foot):,} foundation columns • all specified furnishings inside the wall setback • no furnishing overlaps',21)
|
|
text(65,1394,'This is a design drawing, not a live scan or a completed building. Above-ground shell and floors are proposed.',21)
|
|
text(65,1452,'SOURCE: foundations-final.geometry.json + zone01-complete surface export / 13 SEP 2026',18)
|
|
im.save(OUT/'00-coordinate-plan.png')
|
|
im.save(OUT/'00-coordinate-plan.pdf',resolution=150)
|
|
print(json.dumps({'saved':str(OUT),'foundation_columns':len(foot),'height_counts':dict(hist),'checks':'passed'}))
|