create-asciitorium 0.1.42 → 0.1.44

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.
@@ -0,0 +1,1563 @@
1
+ # ASCIITORIUM Quick Reference
2
+
3
+ > **For LLMs**: This document provides correct usage patterns for ASCIITORIUM components. Always refer to the examples here rather than assuming React-like conventions.
4
+
5
+ ## Quick Index
6
+
7
+ **Core Concepts:**
8
+
9
+ - [State Management](#state-management)
10
+ - [JSX & Props](#jsx--props)
11
+ - [Component Lifecycle](#component-lifecycle)
12
+
13
+ **Layout:**
14
+
15
+ - [Row](#row)
16
+ - [Column](#column)
17
+
18
+ **UI Components:**
19
+
20
+ - [Text](#text)
21
+ - [Button](#button)
22
+ - [Select](#select)
23
+ - [Switch](#switch)
24
+ - [TextInput](#textinput)
25
+ - [Art](#art)
26
+
27
+ **Game Components:**
28
+
29
+ - [GameWorld](#gameworld)
30
+ - [MapView](#mapview)
31
+ - [FirstPersonView](#firstpersonview)
32
+
33
+ **Special:**
34
+
35
+ - [Keybind](#keybind)
36
+ - [PerfMonitor](#perfmonitor)
37
+
38
+ ---
39
+
40
+ ## State Management
41
+
42
+ ### Basic Usage
43
+
44
+ ```tsx
45
+ import { State } from 'asciitorium';
46
+
47
+ // Create reactive state
48
+ const count = new State(0);
49
+
50
+ // Read value
51
+ console.log(count);
52
+
53
+ // Update value (triggers reactivity)
54
+ count.value = 42;
55
+ ```
56
+
57
+ ### Props
58
+
59
+ | Prop | Type | Description |
60
+ | -------------- | ---- | ------------------- |
61
+ | `initialValue` | `T` | Initial state value |
62
+
63
+ ### Common Patterns
64
+
65
+ **Pattern 1: Counter**
66
+
67
+ ```tsx
68
+ const count = new State(0);
69
+
70
+ <Button onClick={() => count.value++}>Increment: {count}</Button>;
71
+ ```
72
+
73
+ **Pattern 2: Toggle**
74
+
75
+ ```tsx
76
+ const isVisible = new State(false);
77
+
78
+ <Keybind
79
+ keyBinding="p"
80
+ action={() => isVisible.value = !isVisible.value}
81
+ />
82
+ <PerfMonitor visible={isVisible} />
83
+ ```
84
+
85
+ **Pattern 3: Conditional Rendering**
86
+
87
+ ```tsx
88
+ const showDebug = new State(false);
89
+
90
+ {
91
+ showDebug.value && <Text>Debug info here</Text>;
92
+ }
93
+ ```
94
+
95
+ ### Common Mistakes
96
+
97
+ ❌ **WRONG** - Using React's useState
98
+
99
+ ```tsx
100
+ const [count, setCount] = useState(0); // NO! This is React, not ASCIITORIUM
101
+ ```
102
+
103
+ ✅ **CORRECT** - Using ASCIITORIUM State
104
+
105
+ ```tsx
106
+ const count = new State(0);
107
+ ```
108
+
109
+ ❌ **WRONG** - Forgetting .value
110
+
111
+ ```tsx
112
+ count++; // NO! count is a State object
113
+ ```
114
+
115
+ ✅ **CORRECT** - Using .value
116
+
117
+ ```tsx
118
+ count.value++;
119
+ ```
120
+
121
+ ---
122
+
123
+ ## JSX & Props
124
+
125
+ ### Core Concepts
126
+
127
+ ASCIITORIUM uses custom JSX runtime. Components accept props either:
128
+
129
+ 1. Via explicit props (e.g., `content="text"`)
130
+ 2. Via JSX children (e.g., `<Text>text</Text>`)
131
+
132
+ ### Common Prop Patterns
133
+
134
+ **Sizing:**
135
+
136
+ ```tsx
137
+ width={40} // Fixed width
138
+ height={10} // Fixed height
139
+ width="fill" // Fill parent
140
+ // Omit width/height for auto-sizing (or use 'auto', equivalent)
141
+ ```
142
+
143
+ **Positioning:**
144
+
145
+ ```tsx
146
+ position={{ x: 5, y: 10, z: 10 }} // Absolute positioning, z is optional
147
+ ```
148
+
149
+ **Styling:**
150
+
151
+ ```tsx
152
+ border={true} // Show border
153
+ gap={1} // Spacing
154
+ align="center" // Alignment (layout-specific)
155
+ ```
156
+
157
+ ### Common Mistakes
158
+
159
+ ❌ **WRONG** - Using className or style objects
160
+
161
+ ```tsx
162
+ <Text className="my-class" style={{ color: 'red' }}>
163
+ NO!
164
+ </Text>
165
+ ```
166
+
167
+ ✅ **CORRECT** - Using ASCIITORIUM props
168
+
169
+ ```tsx
170
+ <Text border={true} width={40}>
171
+ YES!
172
+ </Text>
173
+ ```
174
+
175
+ ---
176
+
177
+ ## Row
178
+
179
+ ### Basic Usage
180
+
181
+ ```tsx
182
+ <Row gap={2} align="center">
183
+ <Button>One</Button>
184
+ <Button>Two</Button>
185
+ <Button>Three</Button>
186
+ </Row>
187
+ ```
188
+
189
+ ### Props
190
+
191
+ | Prop | Type | Default | Description |
192
+ | -------- | ------------------------------- | -------- | ---------------------- |
193
+ | `gap` | `number` | `0` | Space between children |
194
+ | `align` | `'top' \| 'center' \| 'bottom'` | `'top'` | Vertical alignment |
195
+ | `width` | `number \| 'fill'` | `'fill'` | Component width |
196
+ | `height` | `number \| 'fill'` | `'auto'` | Component height |
197
+
198
+ ### Common Patterns
199
+
200
+ **Pattern 1: Button Row**
201
+
202
+ ```tsx
203
+ <Row gap={2} align="center">
204
+ <Button onClick={() => save()}>Save</Button>
205
+ <Button onClick={() => cancel()}>Cancel</Button>
206
+ </Row>
207
+ ```
208
+
209
+ **Pattern 2: Mixed Content**
210
+
211
+ ```tsx
212
+ <Row gap={1}>
213
+ <Text width={20}>Label:</Text>
214
+ <TextInput width={30} value={inputState} />
215
+ </Row>
216
+ ```
217
+
218
+ ---
219
+
220
+ ## Column
221
+
222
+ ### Basic Usage
223
+
224
+ ```tsx
225
+ <Column gap={1} align="center" width="100%">
226
+ <Text>Header</Text>
227
+ <Text>Content</Text>
228
+ <Text>Footer</Text>
229
+ </Column>
230
+ ```
231
+
232
+ ### Props
233
+
234
+ | Prop | Type | Default | Description |
235
+ | -------- | ------------------------------- | -------- | ----------------------------------------------- |
236
+ | `gap` | `number` | `0` | Space between children |
237
+ | `align` | `'left' \| 'center' \| 'right'` | `'left'` | Horizontal alignment |
238
+ | `width` | `number \| 'fill' \| '100%'` | - | Auto-sizes to children (omit) or set explicitly |
239
+ | `height` | `number \| 'fill'` | `'auto'` | Component height |
240
+
241
+ ### Common Patterns
242
+
243
+ **Pattern 1: Centered Layout**
244
+
245
+ ```tsx
246
+ <Column align="center" width="100%" gap={2}>
247
+ <Art src="logo" />
248
+ <Text>Welcome</Text>
249
+ <Button>Start</Button>
250
+ </Column>
251
+ ```
252
+
253
+ **Pattern 2: Form Layout**
254
+
255
+ ```tsx
256
+ <Column gap={1}>
257
+ <Text>Username:</Text>
258
+ <TextInput value={username} />
259
+ <Text>Password:</Text>
260
+ <TextInput value={password} />
261
+ <Button>Login</Button>
262
+ </Column>
263
+ ```
264
+
265
+ ---
266
+
267
+ ## Text
268
+
269
+ ### Basic Usage
270
+
271
+ ```tsx
272
+ // Using JSX children
273
+ <Text>Hello, world!</Text>
274
+
275
+ // Using content prop
276
+ <Text content="Hello, world!" />
277
+
278
+ // Using State
279
+ <Text>{myState}</Text>
280
+ ```
281
+
282
+ ### Props
283
+
284
+ | Prop | Type | Default | Description |
285
+ | ----------------- | -------------------------------------------------------------------------------------------------------------------- | ------------ | -------------------------------------------------------- |
286
+ | `content` | `string \| State<any>` | - | Text content (alternative to children) |
287
+ | `children` | `string \| State<any>` | - | JSX children |
288
+ | `textAlign` | `'top-left' \| 'top' \| 'top-right' \| 'left' \| 'center' \| 'right' \| 'bottom-left' \| 'bottom' \| 'bottom-right'` | `'top-left'` | Text alignment within box |
289
+ | `width` | `number \| 'fill' \| 'auto'` | - | Component width (omit for auto-sizing to content) |
290
+ | `height` | `number \| 'auto'` | - | Component height (omit for auto-sizing to wrapped lines) |
291
+ | `border` | `boolean` | `false` | Show border |
292
+ | `scrollable` | `boolean` | `false` | Enable scrolling (arrow keys) |
293
+ | `wrap` | `boolean` | `true` | Enable text wrapping |
294
+ | `typewriter` | `boolean` | `false` | Typewriter reveal effect |
295
+ | `typewriterSpeed` | `number` | `20` | Characters per second for typewriter |
296
+
297
+ ### Common Patterns
298
+
299
+ **Pattern 1: Static Text**
300
+
301
+ ```tsx
302
+ <Text border={true} width={40}>
303
+ This is a simple text box with a border.
304
+ </Text>
305
+ ```
306
+
307
+ **Pattern 2: Reactive Text**
308
+
309
+ ```tsx
310
+ const playerName = new State('Hero');
311
+
312
+ <Text>Player: {playerName}</Text>;
313
+ ```
314
+
315
+ **Pattern 3: Multi-line with Pilcrow**
316
+
317
+ ```tsx
318
+ <Text>Line one¶Line two¶Line three</Text>
319
+ ```
320
+
321
+ **Pattern 4: Scrollable Content**
322
+
323
+ ```tsx
324
+ <Text scrollable={true} height={10} width={60}>
325
+ Long content that needs scrolling...
326
+ </Text>
327
+ ```
328
+
329
+ **Pattern 5: Typewriter Effect**
330
+
331
+ ```tsx
332
+ <Text typewriter={true} typewriterSpeed={30}>
333
+ This text appears character by character...
334
+ </Text>
335
+ ```
336
+
337
+ ### Common Mistakes
338
+
339
+ ❌ **WRONG** - Using \n for newlines
340
+
341
+ ```tsx
342
+ <Text>Line one\nLine two</Text> // \n doesn't work in text content
343
+ ```
344
+
345
+ ✅ **CORRECT** - Using ¶ (pilcrow) for newlines
346
+
347
+ ```tsx
348
+ <Text>Line one¶Line two</Text>
349
+ ```
350
+
351
+ ❌ **WRONG** - Accessing State directly
352
+
353
+ ```tsx
354
+ <Text>{count}</Text> // Shows as object
355
+ ```
356
+
357
+ ✅ **CORRECT** - Both work (State is auto-resolved in Text)
358
+
359
+ ```tsx
360
+ <Text>{count}</Text> // State auto-resolved
361
+ <Text>{count.value}</Text> // Explicit .value also works
362
+ ```
363
+
364
+ ---
365
+
366
+ ## Button
367
+
368
+ ### Basic Usage
369
+
370
+ ```tsx
371
+ // Using JSX children
372
+ <Button onClick={() => console.log('clicked')}>
373
+ Click Me
374
+ </Button>
375
+
376
+ // Using content prop
377
+ <Button content="Click Me" onClick={() => console.log('clicked')} />
378
+ ```
379
+
380
+ ### Props
381
+
382
+ | Prop | Type | Default | Description |
383
+ | ---------- | ------------ | -------------------- | --------------------------------------------- |
384
+ | `content` | `string` | - | Button text (alternative to children) |
385
+ | `children` | `string` | - | JSX children |
386
+ | `onClick` | `() => void` | - | Click handler |
387
+ | `width` | `number` | `content.length + 7` | Auto-calculated based on content |
388
+ | `height` | `number` | `4` | Button height |
389
+ | `border` | `boolean` | `true` | Show border |
390
+ | `hotkey` | `string` | - | Hotkey letter (press F1 to toggle visibility) |
391
+
392
+ ### Common Patterns
393
+
394
+ **Pattern 1: Simple Button**
395
+
396
+ ```tsx
397
+ <Button onClick={() => startGame()}>Start Game</Button>
398
+ ```
399
+
400
+ **Pattern 2: State-Modifying Button**
401
+
402
+ ```tsx
403
+ const count = new State(0);
404
+
405
+ <Button onClick={() => count.value++}>Count: {count}</Button>;
406
+ ```
407
+
408
+ **Pattern 3: Button with Hotkey**
409
+
410
+ ```tsx
411
+ <Button hotkey="s" onClick={() => save()}>
412
+ Save Game
413
+ </Button>
414
+ ```
415
+
416
+ ### Common Mistakes
417
+
418
+ ❌ **WRONG** - Using onClick with event parameter
419
+
420
+ ```tsx
421
+ <Button onClick={(e) => handleClick(e)}>Click</Button> // NO event parameter
422
+ ```
423
+
424
+ ✅ **CORRECT** - onClick receives no parameters
425
+
426
+ ```tsx
427
+ <Button onClick={() => handleClick()}>Click</Button>
428
+ ```
429
+
430
+ ❌ **WRONG** - Setting width too small
431
+
432
+ ```tsx
433
+ <Button width={5}>Very Long Text</Button> // Text will be cut off
434
+ ```
435
+
436
+ ✅ **CORRECT** - Let it auto-size or calculate properly
437
+
438
+ ```tsx
439
+ <Button>Very Long Text</Button> // Auto-sizes
440
+ ```
441
+
442
+ ---
443
+
444
+ ## Select
445
+
446
+ ### Basic Usage
447
+
448
+ ```tsx
449
+ import { State, Select, Option } from 'asciitorium';
450
+
451
+ const selected = new State<string>('option1');
452
+
453
+ <Select selected={selected}>
454
+ <Option value="option1" label="First Option" />
455
+ <Option value="option2" label="Second Option" />
456
+ <Option value="option3" label="Third Option" />
457
+ </Select>;
458
+ ```
459
+
460
+ ### Props (Select)
461
+
462
+ | Prop | Type | Default | Description |
463
+ | ---------- | ------------- | ------- | -------------------- |
464
+ | `selected` | `State<T>` | - | Selected value state |
465
+ | `children` | `Option<T>[]` | - | Option children |
466
+ | `width` | `number` | - | Component width |
467
+ | `height` | `number` | `3` | Component height |
468
+ | `border` | `boolean` | `true` | Show border |
469
+
470
+ ### Props (Option)
471
+
472
+ | Prop | Type | Description |
473
+ | ------- | -------- | ------------- |
474
+ | `value` | `T` | Option value |
475
+ | `label` | `string` | Display label |
476
+
477
+ ### Common Patterns
478
+
479
+ **Pattern 1: String Select**
480
+
481
+ ```tsx
482
+ const difficulty = new State<string>('normal');
483
+
484
+ <Select selected={difficulty}>
485
+ <Option value="easy" label="Easy Mode" />
486
+ <Option value="normal" label="Normal Mode" />
487
+ <Option value="hard" label="Hard Mode" />
488
+ </Select>;
489
+ ```
490
+
491
+ **Pattern 2: Grouped Options**
492
+
493
+ ```tsx
494
+ import { Select, Option, OptionGroup } from 'asciitorium';
495
+
496
+ const weapon = new State<string>('sword');
497
+
498
+ <Select selected={weapon}>
499
+ <OptionGroup label="Melee">
500
+ <Option value="sword" label="Sword" />
501
+ <Option value="axe" label="Axe" />
502
+ </OptionGroup>
503
+ <OptionGroup label="Ranged">
504
+ <Option value="bow" label="Bow" />
505
+ <Option value="crossbow" label="Crossbow" />
506
+ </OptionGroup>
507
+ </Select>;
508
+ ```
509
+
510
+ **Pattern 3: React to Selection**
511
+
512
+ ```tsx
513
+ const mode = new State<string>('explore');
514
+
515
+ // Subscribe to changes
516
+ mode.subscribe((newMode) => {
517
+ console.log(`Mode changed to: ${newMode}`);
518
+ });
519
+
520
+ <Select selected={mode}>
521
+ <Option value="explore" label="Explore" />
522
+ <Option value="combat" label="Combat" />
523
+ </Select>;
524
+ ```
525
+
526
+ ### Common Mistakes
527
+
528
+ ❌ **WRONG** - Using value without State
529
+
530
+ ```tsx
531
+ <Select selected="option1"> // NO! selected must be a State
532
+ ```
533
+
534
+ ✅ **CORRECT** - Using State
535
+
536
+ ```tsx
537
+ const selected = new State("option1");
538
+ <Select selected={selected}>
539
+ ```
540
+
541
+ ❌ **WRONG** - Old API (items array)
542
+
543
+ ```tsx
544
+ <Select items={['a', 'b']} selectedItem={state} /> // Deprecated
545
+ ```
546
+
547
+ ✅ **CORRECT** - New API (Option children)
548
+
549
+ ```tsx
550
+ <Select selected={state}>
551
+ <Option value="a" label="Option A" />
552
+ <Option value="b" label="Option B" />
553
+ </Select>
554
+ ```
555
+
556
+ ---
557
+
558
+ ## Switch
559
+
560
+ ### Basic Usage
561
+
562
+ ```tsx
563
+ import { State, Switch } from 'asciitorium';
564
+
565
+ // State holding the component to display
566
+ const currentView = new State<any>(DashboardComponent);
567
+
568
+ <Switch component={currentView} />;
569
+ ```
570
+
571
+ ### Props
572
+
573
+ | Prop | Type | Default | Description |
574
+ | ----------- | ---------------------------------------------------------------- | ------- | --------------------------------------------------------- |
575
+ | `component` | `State<Component \| (() => Component) \| (new () => Component)>` | - | State holding component instance, constructor, or factory |
576
+ | `width` | `number \| 'fill'` | - | Component width |
577
+ | `height` | `number \| 'fill'` | - | Component height |
578
+
579
+ ### Common Patterns
580
+
581
+ **Pattern 1: View Switching**
582
+
583
+ ```tsx
584
+ import { State, Switch, Button, Column } from 'asciitorium';
585
+
586
+ // Component classes/constructors
587
+ class DashboardView extends Component {
588
+ /* ... */
589
+ }
590
+ class SettingsView extends Component {
591
+ /* ... */
592
+ }
593
+ class ProfileView extends Component {
594
+ /* ... */
595
+ }
596
+
597
+ const currentView = new State<any>(DashboardView);
598
+
599
+ <Column>
600
+ <Button onClick={() => (currentView.value = DashboardView)}>Dashboard</Button>
601
+ <Button onClick={() => (currentView.value = SettingsView)}>Settings</Button>
602
+ <Button onClick={() => (currentView.value = ProfileView)}>Profile</Button>
603
+
604
+ <Switch component={currentView} width="fill" height="fill" />
605
+ </Column>;
606
+ ```
607
+
608
+ **Pattern 2: Dynamic Content with Select**
609
+
610
+ ```tsx
611
+ import { State, Select, Option, Switch } from 'asciitorium';
612
+
613
+ // Component map
614
+ const pageMap: Record<string, any> = {
615
+ home: HomeComponent,
616
+ about: AboutComponent,
617
+ contact: ContactComponent,
618
+ };
619
+
620
+ const selectedKey = new State<string>('home');
621
+ const selectedComponent = new State<any>(pageMap['home']);
622
+
623
+ // Sync selected component with key
624
+ selectedKey.subscribe((key) => {
625
+ selectedComponent.value = pageMap[key];
626
+ });
627
+
628
+ <Column>
629
+ <Select selected={selectedKey}>
630
+ <Option value="home" label="Home" />
631
+ <Option value="about" label="About" />
632
+ <Option value="contact" label="Contact" />
633
+ </Select>
634
+
635
+ <Switch component={selectedComponent} width="fill" height="fill" />
636
+ </Column>;
637
+ ```
638
+
639
+ **Pattern 3: Factory Functions**
640
+
641
+ ```tsx
642
+ // Using factory functions instead of constructors
643
+ const viewFactory = () => new CustomView({ customProp: 'value' });
644
+ const currentView = new State<any>(viewFactory);
645
+
646
+ <Switch component={currentView} />;
647
+ ```
648
+
649
+ **Pattern 4: Component Instances**
650
+
651
+ ```tsx
652
+ // Using pre-instantiated components
653
+ const dashboard = new DashboardView({ width: 80 });
654
+ const settings = new SettingsView({ width: 80 });
655
+
656
+ const currentView = new State<Component>(dashboard);
657
+
658
+ <Button onClick={() => currentView.value = settings}>Switch to Settings</Button>
659
+ <Switch component={currentView} />
660
+ ```
661
+
662
+ ### Common Mistakes
663
+
664
+ ❌ **WRONG** - Not using State
665
+
666
+ ```tsx
667
+ <Switch component={MyComponent} /> // NO! component must be a State
668
+ ```
669
+
670
+ ✅ **CORRECT** - Using State
671
+
672
+ ```tsx
673
+ const view = new State(MyComponent);
674
+ <Switch component={view} />;
675
+ ```
676
+
677
+ ❌ **WRONG** - Trying to pass component as string
678
+
679
+ ```tsx
680
+ const view = new State<string>('DashboardComponent'); // NO! Must be actual component
681
+ <Switch component={view} />;
682
+ ```
683
+
684
+ ✅ **CORRECT** - Using actual component reference
685
+
686
+ ```tsx
687
+ const view = new State<any>(DashboardComponent); // Component constructor
688
+ <Switch component={view} />;
689
+ ```
690
+
691
+ ---
692
+
693
+ ## TextInput
694
+
695
+ ### Basic Usage
696
+
697
+ ```tsx
698
+ const username = new State('');
699
+
700
+ <TextInput value={username} placeholder="Enter username..." />;
701
+ ```
702
+
703
+ ### Props
704
+
705
+ | Prop | Type | Default | Description |
706
+ | ------------- | -------------------------------- | ------- | ------------------------------------------ |
707
+ | `value` | `State<string> \| State<number>` | - | Input value state (string or number) |
708
+ | `placeholder` | `string` | `''` | Placeholder text |
709
+ | `numeric` | `boolean` | `false` | Restricts input to numbers only |
710
+ | `width` | `number` | - | Input width (often needs explicit setting) |
711
+ | `height` | `number` | `3` | Input height |
712
+ | `border` | `boolean` | `true` | Show border |
713
+ | `maxLength` | `number` | - | Maximum input length |
714
+
715
+ ### Common Patterns
716
+
717
+ **Pattern 1: Simple Input**
718
+
719
+ ```tsx
720
+ const name = new State('');
721
+
722
+ <TextInput value={name} placeholder="Your name" width={30} />;
723
+ ```
724
+
725
+ **Pattern 2: Form with Validation**
726
+
727
+ ```tsx
728
+ const email = new State('');
729
+
730
+ email.subscribe((value) => {
731
+ if (value.includes('@')) {
732
+ console.log('Valid email');
733
+ }
734
+ });
735
+
736
+ <TextInput value={email} placeholder="email@example.com" />;
737
+ ```
738
+
739
+ **Pattern 3: Max Length**
740
+
741
+ ```tsx
742
+ const code = new State('');
743
+
744
+ <TextInput value={code} maxLength={6} placeholder="Enter code" />;
745
+ ```
746
+
747
+ ### Common Mistakes
748
+
749
+ ❌ **WRONG** - Using string directly
750
+
751
+ ```tsx
752
+ <TextInput value="static text" /> // NO! value must be State
753
+ ```
754
+
755
+ ✅ **CORRECT** - Using State
756
+
757
+ ```tsx
758
+ const text = new State('initial');
759
+ <TextInput value={text} />;
760
+ ```
761
+
762
+ ❌ **WRONG** - Trying to get value incorrectly
763
+
764
+ ```tsx
765
+ <Button onClick={() => console.log(inputValue)}>Submit</Button>
766
+ ```
767
+
768
+ ✅ **CORRECT** - Accessing .value
769
+
770
+ ```tsx
771
+ <Button onClick={() => console.log(inputValue.value)}>Submit</Button>
772
+ ```
773
+
774
+ ---
775
+
776
+ ## Art
777
+
778
+ ### Basic Usage
779
+
780
+ ```tsx
781
+ // Load from file
782
+ <Art src="logo" />
783
+
784
+ // ASCII art from figlet font
785
+ <Art font="standard" text="Hello" />
786
+
787
+ // Align center
788
+ <Art src="banner" align="center" />
789
+ ```
790
+
791
+ ### Props
792
+
793
+ | Prop | Type | Default | Description |
794
+ | -------- | ------------------------------- | -------- | -------------------------------- |
795
+ | `src` | `string` | - | Art file name (from art/ folder) |
796
+ | `font` | `string` | - | Figlet font name |
797
+ | `text` | `string` | - | Text to render with font |
798
+ | `width` | `number \| 'auto'` | `'auto'` | Component width |
799
+ | `height` | `number \| 'auto'` | `'auto'` | Component height |
800
+ | `align` | `'left' \| 'center' \| 'right'` | `'left'` | Horizontal alignment |
801
+ | `border` | `boolean` | `false` | Show border |
802
+
803
+ ### Common Patterns
804
+
805
+ **Pattern 1: Logo from File**
806
+
807
+ ```tsx
808
+ // Loads from art/logo.art
809
+ <Art src="logo" align="center" />
810
+ ```
811
+
812
+ **Pattern 2: Generated ASCII Text**
813
+
814
+ ```tsx
815
+ <Art font="standard" text="Game Over" align="center" />
816
+ ```
817
+
818
+ **Pattern 3: Animated Sprite**
819
+
820
+ ```tsx
821
+ // Loads animated art (auto-plays)
822
+ <Art src="beating-heart" width={20} />
823
+ ```
824
+
825
+ ### Common Mistakes
826
+
827
+ ❌ **WRONG** - Using both src and font
828
+
829
+ ```tsx
830
+ <Art src="logo" font="standard" /> // Conflicting props
831
+ ```
832
+
833
+ ✅ **CORRECT** - Use one or the other
834
+
835
+ ```tsx
836
+ <Art src="logo" /> // From file
837
+ <Art font="standard" text="Title" /> // Generated
838
+ ```
839
+
840
+ ---
841
+
842
+ ## Keybind
843
+
844
+ ### Basic Usage
845
+
846
+ ```tsx
847
+ <Keybind keyBinding="F12" action={() => console.log('F12 pressed')} />
848
+ ```
849
+
850
+ ### Props
851
+
852
+ | Prop | Type | Default | Description |
853
+ | ------------- | --------------------------- | ------- | -------------------------------------------- |
854
+ | `keyBinding` | `string` | - | Key combination (e.g., "F12", "Ctrl+s", "p") |
855
+ | `action` | `() => void` | - | Function to execute |
856
+ | `description` | `string` | - | Optional description |
857
+ | `disabled` | `State<boolean> \| boolean` | `false` | Disable keybind |
858
+ | `priority` | `number` | `0` | Priority for conflict resolution |
859
+
860
+ ### Common Patterns
861
+
862
+ **Pattern 1: Toggle Visibility**
863
+
864
+ ```tsx
865
+ const showDebug = new State(false);
866
+
867
+ <Keybind keyBinding="d" action={() => (showDebug.value = !showDebug.value)} />;
868
+
869
+ {
870
+ showDebug.value && <Text>Debug Info</Text>;
871
+ }
872
+ ```
873
+
874
+ **Pattern 2: Game Controls**
875
+
876
+ ```tsx
877
+ <Keybind keyBinding="ArrowUp" action={() => gameWorld.moveForward()} />
878
+ <Keybind keyBinding="ArrowDown" action={() => gameWorld.moveBackward()} />
879
+ <Keybind keyBinding="ArrowLeft" action={() => gameWorld.turnLeft()} />
880
+ <Keybind keyBinding="ArrowRight" action={() => gameWorld.turnRight()} />
881
+ ```
882
+
883
+ **Pattern 3: Conditional Keybind**
884
+
885
+ ```tsx
886
+ const gameStarted = new State(false);
887
+
888
+ <Keybind
889
+ keyBinding="Space"
890
+ action={() => shoot()}
891
+ disabled={gameStarted.not()}
892
+ />;
893
+ ```
894
+
895
+ **Pattern 4: Function Keys**
896
+
897
+ ```tsx
898
+ <Keybind keyBinding="F1" action={() => showHelp()} description="Show help" />
899
+ <Keybind keyBinding="F12" action={() => toggleDebug()} description="Toggle debug" />
900
+ ```
901
+
902
+ ### Common Mistakes
903
+
904
+ ❌ **WRONG** - Using event object
905
+
906
+ ```tsx
907
+ <Keybind action={(e) => console.log(e.key)} /> // NO event parameter
908
+ ```
909
+
910
+ ✅ **CORRECT** - Action receives no parameters
911
+
912
+ ```tsx
913
+ <Keybind keyBinding="p" action={() => console.log('p pressed')} />
914
+ ```
915
+
916
+ ❌ **WRONG** - Assuming global scope without checking focused component
917
+
918
+ ```tsx
919
+ // If a TextInput has focus, letter keys go to the input, not the keybind
920
+ <Keybind keyBinding="w" action={() => moveUp()} />
921
+ ```
922
+
923
+ ✅ **CORRECT** - Use non-conflicting keys for global actions
924
+
925
+ ```tsx
926
+ <Keybind keyBinding="ArrowUp" action={() => moveUp()} />
927
+ ```
928
+
929
+ ---
930
+
931
+ ## GameWorld
932
+
933
+ ### Basic Usage
934
+
935
+ ```tsx
936
+ import { GameWorld, State } from 'asciitorium';
937
+
938
+ const gameWorld = new GameWorld({
939
+ mapName: 'dungeon',
940
+ initialPosition: { x: 5, y: 5, direction: 'north' },
941
+ });
942
+
943
+ // Wait for async load
944
+ await gameWorld.ready;
945
+ ```
946
+
947
+ ### Constructor Options
948
+
949
+ | Option | Type | Description |
950
+ | ----------------- | ------------------------------------------------ | -------------------------------------- |
951
+ | `mapName` | `string` | Map name (loads from art/maps/{name}/) |
952
+ | `initialPosition` | `{ x: number, y: number, direction: Direction }` | Starting player position |
953
+
954
+ ### Methods
955
+
956
+ | Method | Description |
957
+ | ---------------- | ----------------------------------------- |
958
+ | `moveForward()` | Move player forward (respects collision) |
959
+ | `moveBackward()` | Move player backward (respects collision) |
960
+ | `turnLeft()` | Turn player left (no movement) |
961
+ | `turnRight()` | Turn player right (no movement) |
962
+ | `isSolid(x, y)` | Check if tile is solid (legend-based) |
963
+ | `getMapData()` | Get map string array |
964
+ | `getPlayer()` | Get current player state |
965
+ | `getLegend()` | Get legend metadata |
966
+ | `isReady()` | Check if assets are loaded |
967
+
968
+ ### Common Patterns
969
+
970
+ **Pattern 1: Basic Game Setup**
971
+
972
+ ```tsx
973
+ const gameWorld = new GameWorld({
974
+ mapName: 'castle',
975
+ initialPosition: { x: 10, y: 10, direction: 'north' },
976
+ });
977
+
978
+ await gameWorld.ready;
979
+
980
+ <App>
981
+ <Keybind keyBinding="ArrowUp" action={() => gameWorld.moveForward()} />
982
+ <Keybind keyBinding="ArrowDown" action={() => gameWorld.moveBackward()} />
983
+ <Keybind keyBinding="ArrowLeft" action={() => gameWorld.turnLeft()} />
984
+ <Keybind keyBinding="ArrowRight" action={() => gameWorld.turnRight()} />
985
+
986
+ <MapView mapAsset={gameWorld.mapAsset} player={gameWorld.player} />
987
+ <FirstPersonView mapAsset={gameWorld.mapAsset} player={gameWorld.player} />
988
+ </App>;
989
+ ```
990
+
991
+ **Pattern 2: Custom Movement Logic**
992
+
993
+ ```tsx
994
+ const handleMove = () => {
995
+ const player = gameWorld.getPlayer();
996
+ const nextPos = calculateNextPosition(player);
997
+
998
+ if (!gameWorld.isSolid(nextPos.x, nextPos.y)) {
999
+ gameWorld.moveForward();
1000
+ } else {
1001
+ console.log('Blocked!');
1002
+ }
1003
+ };
1004
+
1005
+ <Keybind keyBinding="w" action={handleMove} />;
1006
+ ```
1007
+
1008
+ **Pattern 3: Fog of War**
1009
+
1010
+ ```tsx
1011
+ const exploredTiles = new State(new Set<string>());
1012
+
1013
+ gameWorld.player.subscribe((player) => {
1014
+ const key = `${player.x},${player.y}`;
1015
+ const newSet = new Set(exploredTiles.value);
1016
+ newSet.add(key);
1017
+ exploredTiles.value = newSet;
1018
+ });
1019
+
1020
+ <MapView
1021
+ mapAsset={gameWorld.mapAsset}
1022
+ player={gameWorld.player}
1023
+ fogOfWar={true}
1024
+ exploredTiles={exploredTiles}
1025
+ />;
1026
+ ```
1027
+
1028
+ ### Common Mistakes
1029
+
1030
+ ❌ **WRONG** - Not waiting for ready
1031
+
1032
+ ```tsx
1033
+ const gameWorld = new GameWorld({ mapName: 'dungeon', ... });
1034
+ // Assets not loaded yet!
1035
+ <MapView mapAsset={gameWorld.mapAsset} player={gameWorld.player} />
1036
+ ```
1037
+
1038
+ ✅ **CORRECT** - Await ready
1039
+
1040
+ ```tsx
1041
+ const gameWorld = new GameWorld({ mapName: 'dungeon', ... });
1042
+ await gameWorld.ready;
1043
+ <MapView mapAsset={gameWorld.mapAsset} player={gameWorld.player} />
1044
+ ```
1045
+
1046
+ ❌ **WRONG** - Modifying player state directly
1047
+
1048
+ ```tsx
1049
+ gameWorld.player.value.x += 1; // Bypasses collision detection
1050
+ ```
1051
+
1052
+ ✅ **CORRECT** - Using movement methods
1053
+
1054
+ ```tsx
1055
+ gameWorld.moveForward(); // Handles collision
1056
+ ```
1057
+
1058
+ ---
1059
+
1060
+ ## MapView
1061
+
1062
+ ### Basic Usage
1063
+
1064
+ ```tsx
1065
+ <MapView mapAsset={gameWorld.mapAsset} player={gameWorld.player} />
1066
+ ```
1067
+
1068
+ ### Props
1069
+
1070
+ | Prop | Type | Default | Description |
1071
+ | --------------- | ----------------------------------- | -------- | ------------------------------ |
1072
+ | `mapAsset` | `State<MapAsset \| null>` | - | Map asset state |
1073
+ | `player` | `State<Player>` | - | Player state |
1074
+ | `fogOfWar` | `boolean \| State<boolean>` | `false` | Enable fog of war |
1075
+ | `exploredTiles` | `Set<string> \| State<Set<string>>` | - | Explored tiles (for fog) |
1076
+ | `fogCharacter` | `string` | `' '` | Character for unexplored tiles |
1077
+ | `width` | `number \| 'fill'` | `'fill'` | Component width |
1078
+ | `height` | `number \| 'fill'` | `'fill'` | Component height |
1079
+ | `border` | `boolean` | `true` | Show border |
1080
+
1081
+ ### Common Patterns
1082
+
1083
+ **Pattern 1: Simple Map Display**
1084
+
1085
+ ```tsx
1086
+ <MapView
1087
+ mapAsset={gameWorld.mapAsset}
1088
+ player={gameWorld.player}
1089
+ width={60}
1090
+ height={20}
1091
+ />
1092
+ ```
1093
+
1094
+ **Pattern 2: Map with Fog of War**
1095
+
1096
+ ```tsx
1097
+ const exploredTiles = new State(new Set<string>());
1098
+
1099
+ // Update explored tiles when player moves
1100
+ gameWorld.player.subscribe((p) => {
1101
+ const newSet = new Set(exploredTiles.value);
1102
+ newSet.add(`${p.x},${p.y}`);
1103
+ exploredTiles.value = newSet;
1104
+ });
1105
+
1106
+ <MapView
1107
+ mapAsset={gameWorld.mapAsset}
1108
+ player={gameWorld.player}
1109
+ fogOfWar={true}
1110
+ exploredTiles={exploredTiles}
1111
+ fogCharacter="░"
1112
+ />;
1113
+ ```
1114
+
1115
+ **Pattern 3: Minimap**
1116
+
1117
+ ```tsx
1118
+ <MapView
1119
+ mapAsset={gameWorld.mapAsset}
1120
+ player={gameWorld.player}
1121
+ width={30}
1122
+ height={15}
1123
+ border={true}
1124
+ />
1125
+ ```
1126
+
1127
+ ### Common Mistakes
1128
+
1129
+ ❌ **WRONG** - Using raw map data
1130
+
1131
+ ```tsx
1132
+ <MapView mapData={['###', '#.#', '###']} /> // Old API
1133
+ ```
1134
+
1135
+ ✅ **CORRECT** - Using GameWorld's mapAsset and player
1136
+
1137
+ ```tsx
1138
+ <MapView mapAsset={gameWorld.mapAsset} player={gameWorld.player} />
1139
+ ```
1140
+
1141
+ ---
1142
+
1143
+ ## FirstPersonView
1144
+
1145
+ ### Basic Usage
1146
+
1147
+ ```tsx
1148
+ <FirstPersonView
1149
+ mapAsset={gameWorld.mapAsset}
1150
+ player={gameWorld.player}
1151
+ scene="brick-wall"
1152
+ />
1153
+ ```
1154
+
1155
+ ### Props
1156
+
1157
+ | Prop | Type | Default | Description |
1158
+ | ---------- | ------------------------- | ------------- | ---------------- |
1159
+ | `mapAsset` | `State<MapAsset \| null>` | - | Map asset state |
1160
+ | `player` | `State<Player>` | - | Player state |
1161
+ | `scene` | `string` | `'wireframe'` | Scene style name |
1162
+ | `width` | `number \| 'fill'` | `'fill'` | Component width |
1163
+ | `height` | `number \| 'fill'` | `'fill'` | Component height |
1164
+ | `border` | `boolean` | `true` | Show border |
1165
+
1166
+ ### Available Scenes
1167
+
1168
+ - `wireframe` - Simple line-based rendering
1169
+ - `brick-wall` - Textured brick walls
1170
+ - `stone-wall` - Stone texture
1171
+ - `dungeon` - Dark dungeon atmosphere
1172
+
1173
+ ### Common Patterns
1174
+
1175
+ **Pattern 1: Basic First-Person View**
1176
+
1177
+ ```tsx
1178
+ <FirstPersonView
1179
+ mapAsset={gameWorld.mapAsset}
1180
+ player={gameWorld.player}
1181
+ scene="brick-wall"
1182
+ width={80}
1183
+ height={40}
1184
+ />
1185
+ ```
1186
+
1187
+ **Pattern 2: Split View (Map + First Person)**
1188
+
1189
+ ```tsx
1190
+ <Column width="100%" height="100%">
1191
+ <FirstPersonView
1192
+ mapAsset={gameWorld.mapAsset}
1193
+ player={gameWorld.player}
1194
+ scene="dungeon"
1195
+ height={30}
1196
+ />
1197
+ <MapView
1198
+ mapAsset={gameWorld.mapAsset}
1199
+ player={gameWorld.player}
1200
+ height={15}
1201
+ />
1202
+ </Column>
1203
+ ```
1204
+
1205
+ ### Common Mistakes
1206
+
1207
+ ❌ **WRONG** - Using legacy src prop
1208
+
1209
+ ```tsx
1210
+ <FirstPersonView src={mapData} player={player} /> // Old API
1211
+ ```
1212
+
1213
+ ✅ **CORRECT** - Using mapAsset
1214
+
1215
+ ```tsx
1216
+ <FirstPersonView mapAsset={gameWorld.mapAsset} player={gameWorld.player} />
1217
+ ```
1218
+
1219
+ ---
1220
+
1221
+ ## PerfMonitor
1222
+
1223
+ ### Basic Usage
1224
+
1225
+ ```tsx
1226
+ const showPerf = new State(false);
1227
+
1228
+ <Keybind keyBinding="p" action={() => showPerf.value = !showPerf.value} />
1229
+ <PerfMonitor visible={showPerf} />
1230
+ ```
1231
+
1232
+ ### Props
1233
+
1234
+ | Prop | Type | Default | Description |
1235
+ | ---------- | --------------------------- | ---------------- | ----------------- |
1236
+ | `visible` | `State<boolean> \| boolean` | `true` | Show/hide monitor |
1237
+ | `position` | `{ x: number, y: number }` | `{ x: 0, y: 0 }` | Screen position |
1238
+
1239
+ ### Common Patterns
1240
+
1241
+ **Pattern 1: Toggle with Keybind**
1242
+
1243
+ ```tsx
1244
+ const showPerf = new State(false);
1245
+
1246
+ <Keybind keyBinding="F3" action={() => showPerf.value = !showPerf.value} />
1247
+ <PerfMonitor visible={showPerf} />
1248
+ ```
1249
+
1250
+ **Pattern 2: Always Visible (Dev Mode)**
1251
+
1252
+ ```tsx
1253
+ <PerfMonitor visible={true} />
1254
+ ```
1255
+
1256
+ ### Common Mistakes
1257
+
1258
+ ❌ **WRONG** - Expecting control over position
1259
+
1260
+ ```tsx
1261
+ <PerfMonitor position={{ x: 100, y: 50 }} /> // Position prop exists but fixed to corner
1262
+ ```
1263
+
1264
+ ✅ **CORRECT** - Accept default positioning
1265
+
1266
+ ```tsx
1267
+ <PerfMonitor visible={showPerf} />
1268
+ ```
1269
+
1270
+ ---
1271
+
1272
+ ## Component Lifecycle
1273
+
1274
+ ### Initialization
1275
+
1276
+ ```tsx
1277
+ // Components are instantiated when JSX is executed
1278
+ const app = (
1279
+ <App>
1280
+ <Text>This creates a new Text instance</Text>
1281
+ </App>
1282
+ );
1283
+
1284
+ // App starts rendering
1285
+ await app.start();
1286
+ ```
1287
+
1288
+ ### State Subscriptions
1289
+
1290
+ ```tsx
1291
+ const count = new State(0);
1292
+
1293
+ // Manual subscription
1294
+ count.subscribe((newValue) => {
1295
+ console.log('Count changed:', newValue);
1296
+ });
1297
+
1298
+ // Component auto-subscribes when State is passed as prop
1299
+ <Text>{count}</Text>; // Auto re-renders when count changes
1300
+ ```
1301
+
1302
+ ### Cleanup
1303
+
1304
+ ```tsx
1305
+ // Components auto-cleanup when destroyed
1306
+ // State subscriptions are automatically removed
1307
+ // No manual cleanup needed in most cases
1308
+ ```
1309
+
1310
+ ---
1311
+
1312
+ ## Legend System (Game Maps)
1313
+
1314
+ ### Legend File Format
1315
+
1316
+ Each map has a `legend.json` file that describes tile properties:
1317
+
1318
+ ```json
1319
+ {
1320
+ "#": {
1321
+ "name": "Wall",
1322
+ "kind": "material",
1323
+ "solid": true,
1324
+ "asset": "brick-wall"
1325
+ },
1326
+ ".": {
1327
+ "name": "Floor",
1328
+ "kind": "material",
1329
+ "solid": false,
1330
+ "asset": "floor"
1331
+ },
1332
+ "D": {
1333
+ "name": "Door",
1334
+ "kind": "sprite",
1335
+ "solid": true,
1336
+ "tag": "door",
1337
+ "asset": "door-closed"
1338
+ }
1339
+ }
1340
+ ```
1341
+
1342
+ ### Legend Properties
1343
+
1344
+ | Property | Type | Description |
1345
+ | -------- | ------------------------ | --------------------- |
1346
+ | `name` | `string` | Human-readable name |
1347
+ | `kind` | `'material' \| 'sprite'` | Asset type |
1348
+ | `solid` | `boolean` | Blocks movement |
1349
+ | `asset` | `string` | Asset file name |
1350
+ | `tag` | `string` | Optional semantic tag |
1351
+
1352
+ ### Common Patterns
1353
+
1354
+ **Pattern 1: Check Collision**
1355
+
1356
+ ```tsx
1357
+ const canMove = !gameWorld.isSolid(targetX, targetY);
1358
+ ```
1359
+
1360
+ **Pattern 2: Custom Interaction**
1361
+
1362
+ ```tsx
1363
+ const tile = gameWorld.getLegend()[tileChar];
1364
+ if (tile?.tag === 'door') {
1365
+ openDoor();
1366
+ } else if (tile?.tag === 'treasure') {
1367
+ collectTreasure();
1368
+ }
1369
+ ```
1370
+
1371
+ ---
1372
+
1373
+ ## Directory Structure
1374
+
1375
+ ```
1376
+ your-project/
1377
+ ├── src/
1378
+ │ ├── main.tsx # Entry point
1379
+ │ └── ...
1380
+ ├── art/
1381
+ │ ├── maps/
1382
+ │ │ └── your-map/
1383
+ │ │ ├── map.art
1384
+ │ │ └── legend.json
1385
+ │ ├── materials/
1386
+ │ │ └── wall.art
1387
+ │ ├── sprites/
1388
+ │ │ └── player.art
1389
+ │ └── your-logo.art
1390
+ ├── index.html
1391
+ └── package.json
1392
+ ```
1393
+
1394
+ ### Art File Format
1395
+
1396
+ Art files use special separators:
1397
+
1398
+ - `§` - Metadata separator (e.g., `§width=80§height=40§`)
1399
+ - `¶` - Line separator in content
1400
+
1401
+ Example:
1402
+
1403
+ ```
1404
+ §width=20§height=5§
1405
+ ╔══════════════════╗¶
1406
+ ║ GAME TITLE ║¶
1407
+ ║ ║¶
1408
+ ║ Press START ║¶
1409
+ ╚══════════════════╝
1410
+ ```
1411
+
1412
+ ---
1413
+
1414
+ ## Type Annotations
1415
+
1416
+ ### Player Type
1417
+
1418
+ ```tsx
1419
+ import type { Player } from 'asciitorium';
1420
+
1421
+ const player: Player = {
1422
+ x: 5,
1423
+ y: 10,
1424
+ direction: 'north', // 'north' | 'south' | 'east' | 'west'
1425
+ };
1426
+ ```
1427
+
1428
+ ### MapAsset Type
1429
+
1430
+ ```tsx
1431
+ import type { MapAsset } from 'asciitorium';
1432
+
1433
+ // Returned by AssetManager.loadMap()
1434
+ const mapAsset: MapAsset = {
1435
+ mapData: string[];
1436
+ legend: Record<string, LegendEntry>;
1437
+ };
1438
+ ```
1439
+
1440
+ ### LegendEntry Type
1441
+
1442
+ ```tsx
1443
+ import type { LegendEntry } from 'asciitorium';
1444
+
1445
+ const entry: LegendEntry = {
1446
+ name: string;
1447
+ kind: 'material' | 'sprite';
1448
+ solid: boolean;
1449
+ asset: string;
1450
+ tag?: string;
1451
+ };
1452
+ ```
1453
+
1454
+ ---
1455
+
1456
+ ## Complete Example: Simple Game
1457
+
1458
+ ```tsx
1459
+ import {
1460
+ App,
1461
+ GameWorld,
1462
+ MapView,
1463
+ FirstPersonView,
1464
+ Keybind,
1465
+ Text,
1466
+ Column,
1467
+ State,
1468
+ } from 'asciitorium';
1469
+
1470
+ // Create game world
1471
+ const gameWorld = new GameWorld({
1472
+ mapName: 'dungeon',
1473
+ initialPosition: { x: 5, y: 5, direction: 'north' },
1474
+ });
1475
+
1476
+ // Wait for assets to load
1477
+ await gameWorld.ready;
1478
+
1479
+ // Fog of war state
1480
+ const exploredTiles = new State(new Set<string>());
1481
+
1482
+ // Update explored tiles on player move
1483
+ gameWorld.player.subscribe((p) => {
1484
+ const newSet = new Set(exploredTiles.value);
1485
+ newSet.add(`${p.x},${p.y}`);
1486
+ exploredTiles.value = newSet;
1487
+ });
1488
+
1489
+ const app = (
1490
+ <App>
1491
+ {/* Movement controls */}
1492
+ <Keybind keyBinding="ArrowUp" action={() => gameWorld.moveForward()} />
1493
+ <Keybind keyBinding="ArrowDown" action={() => gameWorld.moveBackward()} />
1494
+ <Keybind keyBinding="ArrowLeft" action={() => gameWorld.turnLeft()} />
1495
+ <Keybind keyBinding="ArrowRight" action={() => gameWorld.turnRight()} />
1496
+
1497
+ {/* UI Layout */}
1498
+ <Column width="100%" height="100%" gap={0}>
1499
+ {/* First-person view */}
1500
+ <FirstPersonView
1501
+ mapAsset={gameWorld.mapAsset}
1502
+ player={gameWorld.player}
1503
+ scene="dungeon"
1504
+ height={30}
1505
+ />
1506
+
1507
+ {/* Map view with fog */}
1508
+ <MapView
1509
+ mapAsset={gameWorld.mapAsset}
1510
+ player={gameWorld.player}
1511
+ fogOfWar={true}
1512
+ exploredTiles={exploredTiles}
1513
+ height={15}
1514
+ />
1515
+
1516
+ {/* Status text */}
1517
+ <Text border={true}>Use arrow keys to move. Explore the dungeon!</Text>
1518
+ </Column>
1519
+ </App>
1520
+ );
1521
+
1522
+ await app.start();
1523
+ ```
1524
+
1525
+ ---
1526
+
1527
+ ## Understanding "auto" vs Omitting Props
1528
+
1529
+ **Key Concept:** In ASCIITORIUM, omitting `width` or `height` props is the same as setting them to `undefined` or `"auto"`.
1530
+
1531
+ - `width={40}` → Fixed width of 40 characters
1532
+ - `width="fill"` → Fill available parent space
1533
+ - `width="auto"` → Auto-size to content (same as omitting the prop)
1534
+ - _Omitting width prop_ → Auto-size to content (recommended approach)
1535
+
1536
+ **Recommendation:** Omit props instead of using `"auto"` for clarity. The type system allows `"auto"` but it's unnecessary.
1537
+
1538
+ **Component-specific auto-sizing:**
1539
+
1540
+ - **Text**: Auto-sizes to content length (width) and wrapped lines (height) when props omitted
1541
+ - **Button**: Always calculates size based on content (`buttonText.length + 7` for width, `4` for height)
1542
+ - **Art**: Auto-sizes to loaded art dimensions when props omitted
1543
+ - **Column**: Auto-sizes to fit children when width omitted
1544
+ - **Row**: Defaults to `width="fill"`
1545
+
1546
+ ---
1547
+
1548
+ ## Tips for LLMs
1549
+
1550
+ 1. **Always use `new State()` for reactive values** - Never use React's useState
1551
+ 2. **State values are accessed via `.value`** - Read and write using `.value`
1552
+ 3. **Text newlines use `¶` (pilcrow)** - Not `\n`
1553
+ 4. **Components accept children via JSX or explicit props** - Both patterns work
1554
+ 5. **GameWorld must be awaited** - `await gameWorld.ready` before rendering
1555
+ 6. **Keybinds are invisible components** - They never render, just register handlers
1556
+ 7. **MapView and FirstPersonView are display-only** - Movement logic goes in GameWorld or Keybinds
1557
+ 8. **Legend system drives collision** - Use `isSolid()` for movement validation
1558
+ 9. **No CSS or className** - ASCIITORIUM uses ASCII rendering, not DOM styling
1559
+ 10. **Omit width/height props for auto-sizing** - Components have smart defaults (Text/Art/Column size to content, Button calculates from label, Row fills width)
1560
+
1561
+ ---
1562
+
1563
+ _End of Reference_