knosky 0.3.0

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,170 @@
1
+ // KC fs indexer (Phase 1b): a real local folder -> contract v2. Deterministic, $0-token.
2
+ // Pointers + projections ONLY (no file bodies). Dir-name categorizer (always-works fallback).
3
+ // Honors IGNORE_DEFAULTS + .gitignore + .kcignore. Council fixes: allowlist+scrub via contract.serializeNode.
4
+ import fs from 'node:fs';
5
+ import path from 'node:path';
6
+ import { execSync } from 'node:child_process';
7
+ import { SCHEMA_VERSION, IGNORE_DEFAULTS, deriveCategories, serializeNode, validateCity, setRedactTerms, findSecrets } from './contract.mjs';
8
+
9
+ const args = Object.fromEntries(process.argv.slice(2).map((a, i, arr) => a.startsWith('--') ? [a.slice(2), arr[i + 1]] : []).filter(Boolean));
10
+ const ROOT = args.root && path.resolve(args.root);
11
+ const OUT = args.out || null;
12
+ const MAX = parseInt(args.max || '6000', 10);
13
+ const SHARE_SAFE = process.argv.includes('--share-safe');
14
+ const INCLUDE_ABS = process.argv.includes('--include-absolute-root');
15
+ const ALLOW_LEAKS = process.argv.includes('--allow-leaks');
16
+ if (!ROOT || !fs.existsSync(ROOT)) { console.log('usage: node fs-indexer.mjs --root <dir> [--out <file>] [--max N] [--redact a,b]'); process.exit(1); }
17
+ const REDACT = (args.redact || '').split(',').map(s => s.trim()).filter(Boolean);
18
+ if (REDACT.length) setRedactTerms(REDACT);
19
+ const REDACT_RX = REDACT.map(t => new RegExp('\\b' + t.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '\\b', 'i'));
20
+
21
+ const CODE = /\.(ts|tsx|js|jsx|mjs|cjs|py|go|rs|java|rb|php|c|cc|cpp|h|hpp|cs|swift|kt|sh|sql)$/i;
22
+ const DOC = /\.(md|markdown|mdx|txt|rst|adoc)$/i;
23
+ const TEXT_READABLE = /\.(md|markdown|mdx|txt|rst|adoc|ts|tsx|js|jsx|mjs|cjs|py|go|rs|java|rb|php|c|cc|cpp|h|hpp|cs|swift|kt|sh|sql|json|ya?ml|toml|ini|cfg)$/i;
24
+
25
+ function gitRev(root) {
26
+ try { return 'git:' + execSync('git rev-parse --short HEAD', { cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim(); }
27
+ catch { return 'fs:' + new Date().toISOString().slice(0, 10); }
28
+ }
29
+ // Parse .gitignore/.kcignore into conservative matchers (dir names + basenames + *.ext).
30
+ function loadIgnore(root) {
31
+ const pats = [];
32
+ for (const f of ['.gitignore', '.kcignore']) {
33
+ try {
34
+ for (let ln of fs.readFileSync(path.join(root, f), 'utf8').split(/\r?\n/)) {
35
+ ln = ln.trim(); if (!ln || ln.startsWith('#') || ln.startsWith('!')) continue;
36
+ ln = ln.replace(/^\/+/, '').replace(/\/+$/, '');
37
+ if (!ln) continue;
38
+ const rx = '(^|/)' + ln.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '[^/]*') + '(/|$)';
39
+ pats.push(new RegExp(rx, 'i'));
40
+ }
41
+ } catch { /* no file */ }
42
+ }
43
+ return pats;
44
+ }
45
+ const REV = gitRev(ROOT);
46
+ const IGN = [...IGNORE_DEFAULTS, ...loadIgnore(ROOT)];
47
+ const ignored = (rel) => IGN.some(re => re.test(rel));
48
+
49
+ const flags = [];
50
+ let scanned = 0, capped = false, redactedPaths = 0;
51
+ const raw = []; // pre-serialize nodes
52
+
53
+ function excerptAndHeadings(fp, isDoc) {
54
+ let txt = '';
55
+ try { const fd = fs.openSync(fp, 'r'); const buf = Buffer.alloc(4096); const n = fs.readSync(fd, buf, 0, 4096, 0); fs.closeSync(fd); txt = buf.slice(0, n).toString('utf8'); }
56
+ catch { return { title: null, summary: '', headings: [] }; }
57
+ let title = null; const headings = [];
58
+ if (isDoc) {
59
+ const h1 = txt.match(/^#\s+(.+)$/m); if (h1) title = h1[1].trim();
60
+ for (const m of txt.matchAll(/^#{2,3}\s+(.+)$/gm)) { headings.push(m[1].trim()); if (headings.length >= 8) break; }
61
+ }
62
+ // first prose line: skip frontmatter + headings/code fences
63
+ const prose = txt.replace(/^---[\s\S]*?---/, '').split(/\r?\n/).map(s => s.trim())
64
+ .find(s => s && !s.startsWith('#') && !s.startsWith('```') && !s.startsWith('|') && !s.startsWith('<')) || '';
65
+ return { title, summary: prose, headings };
66
+ }
67
+
68
+ function walk(dir, depth) {
69
+ if (capped) return;
70
+ let ents; try { ents = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
71
+ for (const e of ents) {
72
+ if (capped) return;
73
+ const fp = path.join(dir, e.name);
74
+ const rel = path.relative(ROOT, fp).split(path.sep).join('/');
75
+ if (ignored(rel)) continue;
76
+ if (e.isDirectory()) { if (depth < 12) walk(fp, depth + 1); continue; }
77
+ if (!e.isFile()) continue;
78
+ if (REDACT_RX.some(re => re.test(rel))) { redactedPaths++; continue; }
79
+ scanned++;
80
+ if (scanned > MAX) { capped = true; flags.push(`capped at ${MAX} files`); return; }
81
+ const parts = rel.split('/');
82
+ const category = parts.length > 1 ? parts[0] : '(root)';
83
+ const ext = path.extname(rel);
84
+ const kind = DOC.test(rel) ? 'doc' : CODE.test(rel) ? 'code' : 'file';
85
+ const readable = TEXT_READABLE.test(rel);
86
+ const { title, summary, headings } = readable ? excerptAndHeadings(fp, DOC.test(rel)) : { title: null, summary: '', headings: [] };
87
+ raw.push({
88
+ id: 'fs:' + rel,
89
+ kind,
90
+ title: title || parts[parts.length - 1],
91
+ summary,
92
+ category,
93
+ status: 'present',
94
+ tags: ext ? [ext.slice(1)] : [],
95
+ headings,
96
+ links: [],
97
+ provenance: { store: 'fs', ref: rel, source_rev: REV, fetched_at: new Date().toISOString() },
98
+ visibility: 'internal',
99
+ });
100
+ }
101
+ }
102
+
103
+ walk(ROOT, 0);
104
+
105
+ // Accurate ignore inside a git repo: let git decide (catches .gitignore patterns the conservative parser misses).
106
+ try {
107
+ const NL = String.fromCharCode(10);
108
+ const rels = raw.map(n => n.provenance.ref);
109
+ if (rels.length) {
110
+ let ignoredOut = '';
111
+ try { ignoredOut = execSync('git check-ignore --stdin', { cwd: ROOT, input: rels.join(NL), encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] }); }
112
+ catch (e) { ignoredOut = (e && e.stdout) ? String(e.stdout) : ''; } // exit 1 (none ignored) / 128 (not a repo) -> empty
113
+ const gi = new Set(ignoredOut.split(NL).map(s => s.trim()).filter(Boolean));
114
+ if (gi.size) for (let i = raw.length - 1; i >= 0; i--) if (gi.has(raw[i].provenance.ref)) raw.splice(i, 1);
115
+ }
116
+ } catch (e) { /* git unavailable: keep the conservative-parser result */ }
117
+
118
+ // light edges: markdown relative links that resolve to an indexed node
119
+ const byRel = new Map(raw.map(n => [n.provenance.ref, n]));
120
+ for (const n of raw) {
121
+ if (n.kind !== 'doc') continue;
122
+ // (edges intentionally minimal in v1 fs; reserved for Phase 2 — keep links=[] unless trivially resolvable)
123
+ }
124
+
125
+ const nodes = raw.map(serializeNode);
126
+ const catIds = [...new Set(nodes.map(n => n.category))].sort();
127
+ const categories = deriveCategories(catIds);
128
+ const city = {
129
+ schema_version: SCHEMA_VERSION,
130
+ generated_at: new Date().toISOString(),
131
+ source: { kind: 'fs', ref: INCLUDE_ABS ? ROOT : path.basename(ROOT), rev: REV },
132
+ categories, node_count: nodes.length, nodes,
133
+ };
134
+
135
+ const res = validateCity(city);
136
+ const dist = {}; for (const n of nodes) dist[n.category] = (dist[n.category] || 0) + 1;
137
+
138
+ // leakage audit: scan emitted projections for secret-ish patterns + employer strings
139
+ const blob = JSON.stringify(nodes);
140
+ const leakHits = (blob.match(/\b(AKIA[0-9A-Z]{16}|-----BEGIN [A-Z ]*PRIVATE KEY-----)\b/g) || []).length
141
+ + (blob.match(/[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g) || []).length;
142
+
143
+ console.log('--- fs index:', INCLUDE_ABS ? ROOT : path.basename(ROOT), '---');
144
+ console.log('rev', REV, '| scanned files:', scanned, '| nodes:', nodes.length, '| categories:', catIds.length);
145
+ console.log('byKind:', JSON.stringify(nodes.reduce((a, n) => (a[n.kind] = (a[n.kind] || 0) + 1, a), {})));
146
+ console.log('top categories:', Object.entries(dist).sort((a, b) => b[1] - a[1]).slice(0, 8).map(([k, v]) => `${k}:${v}`).join(', '));
147
+ console.log('VALID:', res.ok, res.ok ? '' : '\n - ' + res.errors.slice(0, 8).join('\n - '));
148
+ console.log('post-scrub leak hits (emails/keys, should be ~0):', leakHits);
149
+ console.log('redacted-path files skipped:', redactedPaths);
150
+ if (flags.length) console.log('flags:', flags.join('; '));
151
+
152
+ // --- fail-closed safe-share audit on the FINAL emitted projections (defense in depth) ---
153
+ const secretHits = findSecrets(blob);
154
+ const totalSecrets = secretHits.reduce((a, h) => a + h[1], 0);
155
+ const secretList = secretHits.map(h => h[0] + ':' + h[1]).join(', ');
156
+ if (SHARE_SAFE) {
157
+ console.log('');
158
+ console.log('KnoSky safety report');
159
+ console.log('- Files scanned:', scanned);
160
+ console.log('- Files skipped (redact-path):', redactedPaths);
161
+ console.log('- Secrets found:', totalSecrets, totalSecrets ? '[' + secretList + ']' : '');
162
+ console.log('- Absolute path in output:', INCLUDE_ABS ? 'INCLUDED' : 'removed (basename only)');
163
+ console.log('- Risk level:', totalSecrets ? 'HIGH' : 'Low');
164
+ }
165
+ if (totalSecrets && !ALLOW_LEAKS) {
166
+ console.error('BLOCKED: ' + totalSecrets + ' secret-like value(s) in the index [' + secretList + ']. Nothing written.');
167
+ console.error('Add the offending paths to .kcignore or pass --redact <term>, then rebuild. Override with --allow-leaks (not recommended).');
168
+ process.exit(1);
169
+ }
170
+ if (OUT) { fs.writeFileSync(OUT, JSON.stringify(city, null, 2) + '\n'); console.log('wrote', OUT); }
@@ -0,0 +1,46 @@
1
+ // Deterministic N-category city layout (Phase 2b). Implements locked D3:
2
+ // stable order (category manifest order), each district a square sized to hold its nodes,
3
+ // districts shelf-packed into a roughly square city. Recompute is deterministic (same data -> same layout).
4
+ export function layoutCity(city, opts = {}) {
5
+ const PAD = opts.pad ?? 1; // building-cells of padding inside a district
6
+ const GAP = opts.gap ?? 2; // building-cells between districts
7
+
8
+ const cats = (city.categories || []).slice().sort((a, b) => a.order - b.order);
9
+ const byCat = new Map(cats.map(c => [c.id, []]));
10
+ for (const n of city.nodes || []) {
11
+ if (!byCat.has(n.category)) byCat.set(n.category, []); // tolerate uncategorized
12
+ byCat.get(n.category).push(n);
13
+ }
14
+ // ensure any extra categories (not in manifest) still get a district, appended in id order
15
+ const extra = [...byCat.keys()].filter(id => !cats.find(c => c.id === id)).sort();
16
+ const order = [...cats.map(c => ({ id: c.id, label: c.label, color: c.color })),
17
+ ...extra.map(id => ({ id, label: id, color: '#888' }))];
18
+
19
+ const districts = order.map((c, i) => {
20
+ const nodes = byCat.get(c.id) || [];
21
+ const side = Math.max(1, Math.ceil(Math.sqrt(nodes.length || 1)));
22
+ return { id: c.id, label: c.label, color: c.color, idx: i, nodes, side, w: side + PAD * 2, h: side + PAD * 2 };
23
+ });
24
+
25
+ const totalArea = districts.reduce((a, d) => a + d.w * d.h, 0);
26
+ const rowTarget = Math.max(Math.max(...districts.map(d => d.w)), Math.ceil(Math.sqrt(totalArea)));
27
+
28
+ const placed = [];
29
+ let x = 0, y = 0, rowH = 0;
30
+ for (const d of districts) {
31
+ if (x > 0 && x + d.w > rowTarget) { x = 0; y += rowH + GAP; rowH = 0; }
32
+ d.x = x; d.y = y; rowH = Math.max(rowH, d.h);
33
+ d.nodes.forEach((n, i) => {
34
+ placed.push({
35
+ id: n.id, title: n.title, category: n.category, kind: n.kind,
36
+ gx: d.x + PAD + (i % d.side),
37
+ gy: d.y + PAD + Math.floor(i / d.side),
38
+ ref: n.provenance ? n.provenance.ref : null,
39
+ });
40
+ });
41
+ x += d.w + GAP;
42
+ }
43
+ const gridW = Math.max(1, ...placed.map(p => p.gx + 1), ...districts.map(d => d.x + d.w));
44
+ const gridH = Math.max(1, ...placed.map(p => p.gy + 1), ...districts.map(d => d.y + d.h));
45
+ return { districts, nodes: placed, gridW, gridH };
46
+ }
@@ -0,0 +1,53 @@
1
+ // KC retrieval lib (Phase 2a) — the shared seam both the MCP and the City read.
2
+ // Navigation-scoped (where/what/how-connected + citations), NOT code-RAG. Pure, no deps.
3
+ import fs from 'node:fs';
4
+
5
+ export function load(cityPath) {
6
+ const city = JSON.parse(fs.readFileSync(cityPath, 'utf8'));
7
+ const byId = new Map((city.nodes || []).map(n => [n.id, n]));
8
+ return { city, byId };
9
+ }
10
+ const tok = (s) => (String(s || '').toLowerCase().match(/[a-z0-9]+/g) || []);
11
+
12
+ function fmt(n, score) {
13
+ return { id: n.id, title: n.title, summary: n.summary, category: n.category, kind: n.kind, score, provenance: n.provenance };
14
+ }
15
+
16
+ // search: token scoring over title/headings/summary/tags/category. Returns ranked hits w/ provenance (citations).
17
+ export function search(ctx, q, { limit = 10, category = null } = {}) {
18
+ const qt = [...new Set(tok(q))];
19
+ if (!qt.length) return [];
20
+ const out = [];
21
+ for (const n of ctx.city.nodes) {
22
+ if (category && n.category !== category) continue;
23
+ const title = new Set(tok(n.title)), heads = new Set(tok((n.headings || []).join(' ')));
24
+ const summ = new Set(tok(n.summary)), tags = new Set((n.tags || []).map(x => String(x).toLowerCase()));
25
+ const cat = new Set(tok(n.category));
26
+ let score = 0;
27
+ for (const t of qt) {
28
+ if (title.has(t)) score += 5;
29
+ if (heads.has(t)) score += 3;
30
+ if (summ.has(t)) score += 2;
31
+ if (tags.has(t)) score += 2;
32
+ if (cat.has(t)) score += 1;
33
+ }
34
+ if (score > 0) out.push({ n, score });
35
+ }
36
+ out.sort((a, b) => b.score - a.score || String(a.n.id).localeCompare(String(b.n.id)));
37
+ return out.slice(0, limit).map(r => fmt(r.n, r.score));
38
+ }
39
+
40
+ export function getNode(ctx, id) { const n = ctx.byId.get(id); return n ? fmt(n, null) : null; }
41
+
42
+ export function listCategories(ctx) {
43
+ const counts = {};
44
+ for (const n of ctx.city.nodes) counts[n.category] = (counts[n.category] || 0) + 1;
45
+ return (ctx.city.categories || []).map(c => ({ ...c, count: counts[c.id] || 0 }));
46
+ }
47
+
48
+ // provenance = the citation: where the live source is + what it links to.
49
+ export function getProvenance(ctx, id) {
50
+ const n = ctx.byId.get(id);
51
+ if (!n) return null;
52
+ return { id, title: n.title, provenance: n.provenance, links: n.links || [] };
53
+ }
package/mcp/server.mjs ADDED
@@ -0,0 +1,58 @@
1
+ // Knowledge City — local stdio MCP server (Phase 2a).
2
+ // Read-only retrieval over a local contract-v2 index. No network; data never leaves the machine.
3
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
+ import { z } from "zod";
6
+ import { load, search, getNode, listCategories, getProvenance } from "../core/retrieve.mjs";
7
+
8
+ const CITY = process.env.KC_CITY || process.argv[2];
9
+ if (!CITY) { console.error("Knowledge City MCP: set KC_CITY (or pass a path) to a city-data.v2.json"); process.exit(1); }
10
+ const ctx = load(CITY);
11
+
12
+ const server = new McpServer({ name: "knowledge-city", version: "0.2.0" });
13
+
14
+ server.registerTool("kc_search", {
15
+ title: "Search the Knowledge City",
16
+ description: "Search the indexed knowledge base by keywords. Returns ranked items (title, summary, category) each with a provenance citation (source path + revision) that links back to the live file. Use for 'where does X live / what was decided about Y / how does this connect'. Navigation, not full-text code search.",
17
+ inputSchema: { query: z.string().max(500).describe("keywords"), limit: z.number().int().min(1).max(50).optional().describe("max results (default 10, max 50)"), category: z.string().max(200).optional().describe("restrict to one category id") },
18
+ annotations: { readOnlyHint: true, openWorldHint: false },
19
+ }, async ({ query, limit, category }) => {
20
+ const hits = search(ctx, String(query).slice(0, 500), { limit: Math.min(Math.max(1, limit || 10), 50), category: category ? String(category).slice(0, 200) : null });
21
+ const text = hits.length
22
+ ? hits.map(h => `- [${h.category}] ${h.title} — ${h.summary || ""}\n source: ${h.provenance.ref} @ ${h.provenance.source_rev} (id: ${h.id})`).join("\n")
23
+ : "No matches.";
24
+ return { content: [{ type: "text", text }], structuredContent: { results: hits } };
25
+ });
26
+
27
+ server.registerTool("kc_get_node", {
28
+ title: "Get one item",
29
+ description: "Fetch a single indexed item by id (title, summary, category, kind) with its provenance citation.",
30
+ inputSchema: { id: z.string().max(400).describe("node id, e.g. fs:src/index.ts") },
31
+ annotations: { readOnlyHint: true },
32
+ }, async ({ id }) => {
33
+ const n = getNode(ctx, id);
34
+ return { content: [{ type: "text", text: n ? JSON.stringify(n, null, 2) : `No item with id ${id}` }], structuredContent: n || {} };
35
+ });
36
+
37
+ server.registerTool("kc_list_categories", {
38
+ title: "List categories",
39
+ description: "List the knowledge categories (city districts) with item counts.",
40
+ inputSchema: {},
41
+ annotations: { readOnlyHint: true },
42
+ }, async () => {
43
+ const cats = listCategories(ctx);
44
+ return { content: [{ type: "text", text: cats.map(c => `${c.label} (${c.id}): ${c.count}`).join("\n") }], structuredContent: { categories: cats } };
45
+ });
46
+
47
+ server.registerTool("kc_get_provenance", {
48
+ title: "Get provenance (citation)",
49
+ description: "Get the citation for an item: the live source ref + revision, plus its links to related items.",
50
+ inputSchema: { id: z.string().max(400) },
51
+ annotations: { readOnlyHint: true },
52
+ }, async ({ id }) => {
53
+ const p = getProvenance(ctx, id);
54
+ return { content: [{ type: "text", text: p ? JSON.stringify(p, null, 2) : `No item with id ${id}` }], structuredContent: p || {} };
55
+ });
56
+
57
+ await server.connect(new StdioServerTransport());
58
+ console.error(`knowledge-city MCP ready — ${ctx.city.node_count} nodes, ${(ctx.city.categories||[]).length} categories`);
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "knosky",
3
+ "version": "0.3.0",
4
+ "description": "Turn any repo or docs folder into an explorable city, plus a local MCP that grounds your AI in your own source with citations. Local-first, free, $0 tokens.",
5
+ "type": "module",
6
+ "bin": {
7
+ "knosky": "bin/knosky.mjs"
8
+ },
9
+ "files": [
10
+ "bin",
11
+ "core",
12
+ "renderer",
13
+ "mcp/server.mjs",
14
+ "README.md",
15
+ "LICENSE.md",
16
+ "SECURITY.md",
17
+ "PRIVACY.md",
18
+ "LIMITATIONS.md",
19
+ "CREDITS.md",
20
+ "CHANGELOG.md"
21
+ ],
22
+ "engines": {
23
+ "node": ">=18"
24
+ },
25
+ "dependencies": {
26
+ "@modelcontextprotocol/sdk": "^1.12.0",
27
+ "zod": "^3.23.8"
28
+ },
29
+ "keywords": [
30
+ "repo-map",
31
+ "codebase-visualization",
32
+ "mcp",
33
+ "ai",
34
+ "citations",
35
+ "local-first",
36
+ "developer-tools"
37
+ ],
38
+ "license": "SEE LICENSE IN LICENSE.md",
39
+ "homepage": "https://knosky.com",
40
+ "repository": {
41
+ "type": "git",
42
+ "url": "git+https://github.com/SathiaAI/knosky.git"
43
+ }
44
+ }
@@ -0,0 +1,131 @@
1
+ <TextureAtlas imagePath="sheet.png">
2
+ <SubTexture name="buildingTiles_000.png" x="596" y="85" width="99" height="85"/>
3
+ <SubTexture name="buildingTiles_001.png" x="0" y="1270" width="133" height="127"/>
4
+ <SubTexture name="buildingTiles_002.png" x="0" y="1905" width="132" height="127"/>
5
+ <SubTexture name="buildingTiles_003.png" x="265" y="0" width="132" height="127"/>
6
+ <SubTexture name="buildingTiles_004.png" x="265" y="127" width="132" height="127"/>
7
+ <SubTexture name="buildingTiles_005.png" x="596" y="170" width="99" height="54"/>
8
+ <SubTexture name="buildingTiles_006.png" x="596" y="224" width="99" height="54"/>
9
+ <SubTexture name="buildingTiles_007.png" x="596" y="278" width="99" height="85"/>
10
+ <SubTexture name="buildingTiles_008.png" x="596" y="363" width="99" height="85"/>
11
+ <SubTexture name="buildingTiles_009.png" x="265" y="893" width="132" height="127"/>
12
+ <SubTexture name="buildingTiles_010.png" x="133" y="1275" width="132" height="127"/>
13
+ <SubTexture name="buildingTiles_011.png" x="133" y="1147" width="132" height="128"/>
14
+ <SubTexture name="buildingTiles_012.png" x="396" y="1786" width="132" height="127"/>
15
+ <SubTexture name="buildingTiles_013.png" x="596" y="766" width="99" height="54"/>
16
+ <SubTexture name="buildingTiles_014.png" x="396" y="1913" width="132" height="127"/>
17
+ <SubTexture name="buildingTiles_015.png" x="596" y="618" width="99" height="85"/>
18
+ <SubTexture name="buildingTiles_016.png" x="596" y="533" width="99" height="85"/>
19
+ <SubTexture name="buildingTiles_017.png" x="265" y="1659" width="132" height="127"/>
20
+ <SubTexture name="buildingTiles_018.png" x="265" y="639" width="132" height="127"/>
21
+ <SubTexture name="buildingTiles_019.png" x="265" y="381" width="132" height="128"/>
22
+ <SubTexture name="buildingTiles_020.png" x="133" y="639" width="132" height="127"/>
23
+ <SubTexture name="buildingTiles_021.png" x="133" y="385" width="132" height="127"/>
24
+ <SubTexture name="buildingTiles_022.png" x="0" y="1397" width="133" height="127"/>
25
+ <SubTexture name="buildingTiles_023.png" x="596" y="0" width="99" height="85"/>
26
+ <SubTexture name="buildingTiles_024.png" x="595" y="1641" width="99" height="85"/>
27
+ <SubTexture name="buildingTiles_025.png" x="133" y="0" width="132" height="128"/>
28
+ <SubTexture name="buildingTiles_026.png" x="133" y="128" width="132" height="127"/>
29
+ <SubTexture name="buildingTiles_027.png" x="133" y="255" width="132" height="130"/>
30
+ <SubTexture name="buildingTiles_028.png" x="133" y="1659" width="132" height="127"/>
31
+ <SubTexture name="buildingTiles_029.png" x="265" y="254" width="132" height="127"/>
32
+ <SubTexture name="buildingTiles_030.png" x="0" y="254" width="133" height="127"/>
33
+ <SubTexture name="buildingTiles_031.png" x="595" y="1230" width="99" height="85"/>
34
+ <SubTexture name="buildingTiles_032.png" x="595" y="1145" width="99" height="85"/>
35
+ <SubTexture name="buildingTiles_033.png" x="265" y="1020" width="132" height="128"/>
36
+ <SubTexture name="buildingTiles_034.png" x="133" y="1402" width="132" height="127"/>
37
+ <SubTexture name="buildingTiles_035.png" x="133" y="1529" width="132" height="130"/>
38
+ <SubTexture name="buildingTiles_036.png" x="0" y="762" width="133" height="127"/>
39
+ <SubTexture name="buildingTiles_037.png" x="0" y="1016" width="133" height="127"/>
40
+ <SubTexture name="buildingTiles_038.png" x="397" y="434" width="100" height="85"/>
41
+ <SubTexture name="buildingTiles_039.png" x="497" y="945" width="99" height="85"/>
42
+ <SubTexture name="buildingTiles_040.png" x="265" y="509" width="132" height="130"/>
43
+ <SubTexture name="buildingTiles_041.png" x="265" y="1148" width="132" height="127"/>
44
+ <SubTexture name="buildingTiles_042.png" x="0" y="635" width="133" height="127"/>
45
+ <SubTexture name="buildingTiles_043.png" x="497" y="681" width="99" height="85"/>
46
+ <SubTexture name="buildingTiles_044.png" x="397" y="519" width="100" height="85"/>
47
+ <SubTexture name="buildingTiles_045.png" x="497" y="536" width="99" height="85"/>
48
+ <SubTexture name="buildingTiles_046.png" x="265" y="1402" width="132" height="130"/>
49
+ <SubTexture name="buildingTiles_047.png" x="397" y="664" width="100" height="85"/>
50
+ <SubTexture name="buildingTiles_048.png" x="497" y="329" width="99" height="85"/>
51
+ <SubTexture name="buildingTiles_049.png" x="397" y="894" width="100" height="85"/>
52
+ <SubTexture name="buildingTiles_050.png" x="596" y="448" width="99" height="85"/>
53
+ <SubTexture name="buildingTiles_051.png" x="397" y="289" width="100" height="85"/>
54
+ <SubTexture name="buildingTiles_052.png" x="497" y="60" width="99" height="85"/>
55
+ <SubTexture name="buildingTiles_053.png" x="397" y="809" width="100" height="85"/>
56
+ <SubTexture name="buildingTiles_054.png" x="397" y="979" width="100" height="85"/>
57
+ <SubTexture name="buildingTiles_055.png" x="496" y="1632" width="99" height="85"/>
58
+ <SubTexture name="buildingTiles_056.png" x="397" y="1064" width="100" height="85"/>
59
+ <SubTexture name="buildingTiles_057.png" x="496" y="1511" width="99" height="60"/>
60
+ <SubTexture name="buildingTiles_058.png" x="496" y="1451" width="99" height="60"/>
61
+ <SubTexture name="buildingTiles_059.png" x="496" y="1391" width="99" height="60"/>
62
+ <SubTexture name="buildingTiles_060.png" x="496" y="1331" width="99" height="60"/>
63
+ <SubTexture name="buildingTiles_061.png" x="497" y="1030" width="99" height="60"/>
64
+ <SubTexture name="buildingTiles_062.png" x="496" y="1209" width="99" height="60"/>
65
+ <SubTexture name="buildingTiles_063.png" x="496" y="1149" width="99" height="60"/>
66
+ <SubTexture name="buildingTiles_064.png" x="497" y="621" width="99" height="60"/>
67
+ <SubTexture name="buildingTiles_065.png" x="497" y="414" width="99" height="61"/>
68
+ <SubTexture name="buildingTiles_066.png" x="497" y="269" width="99" height="60"/>
69
+ <SubTexture name="buildingTiles_067.png" x="397" y="749" width="100" height="60"/>
70
+ <SubTexture name="buildingTiles_068.png" x="497" y="0" width="99" height="60"/>
71
+ <SubTexture name="buildingTiles_069.png" x="496" y="1717" width="99" height="60"/>
72
+ <SubTexture name="buildingTiles_070.png" x="496" y="1571" width="99" height="61"/>
73
+ <SubTexture name="buildingTiles_071.png" x="496" y="1269" width="99" height="62"/>
74
+ <SubTexture name="buildingTiles_072.png" x="397" y="374" width="100" height="60"/>
75
+ <SubTexture name="buildingTiles_073.png" x="397" y="1698" width="99" height="60"/>
76
+ <SubTexture name="buildingTiles_074.png" x="397" y="1209" width="99" height="61"/>
77
+ <SubTexture name="buildingTiles_075.png" x="397" y="1149" width="99" height="60"/>
78
+ <SubTexture name="buildingTiles_076.png" x="596" y="703" width="99" height="63"/>
79
+ <SubTexture name="buildingTiles_077.png" x="397" y="1455" width="99" height="62"/>
80
+ <SubTexture name="buildingTiles_078.png" x="233" y="1786" width="13" height="24"/>
81
+ <SubTexture name="buildingTiles_079.png" x="397" y="227" width="100" height="62"/>
82
+ <SubTexture name="buildingTiles_080.png" x="397" y="604" width="100" height="60"/>
83
+ <SubTexture name="buildingTiles_081.png" x="397" y="1517" width="99" height="60"/>
84
+ <SubTexture name="buildingTiles_082.png" x="397" y="1270" width="99" height="61"/>
85
+ <SubTexture name="buildingTiles_083.png" x="397" y="1637" width="99" height="61"/>
86
+ <SubTexture name="buildingTiles_084.png" x="497" y="206" width="99" height="63"/>
87
+ <SubTexture name="buildingTiles_085.png" x="0" y="127" width="133" height="127"/>
88
+ <SubTexture name="buildingTiles_086.png" x="397" y="172" width="100" height="55"/>
89
+ <SubTexture name="buildingTiles_087.png" x="397" y="110" width="100" height="62"/>
90
+ <SubTexture name="buildingTiles_088.png" x="497" y="145" width="99" height="61"/>
91
+ <SubTexture name="buildingTiles_089.png" x="397" y="1577" width="99" height="60"/>
92
+ <SubTexture name="buildingTiles_090.png" x="397" y="1331" width="99" height="63"/>
93
+ <SubTexture name="buildingTiles_091.png" x="397" y="1394" width="99" height="61"/>
94
+ <SubTexture name="buildingTiles_092.png" x="0" y="1778" width="133" height="127"/>
95
+ <SubTexture name="buildingTiles_093.png" x="265" y="1532" width="132" height="127"/>
96
+ <SubTexture name="buildingTiles_094.png" x="133" y="1786" width="100" height="55"/>
97
+ <SubTexture name="buildingTiles_095.png" x="133" y="1841" width="100" height="55"/>
98
+ <SubTexture name="buildingTiles_096.png" x="497" y="475" width="99" height="61"/>
99
+ <SubTexture name="buildingTiles_097.png" x="497" y="766" width="99" height="61"/>
100
+ <SubTexture name="buildingTiles_098.png" x="497" y="827" width="99" height="63"/>
101
+ <SubTexture name="buildingTiles_099.png" x="0" y="381" width="133" height="127"/>
102
+ <SubTexture name="buildingTiles_100.png" x="0" y="508" width="133" height="127"/>
103
+ <SubTexture name="buildingTiles_101.png" x="265" y="766" width="132" height="127"/>
104
+ <SubTexture name="buildingTiles_102.png" x="397" y="0" width="100" height="55"/>
105
+ <SubTexture name="buildingTiles_103.png" x="497" y="890" width="99" height="55"/>
106
+ <SubTexture name="buildingTiles_104.png" x="528" y="1777" width="99" height="63"/>
107
+ <SubTexture name="buildingTiles_105.png" x="528" y="1840" width="99" height="61"/>
108
+ <SubTexture name="buildingTiles_106.png" x="0" y="889" width="133" height="127"/>
109
+ <SubTexture name="buildingTiles_107.png" x="0" y="1651" width="133" height="127"/>
110
+ <SubTexture name="buildingTiles_108.png" x="264" y="1913" width="132" height="127"/>
111
+ <SubTexture name="buildingTiles_109.png" x="264" y="1786" width="132" height="127"/>
112
+ <SubTexture name="buildingTiles_110.png" x="397" y="55" width="100" height="55"/>
113
+ <SubTexture name="buildingTiles_111.png" x="497" y="1090" width="99" height="55"/>
114
+ <SubTexture name="buildingTiles_112.png" x="528" y="1901" width="99" height="63"/>
115
+ <SubTexture name="buildingTiles_113.png" x="265" y="1275" width="132" height="127"/>
116
+ <SubTexture name="buildingTiles_114.png" x="0" y="0" width="133" height="127"/>
117
+ <SubTexture name="buildingTiles_115.png" x="0" y="1524" width="133" height="127"/>
118
+ <SubTexture name="buildingTiles_116.png" x="133" y="893" width="132" height="127"/>
119
+ <SubTexture name="buildingTiles_117.png" x="133" y="766" width="132" height="127"/>
120
+ <SubTexture name="buildingTiles_118.png" x="528" y="1964" width="99" height="54"/>
121
+ <SubTexture name="buildingTiles_119.png" x="595" y="1315" width="99" height="54"/>
122
+ <SubTexture name="buildingTiles_120.png" x="595" y="1369" width="99" height="56"/>
123
+ <SubTexture name="buildingTiles_121.png" x="595" y="1425" width="99" height="54"/>
124
+ <SubTexture name="buildingTiles_122.png" x="0" y="1143" width="133" height="127"/>
125
+ <SubTexture name="buildingTiles_123.png" x="133" y="1020" width="132" height="127"/>
126
+ <SubTexture name="buildingTiles_124.png" x="132" y="1905" width="132" height="127"/>
127
+ <SubTexture name="buildingTiles_125.png" x="133" y="512" width="132" height="127"/>
128
+ <SubTexture name="buildingTiles_126.png" x="595" y="1479" width="99" height="54"/>
129
+ <SubTexture name="buildingTiles_127.png" x="595" y="1533" width="99" height="54"/>
130
+ <SubTexture name="buildingTiles_128.png" x="595" y="1587" width="99" height="54"/>
131
+ </TextureAtlas>
@@ -0,0 +1,13 @@
1
+ <TextureAtlas imagePath="sheet.png">
2
+ <SubTexture name="cityDetails_000.png" x="125" y="64" width="22" height="37"/>
3
+ <SubTexture name="cityDetails_001.png" x="71" y="45" width="32" height="32"/>
4
+ <SubTexture name="cityDetails_002.png" x="71" y="77" width="32" height="32"/>
5
+ <SubTexture name="cityDetails_003.png" x="103" y="32" width="32" height="32"/>
6
+ <SubTexture name="cityDetails_004.png" x="103" y="0" width="32" height="32"/>
7
+ <SubTexture name="cityDetails_005.png" x="147" y="46" width="22" height="46"/>
8
+ <SubTexture name="cityDetails_006.png" x="135" y="0" width="22" height="46"/>
9
+ <SubTexture name="cityDetails_007.png" x="103" y="64" width="22" height="37"/>
10
+ <SubTexture name="cityDetails_008.png" x="0" y="64" width="71" height="63"/>
11
+ <SubTexture name="cityDetails_009.png" x="0" y="0" width="71" height="64"/>
12
+ <SubTexture name="cityDetails_010.png" x="71" y="0" width="32" height="45"/>
13
+ </TextureAtlas>