diff --git a/README.md b/README.md
index b61e8c5..a427752 100644
--- a/README.md
+++ b/README.md
@@ -84,7 +84,6 @@ For pull requests, we use [GitConsensus](https://www.gitconsensus.com/) to allow
 | folder | name |author |Description                    |
 | ----- |---------| -------|----------------------- |
 |JS| [actually commented evil tower code.js](/src/misc/JavaScript/actually%20commented%20evil%20tower%20code.js) | daboross| lodash chain tower code|
-|JS| [bunkerLayoutsHumanReadable.js](/src/misc/JavaScript/bunkerLayoutsHumanReadable.js) |sparr| readable bunker layouts sample|
 |JS| [Calculate Cost of a Mine.js](/src/misc/JavaScript/Calculate%20Cost%20of%20a%20Mine.js) |Gankdalf| mining cost calculations|
 |JS| [Check if room is a source keeper room.js](/src/misc/JavaScript/Check%20if%20room%20is%20a%20source%20keeper%20room.js) |issacar|check if room is a source keeper room|
 |JS| [colors.js](/src/misc/JavaScript/colors.js) |dissi| visualize percentage with colors|
@@ -114,6 +113,7 @@ For pull requests, we use [GitConsensus](https://www.gitconsensus.com/) to allow
 |TS| [Creep intent tracker.ts](/src/misc/TypeScript/Creep%20intent%20tracker.ts) |unfleshedone|intent tracker implementation|
 |TS| [moving.average.ts](/src/misc/TypeScript/moving.average.ts) |unsleshedone|moving average implementation|
 |TS| [Typescript roomScan.ts](/src/misc/TypeScript/Typescript%20roomScan.ts) |crzytrane|room scanner?|
+|TS| [BunkerLayout.ts](/src/misc/TypeScript/BunkerLayout.ts) |sparr| parse human-readable readable bunker layout strings|
 || [migrate room to sim.md](/src/misc/migrate%20room%20to%20sim.md) |semperrabbit|how to migrate room to sim|
 || [screeps body calculator.md](/src/misc/screeps%20body%20calculator.md) |nitroevil|link to creep calculator|
 |KT| [DistanceTransform](src/misc/Kotlin/DistanceTransform/) |Vipo|Algoritm for finding open areas in rooms|
diff --git a/src/misc/JavaScript/bunkerLayoutsHumanReadable.js b/src/misc/JavaScript/bunkerLayoutsHumanReadable.js
deleted file mode 100644
index 476c1df..0000000
--- a/src/misc/JavaScript/bunkerLayoutsHumanReadable.js
+++ /dev/null
@@ -1,167 +0,0 @@
-/**
- * Posted 19 October 2017 by @sparr
- */
-
-// maps letters in the layout arrays to structures and vice versa
-exports.layoutKey = {
-    'A': STRUCTURE_SPAWN,
-    'N': STRUCTURE_NUKER,
-    'K': STRUCTURE_LINK,
-    'L': STRUCTURE_LAB,
-    'E': STRUCTURE_EXTENSION,
-    'S': STRUCTURE_STORAGE,
-    'T': STRUCTURE_TOWER,
-    'O': STRUCTURE_OBSERVER,
-    'M': STRUCTURE_TERMINAL,
-    'P': STRUCTURE_POWER_SPAWN,
-    '.': STRUCTURE_ROAD,
-    'C': STRUCTURE_CONTAINER,
-    'R': STRUCTURE_RAMPART,
-    'W': STRUCTURE_WALL,
-};
-_.merge(exports.layoutKey, _.invert(exports.layoutKey));
-
-// the preferred layout, if there's enough room
-exports.bunkerLayout = [
-    '  ..E...E..  ',
-    ' .EE.EEE.EE. ',
-    '.EE.E.E.E.EE.',
-    '.E.EEA.EEE.E.',
-    'E.EEE.T.EEE.E',
-    '.E.E.T.T.A.E.',
-    '.EE.NSKMP.E.E',
-    '.E.E.T.T.E.E.',
-    'E.EEE.T.OLL..',
-    '.E.EEA.ELL.L.',
-    '.EE.E.E.L.LL.',
-    ' .EE.E.E.LL. ',
-    '  ..E.E....  ',
-];
-
-exports.bunkerExtensionOrder = [
-    '    3   7    ',
-    '  43 447 78  ',
-    ' 43 2 4 7 78 ',
-    ' 3 24  887 7 ',
-    '3 242   887 7',
-    ' 4 2       7 ',
-    ' 44       8 8',
-    ' 5 5     8 8 ',
-    '5 566        ',
-    ' 5 56  6     ',
-    ' 65 5 6      ',
-    '  65 5 6     ',
-    '    6 6      ',
-];
-
-exports.bunkerRoadOrder = [
-    '  .. ... ..  ',
-    ' .  2   7  . ',
-    '.  2 2 7 7  .',
-    '. 2   2   7 .',
-    ' 2   2 3   7 ',
-    '. 2 2 3 7 7 .',
-    '.  2     7 8 ',
-    '. 5 5 . . 8 .',
-    ' 5   5 .   ..',
-    '. 5   5   . .',
-    '.  5 5 6 .  .',
-    ' .  5 6 .  . ',
-    '  .. . ....  ',
-];
-
-exports.bunkerRampartOrder = [
-    '  555555555  ',
-    ' 55777777755 ',
-    '5577777777755',
-    '57777   77775',
-    '5777  3  7775',
-    '577  333  775',
-    '577 33333 775',
-    '577  333  775',
-    '5777  3  7775',
-    '57777   77775',
-    '5577777777755',
-    ' 55777777755 ',
-    '  555555555  ',
-];
-
-// just the core of the bunker, plus two spawns
-exports.coreLayout = [
-    '   .   ',
-    '  AT.  ',
-    ' .T.T. ',
-    '.NSKMP.',
-    ' .T.T. ',
-    '  .TAO ',
-    '   .   ',
-];
-
-// just the lab block, for placement elsewhere if necessary
-exports.labLayout = [
-    ' LL.',
-    'LL.L',
-    'L.LL',
-    '.LL ',
-];
-
-// rapid-fill extension block, if there's room
-exports.extensionLayout = [
-    '     EEE   ',
-    '   EErrEE  ',
-    '  EErEErEE ',
-    ' EErEEErrEE',
-    'EErECECEErE',
-    ' rEEEKEEEr ',
-    'ErEECECErEE',
-    'EErrEEErEE ',
-    ' EErEErEE  ',
-    '  EErrEE   ',
-    '   EEE     ',
-];
-
-/**
- * Get all the positions for a certain structure/letter from a layout
- * @param  {String[]} layout
- * @param  {String|String} char letter or structure to return, optional
- * @return {{x:Number,y:Number}[]} array of coordinate objects
- * @return {Object} map of structure types to arrays of coordinate objects
- */
-exports.getPositions = function(layout, char) {
-    if (typeof(char) === 'string') {
-        char = [char];
-    }
-    const height = layout.length;
-    const width = layout[0].length;
-    const top = height / 2 | 0;
-    const left = width / 2 | 0;
-    if (char instanceof Array) {
-        const positions = [];
-        for (let c of char) {
-            if (c.length>1) {
-                if (c in exports.layoutKey) {
-                    c = exports.layoutKey[c];
-                } else {
-                    continue;
-                }
-            }
-            for (let y = 0; y < height; y++) {
-                for (let x = 0; x < width; x++) {
-                    if (layout[y][x] === c) {
-                        positions.push({x: x-left, y: y-top});
-                    }
-                }
-            }
-        }
-        return positions;
-    } else {
-        const positions = {};
-        for (let y = 0; y < height; y++) {
-            for (let x = 0; x < width; x++) {
-                const char = layout[y][x];
-                positions[exports.layoutKey[char]] = positions[exports.layoutKey[char]] || [];
-                positions[exports.layoutKey[char]].push({x: x-left, y: y-top});
-            }
-        }
-    }
-};
diff --git a/src/misc/TypeScript/BunkerLayout.ts b/src/misc/TypeScript/BunkerLayout.ts
new file mode 100644
index 0000000..21ecb19
--- /dev/null
+++ b/src/misc/TypeScript/BunkerLayout.ts
@@ -0,0 +1,142 @@
+// bunkerLayout.ts
+// A library for processing human-readable bunker layout maps
+
+/**
+ * Originally posted to Slack on 19 October 2017 by @sparr
+ * Ported from JS to TS and improved by @sparr in August 2021
+ */
+
+// An example layout, one array for buildings, one for the RCL at which to build them
+// let bunkerStructures : string[] = [      // let bunkerLevels : string[] = [
+//     "  ..E...E..  ",                     //     "  445233555  ",
+//     " .EE.EEE.EE. ",                     //     " 44422233555 ",
+//     ".EE.E.E.E.EE.",                     //     "4442212233555",
+//     ".E.EEA.EEE.E.",                     //     "4444211335555",
+//     "E.EEE.T.EEE.E",                     //     "6444413335556",
+//     ".E.E.T.T.A.E.",                     //     "6664455757666",
+//     ".EE.NSKMP.E.E",                     //     "6666N456P6666",
+//     ".E.E.T.T.E.E.",                     //     "7777686866666",
+//     "E.EEE.T.OLL..",                     //     "7777768686766",
+//     ".E.EEA.ELL.L.",                     //     "7777787866677",
+//     ".EE.E.E.L.LL.",                     //     "7788888888877",
+//     " .EE.E.E.LL. ",                     //     " 88888888888 ",
+//     "  ..E.E....  ",                     //     "  888888888  ",
+// ];                                       // ];
+// A similar structure might describe rampart locations and levels
+
+// example usage:
+// BunkerLayout.getLayout(bunkerStructures,{rcl:6})
+// returns a description of all the structures up to RCL 6:
+// {rcl:6,buildings:{road:{pos:[{x:2,y:0},...]},extension:{pos:[{x:4,y:0},...]},spawn:{pos:[{x:5,y:4}]},...}}
+// intended to comply with the schema used by https://screeps.admon.dev/building-planner
+
+// maps chacters in the layout arrays to structures
+const layoutMappingForward : { [symbol:string]:BuildableStructureConstant } = {
+    'A': STRUCTURE_SPAWN,
+    'N': STRUCTURE_NUKER,
+    'K': STRUCTURE_LINK,
+    'L': STRUCTURE_LAB,
+    'E': STRUCTURE_EXTENSION,
+    'S': STRUCTURE_STORAGE,
+    'T': STRUCTURE_TOWER,
+    'O': STRUCTURE_OBSERVER,
+    'M': STRUCTURE_TERMINAL,
+    'P': STRUCTURE_POWER_SPAWN,
+    '.': STRUCTURE_ROAD,
+    'C': STRUCTURE_CONTAINER,
+    'R': STRUCTURE_RAMPART,
+    'W': STRUCTURE_WALL,
+    'X': STRUCTURE_EXTRACTOR,
+    'F': STRUCTURE_FACTORY,
+};
+// maps structures to characters
+const layoutMappingReverse : { [key in BuildableStructureConstant]:string } = _.invert(layoutMappingForward);
+// lowercase letters are structure+road
+for(let letter in layoutMappingForward) {
+    layoutMappingForward[letter.toLocaleLowerCase()]=layoutMappingForward[letter];
+}
+
+export interface BunkerLayoutPos {
+    x: number,
+    y: number,
+}
+
+export interface BunkerLayoutArrayEntry extends BunkerLayoutPos {
+    rcl?: number,
+    structureType?: BuildableStructureConstant,
+};
+
+
+/**
+ * Get all the positions from a layout, optionally for a specific structure type(s), optionally for a specific RCL, optionally as an array
+ * @param  {String[]} [layout] a layout map with structure letters
+ * @param  {BuildableStructureConstant|BuildableStructureConstant[]} [params.structureType] structure(s) to return, optional
+ * @param  {String[]} [params.levelLayout] a layout map with level numbers, optional
+ * @param  {Number} [params.rcl] room control level, optional
+ * @param  {Boolean} [params.asArray=false] results as flat array, optional
+ * @return {BunkerLayoutResults} object 
+ * @return {{x:Number,y:Number,stuctureType?:String,level?:Number}[]} array of coordinate+type?+level? objects
+ */
+ export interface BunkerGetLayoutParams {
+    structureType?: BuildableStructureConstant | BuildableStructureConstant[],
+    levelLayout?: string[],
+    rcl?: number,
+    asArray?: boolean,
+}
+export interface BunkerLayoutResults {
+    rcl?: number,
+    buildings:Partial<{
+        [structureType in BuildableStructureConstant]:{
+            pos:BunkerLayoutPos[]
+        }
+    }>,
+}
+function getLayout(layout: string[], params?: BunkerGetLayoutParams): any
+{
+    const height = layout.length;
+    const width = layout[0].length;
+    params||={};
+    if(typeof(params.structureType) == "string") params.structureType = [params.structureType];
+    // convert [road,container,extension] into {.:road,C:container,E:extension}
+    const structureTypesMap = new Map((params.structureType||[]).map((s:BuildableStructureConstant)=>[layoutMappingReverse[s],s]));
+    const results: BunkerLayoutResults = {buildings:{}};
+    if(params.rcl) results.rcl = params.rcl;
+    for (let y = 0; y < height; y++) {
+        for (let x = 0; x < width; x++) {
+            if (params.levelLayout && params.rcl && params.rcl < parseInt(params.levelLayout[y][x])) continue;
+            const layoutChar = layout[y][x];
+            const structureChar = layoutChar.toUpperCase();
+            const structureType = layoutMappingForward[structureChar];
+            if(structureType) {
+                const pos = {x:x, y:y};
+                if(structureTypesMap.size==0 || structureTypesMap.get(structureChar)) {
+                    (results.buildings[structureType]||={pos:[]}).pos.push(pos);
+                }
+                if (layoutChar != structureChar && (structureTypesMap.size == 0 || structureTypesMap.get('.'))) {
+                    // emit a road if the character was the wrong case and we're doing roads or everything
+                    (results.buildings[STRUCTURE_ROAD]||={pos:[]}).pos.push(pos);
+                }
+            }
+        }
+    }
+
+    if (!params.asArray) return results;
+
+    const resultsArray: BunkerLayoutArrayEntry[] = [];
+    for(let structureType in results.buildings) {
+        for(let pos of results.buildings[structureType as BuildableStructureConstant]!.pos) {
+            const entry: BunkerLayoutArrayEntry = {x:pos.x, y:pos.y, structureType:structureType as BuildableStructureConstant};
+            if (!params.rcl && params.levelLayout) entry.rcl = parseInt(params.levelLayout[pos.y][pos.x]);
+            resultsArray.push(entry);
+        }
+    }
+    return resultsArray;
+};
+
+const BunkerLayout = {
+    layoutMappingForward: layoutMappingForward,
+    layoutMappingReverse: layoutMappingReverse,
+    getLayout: getLayout,
+}
+
+export default BunkerLayout;
\ No newline at end of file