burnrate 0.1.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.
- package/LICENSE +21 -0
- package/README.md +507 -0
- package/dist/cli/format.d.ts +13 -0
- package/dist/cli/format.js +319 -0
- package/dist/cli/index.d.ts +7 -0
- package/dist/cli/index.js +1121 -0
- package/dist/cli/setup.d.ts +8 -0
- package/dist/cli/setup.js +143 -0
- package/dist/core/async-engine.d.ts +411 -0
- package/dist/core/async-engine.js +2274 -0
- package/dist/core/async-worldgen.d.ts +19 -0
- package/dist/core/async-worldgen.js +221 -0
- package/dist/core/engine.d.ts +154 -0
- package/dist/core/engine.js +1104 -0
- package/dist/core/pathfinding.d.ts +38 -0
- package/dist/core/pathfinding.js +146 -0
- package/dist/core/types.d.ts +489 -0
- package/dist/core/types.js +359 -0
- package/dist/core/worldgen.d.ts +22 -0
- package/dist/core/worldgen.js +292 -0
- package/dist/db/database.d.ts +83 -0
- package/dist/db/database.js +829 -0
- package/dist/db/turso-database.d.ts +177 -0
- package/dist/db/turso-database.js +1586 -0
- package/dist/mcp/server.d.ts +7 -0
- package/dist/mcp/server.js +1877 -0
- package/dist/server/api.d.ts +8 -0
- package/dist/server/api.js +1234 -0
- package/dist/server/async-tick-server.d.ts +5 -0
- package/dist/server/async-tick-server.js +63 -0
- package/dist/server/errors.d.ts +78 -0
- package/dist/server/errors.js +156 -0
- package/dist/server/rate-limit.d.ts +22 -0
- package/dist/server/rate-limit.js +134 -0
- package/dist/server/tick-server.d.ts +9 -0
- package/dist/server/tick-server.js +114 -0
- package/dist/server/validation.d.ts +194 -0
- package/dist/server/validation.js +114 -0
- package/package.json +65 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BURNRATE Async World Generator
|
|
3
|
+
* Creates the initial map for a new season (returns data for async database)
|
|
4
|
+
*/
|
|
5
|
+
import { Zone, Route } from './types.js';
|
|
6
|
+
interface WorldGenConfig {
|
|
7
|
+
hubs: number;
|
|
8
|
+
factories: number;
|
|
9
|
+
fields: number;
|
|
10
|
+
junctions: number;
|
|
11
|
+
fronts: number;
|
|
12
|
+
strongholds: number;
|
|
13
|
+
}
|
|
14
|
+
export declare function generateWorldData(config?: WorldGenConfig): {
|
|
15
|
+
zones: Omit<Zone, 'id'>[];
|
|
16
|
+
routes: Omit<Route, 'id'>[];
|
|
17
|
+
};
|
|
18
|
+
export declare function mapRouteNamesToIds(routes: Omit<Route, 'id'>[], zoneNameToId: Map<string, string>): Omit<Route, 'id'>[];
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BURNRATE Async World Generator
|
|
3
|
+
* Creates the initial map for a new season (returns data for async database)
|
|
4
|
+
*/
|
|
5
|
+
import { BURN_RATES, emptyInventory } from './types.js';
|
|
6
|
+
const DEFAULT_CONFIG = {
|
|
7
|
+
hubs: 3,
|
|
8
|
+
factories: 8,
|
|
9
|
+
fields: 12,
|
|
10
|
+
junctions: 10,
|
|
11
|
+
fronts: 6,
|
|
12
|
+
strongholds: 3
|
|
13
|
+
};
|
|
14
|
+
export function generateWorldData(config = DEFAULT_CONFIG) {
|
|
15
|
+
const zones = [];
|
|
16
|
+
const zoneNames = [];
|
|
17
|
+
// Generate Hubs (safe starting areas)
|
|
18
|
+
const hubNames = ['Hub.Central', 'Hub.East', 'Hub.West'];
|
|
19
|
+
for (let i = 0; i < config.hubs; i++) {
|
|
20
|
+
const name = hubNames[i] || `Hub.${i + 1}`;
|
|
21
|
+
zoneNames.push(name);
|
|
22
|
+
zones.push({
|
|
23
|
+
name,
|
|
24
|
+
type: 'hub',
|
|
25
|
+
ownerId: null,
|
|
26
|
+
supplyLevel: 100,
|
|
27
|
+
burnRate: BURN_RATES.hub,
|
|
28
|
+
complianceStreak: 0,
|
|
29
|
+
suStockpile: 0,
|
|
30
|
+
inventory: { ...emptyInventory(), credits: 10000 },
|
|
31
|
+
productionCapacity: 0,
|
|
32
|
+
garrisonLevel: 10,
|
|
33
|
+
marketDepth: 2.0,
|
|
34
|
+
medkitStockpile: 0,
|
|
35
|
+
commsStockpile: 0
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
// Generate Factories
|
|
39
|
+
const factoryPrefixes = ['Factory', 'Mill', 'Works', 'Plant'];
|
|
40
|
+
const factorySuffixes = ['North', 'South', 'East', 'West', 'Central', 'Upper', 'Lower', 'Main'];
|
|
41
|
+
for (let i = 0; i < config.factories; i++) {
|
|
42
|
+
const prefix = factoryPrefixes[i % factoryPrefixes.length];
|
|
43
|
+
const suffix = factorySuffixes[i % factorySuffixes.length];
|
|
44
|
+
const name = `${prefix}.${suffix}`;
|
|
45
|
+
zoneNames.push(name);
|
|
46
|
+
zones.push({
|
|
47
|
+
name,
|
|
48
|
+
type: 'factory',
|
|
49
|
+
ownerId: null,
|
|
50
|
+
supplyLevel: 100,
|
|
51
|
+
burnRate: BURN_RATES.factory,
|
|
52
|
+
complianceStreak: 0,
|
|
53
|
+
suStockpile: 0,
|
|
54
|
+
inventory: emptyInventory(),
|
|
55
|
+
productionCapacity: 100,
|
|
56
|
+
garrisonLevel: 2,
|
|
57
|
+
marketDepth: 1.0,
|
|
58
|
+
medkitStockpile: 0,
|
|
59
|
+
commsStockpile: 0
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
// Generate Fields
|
|
63
|
+
const fieldTypes = [
|
|
64
|
+
{ prefix: 'Mine', resource: 'ore' },
|
|
65
|
+
{ prefix: 'Refinery', resource: 'fuel' },
|
|
66
|
+
{ prefix: 'Farm', resource: 'grain' },
|
|
67
|
+
{ prefix: 'Grove', resource: 'fiber' }
|
|
68
|
+
];
|
|
69
|
+
for (let i = 0; i < config.fields; i++) {
|
|
70
|
+
const fieldType = fieldTypes[i % fieldTypes.length];
|
|
71
|
+
const name = `${fieldType.prefix}.${i + 1}`;
|
|
72
|
+
zoneNames.push(name);
|
|
73
|
+
const inventory = emptyInventory();
|
|
74
|
+
inventory[fieldType.resource] = 500;
|
|
75
|
+
zones.push({
|
|
76
|
+
name,
|
|
77
|
+
type: 'field',
|
|
78
|
+
ownerId: null,
|
|
79
|
+
supplyLevel: 100,
|
|
80
|
+
burnRate: BURN_RATES.field,
|
|
81
|
+
complianceStreak: 0,
|
|
82
|
+
suStockpile: 0,
|
|
83
|
+
inventory,
|
|
84
|
+
productionCapacity: 50,
|
|
85
|
+
garrisonLevel: 0,
|
|
86
|
+
marketDepth: 0.5,
|
|
87
|
+
medkitStockpile: 0,
|
|
88
|
+
commsStockpile: 0
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
// Generate Junctions
|
|
92
|
+
for (let i = 0; i < config.junctions; i++) {
|
|
93
|
+
const name = `Junction.${String.fromCharCode(65 + i)}`;
|
|
94
|
+
zoneNames.push(name);
|
|
95
|
+
zones.push({
|
|
96
|
+
name,
|
|
97
|
+
type: 'junction',
|
|
98
|
+
ownerId: null,
|
|
99
|
+
supplyLevel: 100,
|
|
100
|
+
burnRate: BURN_RATES.junction,
|
|
101
|
+
complianceStreak: 0,
|
|
102
|
+
suStockpile: 0,
|
|
103
|
+
inventory: emptyInventory(),
|
|
104
|
+
productionCapacity: 0,
|
|
105
|
+
garrisonLevel: 0,
|
|
106
|
+
marketDepth: 0.5,
|
|
107
|
+
medkitStockpile: 0,
|
|
108
|
+
commsStockpile: 0
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
// Generate Fronts
|
|
112
|
+
for (let i = 0; i < config.fronts; i++) {
|
|
113
|
+
const name = `Front.${['Alpha', 'Bravo', 'Charlie', 'Delta', 'Echo', 'Foxtrot'][i] || `F${i + 1}`}`;
|
|
114
|
+
zoneNames.push(name);
|
|
115
|
+
zones.push({
|
|
116
|
+
name,
|
|
117
|
+
type: 'front',
|
|
118
|
+
ownerId: null,
|
|
119
|
+
supplyLevel: 50,
|
|
120
|
+
burnRate: BURN_RATES.front,
|
|
121
|
+
complianceStreak: 0,
|
|
122
|
+
suStockpile: 0,
|
|
123
|
+
inventory: emptyInventory(),
|
|
124
|
+
productionCapacity: 0,
|
|
125
|
+
garrisonLevel: 5,
|
|
126
|
+
marketDepth: 0.2,
|
|
127
|
+
medkitStockpile: 0,
|
|
128
|
+
commsStockpile: 0
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
// Generate Strongholds
|
|
132
|
+
for (let i = 0; i < config.strongholds; i++) {
|
|
133
|
+
const name = `Stronghold.${['Prime', 'Omega', 'Nexus'][i] || `S${i + 1}`}`;
|
|
134
|
+
zoneNames.push(name);
|
|
135
|
+
zones.push({
|
|
136
|
+
name,
|
|
137
|
+
type: 'stronghold',
|
|
138
|
+
ownerId: null,
|
|
139
|
+
supplyLevel: 25,
|
|
140
|
+
burnRate: BURN_RATES.stronghold,
|
|
141
|
+
complianceStreak: 0,
|
|
142
|
+
suStockpile: 0,
|
|
143
|
+
inventory: emptyInventory(),
|
|
144
|
+
productionCapacity: 0,
|
|
145
|
+
garrisonLevel: 10,
|
|
146
|
+
marketDepth: 0.1,
|
|
147
|
+
medkitStockpile: 0,
|
|
148
|
+
commsStockpile: 0
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
// Generate routes (create a connected graph)
|
|
152
|
+
const routes = [];
|
|
153
|
+
const totalZones = zones.length;
|
|
154
|
+
// Connect hubs to factories
|
|
155
|
+
for (let i = 0; i < config.hubs; i++) {
|
|
156
|
+
const hubIdx = i;
|
|
157
|
+
for (let j = 0; j < 3; j++) {
|
|
158
|
+
const factoryIdx = config.hubs + (i * 3 + j) % config.factories;
|
|
159
|
+
routes.push(createRoute(zoneNames[hubIdx], zoneNames[factoryIdx], 2, 0.05));
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
// Connect factories to fields
|
|
163
|
+
for (let i = 0; i < config.factories; i++) {
|
|
164
|
+
const factoryIdx = config.hubs + i;
|
|
165
|
+
const field1Idx = config.hubs + config.factories + (i * 2) % config.fields;
|
|
166
|
+
const field2Idx = config.hubs + config.factories + (i * 2 + 1) % config.fields;
|
|
167
|
+
routes.push(createRoute(zoneNames[factoryIdx], zoneNames[field1Idx], 3, 0.1));
|
|
168
|
+
routes.push(createRoute(zoneNames[factoryIdx], zoneNames[field2Idx], 3, 0.1));
|
|
169
|
+
}
|
|
170
|
+
// Connect junctions
|
|
171
|
+
const junctionStart = config.hubs + config.factories + config.fields;
|
|
172
|
+
for (let i = 0; i < config.junctions; i++) {
|
|
173
|
+
const jIdx = junctionStart + i;
|
|
174
|
+
// Connect to some factories
|
|
175
|
+
const factoryIdx = config.hubs + (i * 2) % config.factories;
|
|
176
|
+
routes.push(createRoute(zoneNames[jIdx], zoneNames[factoryIdx], 2, 0.15));
|
|
177
|
+
// Connect to next junction
|
|
178
|
+
if (i < config.junctions - 1) {
|
|
179
|
+
routes.push(createRoute(zoneNames[jIdx], zoneNames[jIdx + 1], 2, 0.2));
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
// Connect fronts to junctions and other fronts
|
|
183
|
+
const frontStart = junctionStart + config.junctions;
|
|
184
|
+
for (let i = 0; i < config.fronts; i++) {
|
|
185
|
+
const fIdx = frontStart + i;
|
|
186
|
+
const jIdx = junctionStart + (i * 2) % config.junctions;
|
|
187
|
+
routes.push(createRoute(zoneNames[fIdx], zoneNames[jIdx], 3, 0.3));
|
|
188
|
+
// Connect to next front
|
|
189
|
+
if (i < config.fronts - 1) {
|
|
190
|
+
routes.push(createRoute(zoneNames[fIdx], zoneNames[fIdx + 1], 2, 0.35));
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
// Connect strongholds to fronts
|
|
194
|
+
const strongholdStart = frontStart + config.fronts;
|
|
195
|
+
for (let i = 0; i < config.strongholds; i++) {
|
|
196
|
+
const sIdx = strongholdStart + i;
|
|
197
|
+
const f1Idx = frontStart + (i * 2) % config.fronts;
|
|
198
|
+
const f2Idx = frontStart + (i * 2 + 1) % config.fronts;
|
|
199
|
+
routes.push(createRoute(zoneNames[sIdx], zoneNames[f1Idx], 3, 0.4));
|
|
200
|
+
routes.push(createRoute(zoneNames[sIdx], zoneNames[f2Idx], 3, 0.4));
|
|
201
|
+
}
|
|
202
|
+
return { zones, routes };
|
|
203
|
+
}
|
|
204
|
+
function createRoute(fromName, toName, distance, risk) {
|
|
205
|
+
return {
|
|
206
|
+
fromZoneId: fromName, // Will be replaced with actual IDs after zone creation
|
|
207
|
+
toZoneId: toName,
|
|
208
|
+
distance,
|
|
209
|
+
capacity: 1000,
|
|
210
|
+
baseRisk: risk,
|
|
211
|
+
chokepointRating: 1.0 + Math.random() * 0.5
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
// Helper to convert zone names to IDs after creation
|
|
215
|
+
export function mapRouteNamesToIds(routes, zoneNameToId) {
|
|
216
|
+
return routes.map(r => ({
|
|
217
|
+
...r,
|
|
218
|
+
fromZoneId: zoneNameToId.get(r.fromZoneId) || r.fromZoneId,
|
|
219
|
+
toZoneId: zoneNameToId.get(r.toZoneId) || r.toZoneId
|
|
220
|
+
}));
|
|
221
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BURNRATE Game Engine
|
|
3
|
+
* Handles tick processing, supply burn, shipment movement, and combat resolution
|
|
4
|
+
*/
|
|
5
|
+
import { GameDatabase } from '../db/database.js';
|
|
6
|
+
import { Shipment, Unit, MarketOrder, GameEvent, Inventory, Resource } from './types.js';
|
|
7
|
+
export declare class GameEngine {
|
|
8
|
+
private db;
|
|
9
|
+
constructor(db: GameDatabase);
|
|
10
|
+
/**
|
|
11
|
+
* Process a single game tick
|
|
12
|
+
* This is the core simulation loop
|
|
13
|
+
*/
|
|
14
|
+
processTick(): {
|
|
15
|
+
tick: number;
|
|
16
|
+
events: GameEvent[];
|
|
17
|
+
};
|
|
18
|
+
private processSupplyBurn;
|
|
19
|
+
private processShipments;
|
|
20
|
+
private completeShipment;
|
|
21
|
+
private checkInterception;
|
|
22
|
+
/**
|
|
23
|
+
* Get all raiders deployed on a specific route
|
|
24
|
+
*/
|
|
25
|
+
private getRaidersOnRoute;
|
|
26
|
+
private interceptShipment;
|
|
27
|
+
private processUnitMaintenance;
|
|
28
|
+
private processContractExpiration;
|
|
29
|
+
private resetDailyActions;
|
|
30
|
+
private processFieldRegeneration;
|
|
31
|
+
/**
|
|
32
|
+
* Check if player can perform an action (rate limiting)
|
|
33
|
+
*/
|
|
34
|
+
canPlayerAct(playerId: string): {
|
|
35
|
+
allowed: boolean;
|
|
36
|
+
reason?: string;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Record that player took an action
|
|
40
|
+
*/
|
|
41
|
+
recordPlayerAction(playerId: string): void;
|
|
42
|
+
/**
|
|
43
|
+
* Create a new shipment with an explicit path
|
|
44
|
+
* Players must specify waypoints - no automatic pathfinding
|
|
45
|
+
*/
|
|
46
|
+
createShipmentWithPath(playerId: string, type: 'courier' | 'freight' | 'convoy', path: string[], cargo: Partial<Inventory>): {
|
|
47
|
+
success: boolean;
|
|
48
|
+
shipment?: Shipment;
|
|
49
|
+
error?: string;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Create a new shipment (legacy - uses pathfinding internally)
|
|
53
|
+
* @deprecated Use createShipmentWithPath for explicit control
|
|
54
|
+
*/
|
|
55
|
+
createShipment(playerId: string, type: 'courier' | 'freight' | 'convoy', fromZoneId: string, toZoneId: string, cargo: Partial<Inventory>): {
|
|
56
|
+
success: boolean;
|
|
57
|
+
shipment?: Shipment;
|
|
58
|
+
error?: string;
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Place a market order
|
|
62
|
+
*/
|
|
63
|
+
placeOrder(playerId: string, zoneId: string, resource: Resource, side: 'buy' | 'sell', price: number, quantity: number): {
|
|
64
|
+
success: boolean;
|
|
65
|
+
order?: MarketOrder;
|
|
66
|
+
error?: string;
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* Match buy and sell orders
|
|
70
|
+
*/
|
|
71
|
+
private matchOrders;
|
|
72
|
+
private executeTrade;
|
|
73
|
+
/**
|
|
74
|
+
* Deposit SU to a zone
|
|
75
|
+
*/
|
|
76
|
+
depositSU(playerId: string, zoneId: string, amount: number): {
|
|
77
|
+
success: boolean;
|
|
78
|
+
error?: string;
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* Scan a zone or route for intel
|
|
82
|
+
* If player is in a faction, intel is automatically shared with faction members
|
|
83
|
+
*/
|
|
84
|
+
scan(playerId: string, targetType: 'zone' | 'route', targetId: string): {
|
|
85
|
+
success: boolean;
|
|
86
|
+
intel?: any;
|
|
87
|
+
error?: string;
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* Produce resources or units at a Factory
|
|
91
|
+
*/
|
|
92
|
+
produce(playerId: string, output: string, quantity: number): {
|
|
93
|
+
success: boolean;
|
|
94
|
+
produced?: number;
|
|
95
|
+
units?: Unit[];
|
|
96
|
+
error?: string;
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* Extract raw resources from a Field
|
|
100
|
+
*/
|
|
101
|
+
extract(playerId: string, quantity: number): {
|
|
102
|
+
success: boolean;
|
|
103
|
+
extracted?: {
|
|
104
|
+
resource: string;
|
|
105
|
+
amount: number;
|
|
106
|
+
};
|
|
107
|
+
error?: string;
|
|
108
|
+
};
|
|
109
|
+
/**
|
|
110
|
+
* Capture a neutral zone for a faction
|
|
111
|
+
*/
|
|
112
|
+
captureZone(playerId: string, zoneId: string): {
|
|
113
|
+
success: boolean;
|
|
114
|
+
error?: string;
|
|
115
|
+
};
|
|
116
|
+
/**
|
|
117
|
+
* Assign an escort unit to a shipment
|
|
118
|
+
*/
|
|
119
|
+
assignEscort(playerId: string, unitId: string, shipmentId: string): {
|
|
120
|
+
success: boolean;
|
|
121
|
+
error?: string;
|
|
122
|
+
};
|
|
123
|
+
/**
|
|
124
|
+
* List a unit for sale at current location (must be at Hub)
|
|
125
|
+
*/
|
|
126
|
+
listUnitForSale(playerId: string, unitId: string, price: number): {
|
|
127
|
+
success: boolean;
|
|
128
|
+
error?: string;
|
|
129
|
+
};
|
|
130
|
+
/**
|
|
131
|
+
* Remove a unit from sale
|
|
132
|
+
*/
|
|
133
|
+
unlistUnit(playerId: string, unitId: string): {
|
|
134
|
+
success: boolean;
|
|
135
|
+
error?: string;
|
|
136
|
+
};
|
|
137
|
+
/**
|
|
138
|
+
* Hire (buy) a unit that's listed for sale
|
|
139
|
+
*/
|
|
140
|
+
hireUnit(playerId: string, unitId: string): {
|
|
141
|
+
success: boolean;
|
|
142
|
+
unit?: Unit;
|
|
143
|
+
error?: string;
|
|
144
|
+
};
|
|
145
|
+
/**
|
|
146
|
+
* Deploy a raider unit to interdict a route
|
|
147
|
+
*/
|
|
148
|
+
deployRaider(playerId: string, unitId: string, routeId: string): {
|
|
149
|
+
success: boolean;
|
|
150
|
+
error?: string;
|
|
151
|
+
};
|
|
152
|
+
private findRoute;
|
|
153
|
+
private recordEvent;
|
|
154
|
+
}
|