diff --git a/assets-source/luneraria/build_map_props.py b/assets-source/luneraria/build_map_props.py new file mode 100644 index 0000000..d88f781 --- /dev/null +++ b/assets-source/luneraria/build_map_props.py @@ -0,0 +1,248 @@ +"""Map dressing for Champions of Luneraria: river bridge, curb stone, trees, rocks, banner +and edge cliffs, exported as separate named meshes in one GLB. Godot instances them with +MultiMeshes. Stone, bark and leaves use ENV_* names so the Kluft shaders (with wind on +foliage) apply at runtime. + +Run: ALSOFT_DRIVERS=null blender -b --python-exit-code 1 --python assets-source/luneraria/build_map_props.py +Godot frame per prop: y up, prop origin at ground level; the bridge spans local +x. +""" +from pathlib import Path +import bpy, bmesh, json, hashlib, struct +from math import sin, cos, pi +from mathutils import Vector, noise + +P = Path(__file__).resolve().parents[2] +SRC = Path(__file__).resolve().parent +OUT = P / 'assets/luneraria' +bpy.ops.wm.read_factory_settings(use_empty=True) +bpy.context.preferences.filepaths.save_version = 0 + + +def G(x, y, z): + return Vector((x, -z, y)) + + +def material(name, color, rough=.9, metal=0.0, emission=0.0): + m = bpy.data.materials.new(name) + m.diffuse_color = (*color, 1) + m.use_nodes = True + bs = m.node_tree.nodes.get('Principled BSDF') + bs.inputs['Base Color'].default_value = (*color, 1) + bs.inputs['Roughness'].default_value = rough + bs.inputs['Metallic'].default_value = metal + if emission: + bs.inputs['Emission Color'].default_value = (*color, 1) + bs.inputs['Emission Strength'].default_value = emission + return m + + +M = { + 'limestone': material('ENV_limestone', (.283, .246, .178)), + 'paving': material('ENV_paving', (.19, .185, .15), .92), + 'cliff': material('ENV_cliff', (.133, .153, .138)), + 'bark': material('ENV_bark', (.087, .056, .032), .93), + 'leaf_dark': material('ENV_leaf_dark', (.031, .07, .04), .89), + 'leaf_mid': material('ENV_leaf_mid', (.084, .144, .054), .85), + 'leaf_light': material('ENV_leaf_light', (.258, .305, .091), .79), + 'soil': material('ENV_soil', (.10, .15, .065), .97), + 'brass': material('ENV_brass', (.515, .366, .153), .45, .7), + 'cloth': material('PR_Banner', (.9, .9, .9), .8), +} +groups = {} + + +def add(group, name, vs, fs, kind, smooth=False): + me = bpy.data.meshes.new(name) + me.from_pydata([tuple(v) for v in vs], [], fs) + me.update() + me.materials.append(M[kind]) + for p in me.polygons: + p.use_smooth = smooth + ob = bpy.data.objects.new(name, me) + bpy.context.collection.objects.link(ob) + groups.setdefault(group, []).append(ob) + return ob + + +def box(group, name, c, size, kind, yaw=0.0): + cx, cy, cz = c + hx, hy, hz = size[0] / 2, size[1] / 2, size[2] / 2 + vs = [] + for dy in (-1, 1): + for dz in (-1, 1): + for dx in (-1, 1): + x, z = dx * hx, dz * hz + vs.append(G(cx + x * cos(yaw) - z * sin(yaw), cy + dy * hy, cz + x * sin(yaw) + z * cos(yaw))) + return add(group, name, vs, [(0, 1, 3, 2), (4, 6, 7, 5), (0, 4, 5, 1), (2, 3, 7, 6), (0, 2, 6, 4), (1, 5, 7, 3)], kind) + + +def lathe(group, name, profile, sides, kind, c=(0, 0, 0), jitter=0.0, seed=0.0, smooth=False): + cx, cy, cz = c + vs, fs = [], [] + for r, y in profile: + for k in range(sides): + a = 2 * pi * k / sides + rr = r * (1 + jitter * noise.noise(Vector((cos(a) * 2, y * 1.3, sin(a) * 2 + seed)))) + vs.append(G(cx + cos(a) * rr, cy + y, cz + sin(a) * rr)) + n = len(profile) + for j in range(n - 1): + for k in range(sides): + a, b = j * sides + k, j * sides + (k + 1) % sides + fs.append((a, b, b + sides, a + sides)) + fs += [tuple(reversed(range(sides))), tuple(range((n - 1) * sides, n * sides))] + return add(group, name, vs, fs, kind, smooth) + + +def blob(group, name, c, size, kind, seed, subdiv=2, amp=.3, floor=None, smooth=False): + bm = bmesh.new() + bmesh.ops.create_icosphere(bm, subdivisions=subdiv, radius=1.0) + cc = G(*c) + for v in bm.verts: + d = v.co.normalized() + r = 1 + amp * noise.noise(d * 1.7 + Vector((seed, seed * .7, seed * .3))) + gx, gy, gz = d.x * r * size[0], d.z * r * size[1], -d.y * r * size[2] + if floor is not None and gy < floor * size[1]: + gy = floor * size[1] + v.co = cc + Vector((gx, -gz, gy)) + me = bpy.data.meshes.new(name) + bm.to_mesh(me) + bm.free() + me.materials.append(M[kind]) + for p in me.polygons: + p.use_smooth = smooth + ob = bpy.data.objects.new(name, me) + bpy.context.collection.objects.link(ob) + groups.setdefault(group, []).append(ob) + return ob + + +def tube(group, name, points, radii, kind, sides=8, steps=10, seed=0.0, gnarl=0.0): + pts = [G(*p) for p in points] + vs, fs = [], [] + for j in range(steps + 1): + t = j / steps + seg = min(len(pts) - 2, int(t * (len(pts) - 1))) + u = t * (len(pts) - 1) - seg + p = pts[seg].lerp(pts[seg + 1], u) + tangent = (pts[seg + 1] - pts[seg]).normalized() + ref = Vector((0, 0, 1)) if abs(tangent.z) < .9 else Vector((1, 0, 0)) + a1 = tangent.cross(ref).normalized() + a2 = tangent.cross(a1) + rs = min(len(radii) - 2, int(t * (len(radii) - 1))) + ru = t * (len(radii) - 1) - rs + radius = radii[rs] * (1 - ru) + radii[rs + 1] * ru + for k in range(sides): + ang = 2 * pi * k / sides + w = 1 + gnarl * noise.noise(Vector((cos(ang), t * 5, sin(ang) + seed))) + vs.append(p + (a1 * cos(ang) + a2 * sin(ang)) * radius * w) + for j in range(steps): + for k in range(sides): + a, b = j * sides + k, j * sides + (k + 1) % sides + fs.append((a, b, b + sides, a + sides)) + fs += [tuple(reversed(range(sides))), tuple(range(steps * sides, (steps + 1) * sides))] + return add(group, name, vs, fs, kind, True) + + +# ---------------------------------------------------------------- bridge (spans local x, 11 m long, 7 m wide) +# The simulation is flat: the deck sits at ground level so units walk on it, not inside it. +L, Wd = 11.0, 7.0 +for i in range(12): + x = -L / 2 + (i + .5) * L / 12 + box('Bridge', 'Deck_%d' % i, (x, .05, 0), (L / 12 + .02, .1, Wd), 'paving') + for s_ in (-1, 1): + box('Bridge', 'Parapet_%d_%d' % (i, s_), (x, .42, s_ * (Wd / 2 - .22)), (L / 12 + .03, .62, .38), 'limestone') +for x in (-L / 2 + .4, -1.8, 1.8, L / 2 - .4): + for s_ in (-1, 1): + lathe('Bridge', 'Post_%d_%d' % (int(x * 10), s_), [(.32, 0), (.32, 1.05), (.26, 1.15), (.2, 1.3)], 6, 'limestone', (x, 0, s_ * (Wd / 2 - .22))) + lathe('Bridge', 'Cap_%d_%d' % (int(x * 10), s_), [(.22, 0), (.22, .1), (.14, .16)], 6, 'brass', (x, 1.3, s_ * (Wd / 2 - .22))) +for x in (-2.6, 2.6): + for s_ in (-1, 1): + box('Bridge', 'Pier_%d_%d' % (int(x), s_), (x, -.6, s_ * (Wd / 2 + .15)), (1.1, 1.4, .5), 'limestone') + +# ---------------------------------------------------------------- curb stone (placed along lane edges) +box('Curb', 'Curb', (0, .12, 0), (1.3, .26, .42), 'limestone') + +# ---------------------------------------------------------------- trees +for v, (h, seed) in enumerate([(3.8, 1.0), (4.6, 2.0), (3.2, 3.0)]): + group = 'Pine_%d' % v + tube(group, 'Trunk', [(0, 0, 0), (0, h * .55, 0), (0, h, 0)], [.24, .17, .08], 'bark', 8, 6, seed, .1) + for k in range(5): + y = h * (.28 + k * .15) + r = (1.35 - k * .22) * (1 + .08 * v) + lathe(group, 'Cone_%d' % k, [(r, y), (r * .75, y + .35), (.05, y + h * .26)], 9, ('leaf_dark', 'leaf_mid')[k % 2], (0, 0, 0), .12, seed + k) +for v, (h, seed) in enumerate([(3.4, 4.0), (4.0, 5.0), (2.8, 6.0)]): + group = 'Oak_%d' % v + tube(group, 'Trunk', [(0, 0, 0), (.15, h * .5, .05), (-.1, h * .85, 0)], [.34, .24, .15], 'bark', 9, 8, seed, .15) + for k in range(3): + ang = k * 2.1 + seed + tube(group, 'Branch_%d' % k, [(0, h * .55, 0), (cos(ang) * .9, h * .78, sin(ang) * .9)], [.13, .06], 'bark', 6, 4, seed + k, .1) + for k in range(9): + ang = k * 2.4 + seed + rr = .55 + .5 * ((k * .618) % 1.0) + s = .95 - .2 * ((k * .37) % 1.0) + blob(group, 'Leaves_%d' % k, (cos(ang) * rr, h * .88 + .5 * ((k * .53) % 1.0), sin(ang) * rr), (s, s * .78, s), + ('leaf_mid', 'leaf_light', 'leaf_dark')[k % 3], seed * 10 + k, 2, .28) + for k in range(4): + ang = k * 1.57 + seed + tube(group, 'Root_%d' % k, [(0, .25, 0), (cos(ang) * .7, .05, sin(ang) * .7)], [.14, .05], 'bark', 6, 4, seed + k) + +# ---------------------------------------------------------------- rocks and shrubs +for v, (size, seed) in enumerate([((1.2, .8, 1.0), 7.0), ((1.8, 1.1, 1.3), 8.0), ((.8, .5, .7), 9.0)]): + group = 'Rock_%d' % v + blob(group, 'Rock', (0, size[1] * .3, 0), size, 'cliff', seed, 2, .34, -.35) + blob(group, 'Moss', (size[0] * .15, size[1] * .82, 0), (size[0] * .55, size[1] * .25, size[2] * .5), 'leaf_dark', seed + 1, 1, .3, -.2) + for k in range(2): + blob(group, 'Pebble_%d' % k, (size[0] * (.9 + .3 * k), .1, size[2] * (.5 - k)), (.28, .18, .24), 'limestone', seed + 3 + k, 1, .3, -.3) +for v in range(2): + blob('Shrub_%d' % v, 'Shrub', (0, .3, 0), (.8 + .3 * v, .55, .7 + .2 * v), ('leaf_mid', 'leaf_dark')[v], 11 + v, 2, .35, -.3) + +# ---------------------------------------------------------------- banner (team colour at runtime via PR_Banner) +lathe('Banner', 'Pole', [(.07, 0), (.07, 4.2), (.1, 4.3), (.02, 4.5)], 6, 'brass') +vs, fs = [], [] +for j in range(6): + for i in range(4): + x = .08 + i * .35 + y = 4.1 - j * .45 + z = .08 * sin(i * 1.3 + j * .6) + vs.append(G(x, y - (.25 * (i / 3) if j == 5 else 0), z)) +for j in range(5): + for i in range(3): + a = j * 4 + i + fs.append((a, a + 1, a + 5, a + 4)) +add('Banner', 'Cloth', vs, fs, 'cloth', True) + +# ---------------------------------------------------------------- edge cliffs (a 12 m wall segment, local x) +for i in range(7): + x = -6 + i * 2 + h = 3.4 + 1.6 * noise.noise(Vector((i * .7, 3.1, 0))) + blob('EdgeCliff', 'Block_%d' % i, (x, h * .45, 0), (1.6, h * .6, 1.8), 'cliff', 20 + i, 2, .3, -.5) + blob('EdgeCliff', 'Grass_%d' % i, (x, h * .85, -.3), (1.2, .35, 1.3), 'leaf_mid', 30 + i, 1, .3, -.2) + +# Join each group into one mesh object per prop and triangulate. +exported = [] +for group, objects in groups.items(): + bpy.ops.object.select_all(action='DESELECT') + for ob in objects: + ob.select_set(True) + bpy.context.view_layer.objects.active = objects[0] + bpy.ops.object.join() + joined = bpy.context.object + joined.name = group + bm = bmesh.new() + bm.from_mesh(joined.data) + bmesh.ops.triangulate(bm, faces=list(bm.faces)) + bm.to_mesh(joined.data) + bm.free() + exported.append(joined) +bpy.ops.object.select_all(action='DESELECT') +for ob in exported: + ob.select_set(True) +bpy.ops.wm.save_as_mainfile(filepath=str(SRC / 'map_props.blend')) +bpy.ops.export_scene.gltf(filepath=str(OUT / 'map_props.glb'), export_format='GLB', use_selection=True, + export_materials='EXPORT', export_yup=True, export_animations=False) +raw = (OUT / 'map_props.glb').read_bytes() +doc = json.loads(raw[20:20 + struct.unpack_from(' hi: + error, c, d = d - hi, a + delta.normalized() * hi, hi + elif d < lo: + error, c, d = lo - d, a + delta.normalized() * lo, lo + line = (c - a) / d + v = V(pole) - a + perpendicular = (v - line * v.dot(line)).normalized() + along = (l1 * l1 - l2 * l2 + d * d) / (2 * d) + return a + line * along + perpendicular * sqrt(max(0, l1 * l1 - along * along)), c, error + + +def frame(u, hint): + u = V(u).normalized() + f = V(hint) - u * V(hint).dot(u) + f.normalize() + return u, f, u.cross(f) + + +class Figure: + def __init__(self, key, sp): + self.key, self.sp = key, sp + self.parts, self.defs, self.mats, self.errors = [], {}, {}, [] + self.s = sp['s'] + self.pal = dict(BASE) + self.pal.update(sp['pal']) + self.ankle = .085 + self.hip = self.ankle + .84 * sp['leg'] + self.knee = self.ankle + .84 * sp['leg'] * .52 + self.waist = self.hip + .13 + self.chest = self.hip + .38 + self.shoulder = self.hip + .52 + self.neck = self.hip + .60 + self.head_c = self.neck + .10 + .085 * sp['head'] + self.hover = sp.get('hover', 0.0) + self.extra_bones = [] + self.scaled = set() + + # ------------------------------------------------------------ space + def X(self, p): + """Pre-space (upright human proportions) -> final space: bend, scale, hover.""" + x, y, z = p + a = self.sp.get('hunch', 0.0) + if a and z > self.waist: + t = min(1.0, (z - self.waist) / (self.shoulder - self.waist)) + ang = a * t * t * (3 - 2 * t) + dz = z - self.waist + y, z = y * cos(ang) - dz * sin(ang), self.waist + y * sin(ang) + dz * cos(ang) + return V((x * self.s, y * self.s, z * self.s + self.hover)) + + # ------------------------------------------------------------ materials and meshes + def mat(self, kind): + name = 'LP_%s_%s' % (self.sp['tag'], KIND_NAMES[kind]) + if name in self.mats: + return self.mats[name] + color, rough, metal, emission = self.pal[kind] + color = lin(color) + m = bpy.data.materials.new(name) + m.diffuse_color = (*color, 1) + m.use_nodes = True + n, links = m.node_tree.nodes, m.node_tree.links + bs = n.get('Principled BSDF') + bs.inputs['Base Color'].default_value = (*color, 1) + bs.inputs['Metallic'].default_value = metal + bs.inputs['Roughness'].default_value = rough + if not emission: + vertex = n.new('ShaderNodeVertexColor') + vertex.layer_name = 'SurfaceColor' + links.new(vertex.outputs['Color'], bs.inputs['Base Color']) + if emission: + bs.inputs['Emission Color'].default_value = (*color, 1) + bs.inputs['Emission Strength'].default_value = emission + else: + tex = n.new('ShaderNodeTexNoise') + tex.inputs['Scale'].default_value = 65 if metal else 30 + bump = n.new('ShaderNodeBump') + bump.inputs['Strength'].default_value = .12 if metal else .2 + bump.inputs['Distance'].default_value = .006 + links.new(tex.outputs['Fac'], bump.inputs['Height']) + links.new(bump.outputs['Normal'], bs.inputs['Normal']) + self.mats[name] = m + return m + + def mesh(self, name, vs, fs, kind, bone, weights=None, smooth=True, uv=None, pre=True): + vs = [self.X(v) if pre else V(v) for v in vs] + me = bpy.data.meshes.new(name) + me.from_pydata([tuple(v) for v in vs], [], fs) + me.update() + ob = bpy.data.objects.new(name, me) + bpy.context.collection.objects.link(ob) + self.parts.append(ob) + m = self.mat(kind) + me.materials.append(m) + col = me.color_attributes.new(name='SurfaceColor', type='FLOAT_COLOR', domain='CORNER') + tex = me.uv_layers.new(name='UVMap') + base = m.diffuse_color[:3] + for face in me.polygons: + face.use_smooth = smooth + for li in face.loop_indices: + vi = me.loops[li].vertex_index + x, y, z = vs[vi] + variation = .93 + .05 * sin(17 * x + 11 * z) + .025 * cos(24 * y - 7 * z) + col.data[li].color = (*(min(1, c * variation) for c in base), 1) + tex.data[li].uv = uv[vi] if uv else (x * 1.7 + y * .3, z * 1.5) + groups = {} + for i in range(len(vs)): + ws = weights[i] if weights else {bone: 1.0} + total = sum(ws.values()) + for key, weight in ws.items(): + if weight <= 1e-8: + continue + groups.setdefault(key, ob.vertex_groups.get(key) or ob.vertex_groups.new(name=key)) + groups[key].add([i], weight / total, 'REPLACE') + return ob + + def tube(self, name, points, radii, kind, bone, sides=16, steps=20, flatten=1.0, weight_fn=None, groove=0.0, + smooth=True, pre=True, ref=None, lobes=5): + pts = [V(x) for x in points] + vs, fs, ws, uv = [], [], [], [] + for j in range(steps + 1): + t = j / steps + p = catmull(pts, t) + seg = min(len(radii) - 2, int(t * (len(radii) - 1))) + a = t * (len(radii) - 1) - seg + radius = radii[seg] * (1 - a) + radii[seg + 1] * a + tangent = (catmull(pts, min(1, t + .002)) - catmull(pts, max(0, t - .002))).normalized() + r = V(ref) if ref else (V((0, 1, 0)) if abs(tangent.y) < .9 else V((1, 0, 0))) + u = tangent.cross(r).normalized() + v = tangent.cross(u) + for k in range(sides): + angle = 2 * pi * k / sides + detail = 1 + groove * cos(lobes * angle + t * 4) + vs.append(p + radius * detail * (u * cos(angle) + v * sin(angle) * flatten)) + ws.append(weight_fn(t) if weight_fn else {bone: 1}) + uv.append((k / sides, t)) + if j: + for k in range(sides): + a0, b0 = (j - 1) * sides + k, (j - 1) * sides + (k + 1) % sides + fs.append((a0, b0, b0 + sides, a0 + sides)) + fs += [tuple(reversed(range(sides))), tuple(range(steps * sides, (steps + 1) * sides))] + return self.mesh(name, vs, fs, kind, bone, ws, smooth, uv, pre) + + def ell(self, name, c, r, kind, bone, seg=18, rings=11, smooth=True, pre=True, axes=None, bumps=0.0, weights=None): + ax = [V(a).normalized() for a in axes] if axes else [V((1, 0, 0)), V((0, 1, 0)), V((0, 0, 1))] + c = V(c) + + def at(th, a): + w = 1 + bumps * sin(3 * a + 5 * th) * sin(2 * th) + return c + ax[0] * r[0] * sin(th) * cos(a) * w + ax[1] * r[1] * sin(th) * sin(a) * w + ax[2] * r[2] * cos(th) * w + vs = [c + ax[2] * r[2]] + for j in range(1, rings): + for k in range(seg): + vs.append(at(pi * j / rings, 2 * pi * k / seg)) + vs.append(c - ax[2] * r[2]) + fs = [(0, 1 + k, 1 + (k + 1) % seg) for k in range(seg)] + for j in range(rings - 2): + for k in range(seg): + a0 = 1 + j * seg + k + b0 = 1 + j * seg + (k + 1) % seg + fs.append((a0, a0 + seg, b0 + seg, b0)) + fs += [(len(vs) - 1, len(vs) - 2 - k, len(vs) - 2 - (k + 1) % seg) for k in range(seg)] + ws = [weights(v) for v in vs] if weights else None + return self.mesh(name, vs, fs, kind, bone, ws, smooth, None, pre) + + def plate(self, name, outline, center, depth, kind, bone, normal=(0, -1, 0), pre=True, weights=None): + """Closed tapered plate with bevel belt and a proud central ridge, never a box.""" + c, n = V(center), V(normal).normalized() + front = [V(p) for p in outline] + vs = front + [p.lerp(c, .13) + n * depth * .32 for p in front] + [c + n * depth] + [p - n * depth * .22 for p in front] + k = len(front) + fs = [] + for i in range(k): + q = (i + 1) % k + fs += [(i, q, k + q, k + i), (k + i, k + q, 2 * k), (i, 2 * k + 1 + i, 2 * k + 1 + q, q)] + fs.append(tuple(reversed(range(2 * k + 1, 3 * k + 1)))) + ws = [weights(v) for v in vs] if weights else None + return self.mesh(name, vs, fs, kind, bone, ws, False, None, pre) + + def ring(self, name, p, axis, radius, width, kind, bone, sides=16, pre=True): + axis, p = V(axis).normalized(), V(p) + return self.tube(name, [p - axis * width / 2, p + axis * width / 2], [radius, radius], kind, bone, sides, 3, smooth=False, pre=pre) + + def shard(self, name, points, radius, kind, bone, pre=True, sides=6): + return self.tube(name, points, [radius, .74 * radius, .002], kind, bone, sides, 5, smooth=False, pre=pre) + + def cone(self, name, base, tip, r, kind, bone, sides=8, pre=True): + return self.tube(name, [base, V(base).lerp(V(tip), .5), tip], [r, r * .55, .0015], kind, bone, sides, 6, smooth=True, pre=pre) + + def loft(self, name, sections, kind, weight_fn, sides=24, steps=36, front_bulge=0.0, y0=0.0, smooth=True, scallop=0.0): + """Elliptic loft in pre-space. sections: (z, rx, ry); front_bulge rounds the chest.""" + vs, fs, ws = [], [], [] + for j in range(steps + 1): + t = j / steps + f = t * (len(sections) - 1) + idx = min(len(sections) - 2, int(f)) + u = f - idx + z, rx, ry = [sections[idx][q] * (1 - u) + sections[idx + 1][q] * u for q in range(3)] + for k in range(sides): + a = 2 * pi * k / sides + bulge = 1 + front_bulge * max(0.0, -sin(a)) ** 2 * abs(cos(a)) * 1.6 + edge = 1 + scallop * (cos(a * 9) * .5 + .5) * t + vs.append((rx * cos(a) * edge, y0 + ry * sin(a) * bulge * edge, z)) + ws.append(weight_fn(z, cos(a) * rx)) + if j: + for k in range(sides): + a0, b0 = (j - 1) * sides + k, (j - 1) * sides + (k + 1) % sides + fs.append((a0, b0, b0 + sides, a0 + sides)) + fs += [tuple(reversed(range(sides))), tuple(range(steps * sides, (steps + 1) * sides))] + return self.mesh(name, vs, fs, kind, 'Spine', ws, smooth) + + # ------------------------------------------------------------ weights + def torso_weights(self, z, x=0.0): + if z < self.waist - .05: + return {'Pelvis': 1} + if z < self.waist + .07: + return blend('Pelvis', 'Spine', (z - self.waist + .05) / .12) + if z < self.chest - .06: + return {'Spine': 1} + if z < self.chest + .05: + return blend('Spine', 'Chest', (z - self.chest + .06) / .11) + if z < self.neck - .02: + return {'Chest': 1} + return blend('Chest', 'Neck', (z - self.neck + .02) / .06) + + def skirt_weights(self, top, bottom, share=.45): + """Hanging cloth: pelvis at the belt, partly following the nearer thigh below.""" + def fn(z, x): + t = max(0.0, min(1.0, (top - z) / max(.01, top - bottom))) + side = 'Thigh.L' if x > 0 else 'Thigh.R' + return {'Pelvis': 1 - share * t, side: share * t} + return fn + + # ------------------------------------------------------------ body + def build_body(self, torso_kind='skin', arm_kind='skin', leg_kind='cloth', boot_kind='leather', chest=1.0, belly=1.0): + sp = self.sp + w, d, lb, H = sp['w'], sp['d'], sp['limb'], self.hip + secs = [(-.08, .14, .105), (0, .165, .118), (.06, .158, .112 * belly), (.13, .142, .104 * belly), (.22, .158, .112 * belly), + (.32, .19 * chest, .124 * chest), (.42, .212 * chest, .128 * chest), (.49, .205 * chest, .118), (.55, .15, .095), (.60, .072, .066)] + secs = [(H + z, rx * w, ry * d) for z, rx, ry in secs] + self.loft('Torso', secs, torso_kind, self.torso_weights, front_bulge=.06) + self.torso_sections = secs + self.tube('Neck', [(0, .0, self.neck - .03), (0, -.004, self.neck + .05), (0, -.012, self.head_c - .05)], + [.058 * lb, .054 * lb, .05 * lb], torso_kind if torso_kind not in ('metal', 'cloth', 'cloth2', 'leather') else 'skin', 'Neck', 16, 10, + weight_fn=lambda t: blend('Chest', 'Neck', t * 2) if t < .5 else blend('Neck', 'Head', (t - .5) * 2)) + # Arms in final space from IK against the style's grips. + s = self.s + self.l1, self.l2 = .285 * sp['arm'] * s, .265 * sp['arm'] * s + self.L = self.l1 + self.l2 + self.sh = {side: self.X((sx * .19 * w, .012, self.shoulder - .015)) for side, sx in SIDES} + self.bind_grips() + self.elbow, self.W = {}, {} + for side, sx in SIDES: + g = self.G[side] + e0, _, _ = ik_elbow(self.sh[side], g, self.l1, self.l2, self.pole[side]) + fore = (g - e0).normalized() + w0 = g - fore * .035 * s * lb + elbow, w0, err = ik_elbow(self.sh[side], w0, self.l1, self.l2, self.pole[side]) + self.elbow[side], self.W[side] = elbow, w0 + a, b, c = self.sh[side], elbow, w0 + upper, fore_b, hand = 'UpperArm.' + side, 'Forearm.' + side, 'Hand.' + side + + def arm_weights(t, upper=upper, fore_b=fore_b, hand=hand, side=side): + if t < .12: + return blend('Clavicle.' + side, upper, t / .12) + if t < .42: + return {upper: 1} + if t < .58: + return blend(upper, fore_b, (t - .42) / .16) + if t < .9: + return {fore_b: 1} + return blend(fore_b, hand, (t - .9) / .1) + self.tube('Arm_' + side, [a, a.lerp(b, .45), b, b.lerp(c, .45), c], + [.056 * lb * s, .055 * lb * s, .041 * lb * s, .047 * lb * s, .033 * lb * s], arm_kind, upper, 16, 30, + weight_fn=arm_weights, pre=False) + self.ell('Deltoid_' + side, a + V((sx * .012 * s, 0, .008 * s)), (.066 * lb * s, .066 * lb * s, .07 * lb * s), arm_kind, + 'UpperArm.' + side, 16, 10, pre=False, + weights=lambda v, side=side: blend('Clavicle.' + side, 'UpperArm.' + side, .6)) + # Legs in pre-space (no bending below the waist). + self.leg = {} + for side, sx in SIDES: + hp = V((sx * .092 * w, .005, H - .03)) + if sp.get('digitigrade'): + kn = V((sx * .10 * w, -.12, self.knee + .06)) + an = V((sx * .105 * w, .10, .21)) + toe = V((sx * .11 * w, -.07, .03)) + else: + kn = V((sx * .10 * w, -.035, self.knee)) + an = V((sx * .105 * w, .012, self.ankle)) + toe = V((sx * .11 * w, -.16, .025)) + if self.hover: + kn += V((0, -.04 if sx > 0 else .03, .04)) + toe = an + V((0, -.05, -.1)) + self.leg[side] = (hp, kn, an, toe) + thigh, shin, foot = 'Thigh.' + side, 'Shin.' + side, 'Foot.' + side + + def leg_weights(t, thigh=thigh, shin=shin, foot=foot): + if t < .08: + return blend('Pelvis', thigh, t / .08) + if t < .42: + return {thigh: 1} + if t < .6: + return blend(thigh, shin, (t - .42) / .18) + if t < .92: + return {shin: 1} + return blend(shin, foot, (t - .92) / .08) + self.tube('Leg_' + side, [hp, hp.lerp(kn, .46), kn, kn.lerp(an, .45), an], + [.088 * lb, .08 * lb, .056 * lb, .062 * lb, .04 * lb], leg_kind, thigh, 16, 30, weight_fn=leg_weights) + if boot_kind: + heel = an + V((0, .035, -.045 if not sp.get('digitigrade') else -.03)) + self.tube('Boot_' + side, [heel, an.lerp(toe, .45) + V((0, 0, .012)), toe + V((0, -.012, .012))], + [.05 * lb, .058 * lb, .03 * lb], boot_kind, foot, 12, 12, flatten=.72, smooth=True) + self.ring('BootCuff_' + side, an + V((0, 0, .045)), (0, 0, 1), .052 * lb, .03, boot_kind, 'Shin.' + side, 14) + + # ------------------------------------------------------------ hands + def fist(self, side, kind, grip_r): + """Closed hand around the grip axis in final space (rigid on the hand bone).""" + s, lb = self.s, self.sp['limb'] + g, u, f = self.G[side], self.u[side], self.f[side] + fore = (self.W[side] - self.elbow[side]).normalized() + back = (fore - u * fore.dot(u)).normalized() + across = u.cross(back) + hand = 'Hand.' + side + self.ell('Palm_' + side, g - back * (grip_r + .012 * s * lb) * .4, (.036 * s * lb, .048 * s * lb, .036 * s * lb), kind, hand, 14, 9, + pre=False, axes=[across, u, back]) + for k in range(4): + off = u * ((k - 1.5) * .02 * s * lb) + pts = [] + for q in range(6): + ang = -.5 + q * (3.5 / 5) + pts.append(g + off + (back * cos(ang) + across * sin(ang)) * (grip_r + .011 * s * lb)) + self.tube('Finger_%s%d' % (side, k), pts, [.012 * s * lb, .013 * s * lb, .011 * s * lb], kind, hand, 8, 10, pre=False) + self.tube('Thumb_' + side, [g - back * grip_r * .8 - across * .03 * s * lb + u * .03 * s * lb, + g - across * (grip_r + .012 * s * lb) + u * .03 * s * lb, + g + back * grip_r * .4 - across * (grip_r + .004 * s) + u * .036 * s * lb], + [.014 * s * lb, .013 * s * lb, .01 * s * lb], kind, hand, 8, 8, pre=False) + + def open_hand(self, side, kind, claws=None, spread=.12): + """Open, slightly curled hand pointing along u with palm facing -f; optional claws.""" + s, lb = self.s, self.sp['limb'] + g, u, f = self.G[side], self.u[side], self.f[side] + across = u.cross(f) + hand = 'Hand.' + side + self.ell('Palm_' + side, g, (.04 * s * lb, .05 * s * lb, .02 * s * lb), kind, hand, 14, 9, pre=False, axes=[across, u, f]) + for k in range(4): + base = g + u * .04 * s * lb + across * ((k - 1.5) * .022 * s * lb) + dirn = (u + across * (k - 1.5) * spread).normalized() + p1 = base + dirn * .035 * s * lb - f * .008 * s + p2 = p1 + dirn * .03 * s * lb - f * .018 * s + self.tube('Finger_%s%d' % (side, k), [base, p1, p2], [.012 * s * lb, .011 * s * lb, .009 * s * lb], kind, hand, 8, 8, pre=False) + if claws: + self.cone('Claw_%s%d' % (side, k), p2, p2 + dirn * .05 * s * lb - f * .03 * s, .01 * s * lb, claws, hand, 6, pre=False) + inner = -1 if side == 'L' else 1 + tb = g + across * .035 * s * lb * inner + self.tube('Thumb_' + side, [g + across * .02 * s * lb * inner, tb + u * .03 * s * lb, tb + u * .055 * s * lb - f * .01 * s], + [.014 * s * lb, .012 * s * lb, .01 * s * lb], kind, hand, 8, 8, pre=False) + if claws: + self.cone('ThumbClaw_' + side, tb + u * .055 * s * lb, tb + u * .09 * s * lb - f * .02 * s, .009 * s * lb, claws, hand, 6, pre=False) + + def fist_free(self, side, kind): + s, lb = self.s, self.sp['limb'] + g = self.G[side] + self.ell('Fist_' + side, g, (.045 * s * lb, .05 * s * lb, .055 * s * lb), kind, 'Hand.' + side, 14, 9, pre=False) + for k in range(4): + self.ell('Knuckle_%s%d' % (side, k), g + V(((k - 1.5) * .02 * s * lb, -.04 * s * lb, .012 * s)), (.013 * s * lb, .014 * s * lb, .014 * s * lb), + kind, 'Hand.' + side, 8, 6, pre=False) + + # ------------------------------------------------------------ head + def face(self, kind='skin', eyes='dark', ears='human', nose=1.0, jaw=1.0, brow=1.0): + hd, hc = self.sp['head'], self.head_c + self.ell('Skull', (0, .0, hc), (.088 * hd, .1 * hd, .108 * hd), kind, 'Head', 20, 14) + self.ell('Jaw', (0, -.042 * hd, hc - .058 * hd), (.064 * hd * jaw, .062 * hd, .05 * hd * jaw), kind, 'Head', 16, 10) + self.ell('Brow', (0, -.078 * hd, hc + .026 * hd), (.068 * hd, .028 * hd * brow, .017 * hd * brow), kind, 'Head', 14, 8) + self.tube('Nose', [(0, -.088 * hd, hc + .02 * hd), (0, -.106 * hd * nose, hc - .012 * hd), (0, -.1 * hd * nose, hc - .03 * hd)], + [.012 * hd * nose, .018 * hd * nose, .014 * hd * nose], kind, 'Head', 10, 8) + for side, sx in SIDES: + self.ell('Eye_' + side, (sx * .032 * hd, -.084 * hd, hc + .006 * hd), (.014 * hd, .008 * hd, .011 * hd), eyes, 'Head', 10, 7) + if ears == 'human': + self.ell('Ear_' + side, (sx * .088 * hd, .0, hc - .002), (.012 * hd, .024 * hd, .034 * hd), kind, 'Head', 10, 7) + elif ears == 'elf': + self.tube('Ear_' + side, [(sx * .082 * hd, .01, hc - .02 * hd), (sx * .13 * hd, .04 * hd, hc + .03 * hd), (sx * .19 * hd, .08 * hd, hc + .1 * hd)], + [.022 * hd, .016 * hd, .002], kind, 'Head', 8, 8, flatten=.4, ref=(0, 0, 1)) + elif ears == 'orc': + self.tube('Ear_' + side, [(sx * .085 * hd, .01, hc - .01 * hd), (sx * .13 * hd, .03 * hd, hc + .02 * hd), (sx * .17 * hd, .05 * hd, hc + .05 * hd)], + [.026 * hd, .02 * hd, .002], kind, 'Head', 8, 8, flatten=.4, ref=(0, 0, 1)) + elif ears == 'troll': + self.tube('Ear_' + side, [(sx * .085 * hd, .0, hc), (sx * .15 * hd, .03 * hd, hc - .02 * hd), (sx * .22 * hd, .06 * hd, hc - .07 * hd)], + [.032 * hd, .026 * hd, .003], kind, 'Head', 8, 8, flatten=.35, ref=(0, 0, 1)) + + # ------------------------------------------------------------ styles: grips, weapons + def bind_grips(self): + st, s, L = self.sp['style'], self.s, self.L + shR, shL = self.sh['R'], self.sh['L'] + self.G, self.u, self.f, self.pole = {}, {}, {}, {} + self.target = {'R': 'Weapon', 'L': 'Offhand'} + self.offhand_parent = 'Chest' + self.weapon_parent = 'Chest' + if st == 'blade_shield': + self.G['R'] = shR + L * V((.03, -.50, -.60)) + self.u['R'], self.f['R'], _ = frame((0, -.55, 1), (0, -1, 0)) + self.G['L'] = shL + L * V((-.02, -.54, -.50)) + self.u['L'], self.f['L'], _ = frame((0, 0, 1), (0, -1, 0)) + elif st == 'bow': + self.G['L'] = shL + L * V((-.12, -.84, -.12)) + self.u['L'], self.f['L'], _ = frame((.1, 0, 1), (0, -1, 0)) + self.G['R'] = self.G['L'] + V((0, .30 * L, .012 * s)) + self.u['R'], self.f['R'], _ = frame((0, -1, 0), (-1, 0, 0)) + self.weapon_parent = 'Offhand' + elif st == 'staff': + self.G['R'] = shR + L * V((-.14, -.30, -.60)) + self.u['R'], self.f['R'], _ = frame((-.08, -.12, 1), (0, -1, 0)) + self.G['L'] = shL + L * V((-.02, -.60, -.42)) + self.u['L'], self.f['L'], _ = frame((0, -1, .35), (0, 0, -1)) + elif st == 'club2h': + self.G['R'] = shR + L * V((.30, -.55, -.60)) + self.u['R'], self.f['R'], _ = frame((.25, -.5, 1), (0, -1, 0)) + self.G['L'] = self.G['R'] + self.u['R'] * .17 * s + self.u['L'], self.f['L'] = self.u['R'], self.f['R'] + self.target['L'] = 'Weapon' + elif st == 'axe': + self.G['R'] = shR + L * V((-.12, -.42, -.64)) + self.u['R'], self.f['R'], _ = frame((-.3, -.5, 1), (0, -1, 0)) + self.G['L'] = shL + L * V((-.06, -.40, -.62)) + self.u['L'], self.f['L'], _ = frame((0, 0, 1), (0, -1, 0)) + elif st == 'claws': + for side, sx in SIDES: + self.G[side] = self.sh[side] + L * V((sx * -.06, -.58, -.46)) + self.u[side], self.f[side], _ = frame((sx * -.1, -1, -.25), (0, 0, -1)) + for side, sx in SIDES: + self.pole[side] = self.sh[side] + L * V((sx * .8, .35, -.35)) + if st == 'bow': + self.pole['R'] = shR + L * V((-.8, .7, .2)) + self.pole['L'] = shL + L * V((.7, .1, -.7)) + + def weapon_frame(self, side): + g, u, f = self.G[side], self.u[side], self.f[side] + side_v = u.cross(f) + return lambda x, y, z: g + side_v * x * self.s + f * y * self.s + u * z * self.s + + def build_weapon(self): + sp, st, s = self.sp, self.sp['style'], self.s + if st == 'blade_shield': + F = self.weapon_frame('R') + u, f = self.u['R'], self.f['R'] + if sp['weapon'] == 'sword': + self.tube('Sword_Grip', [F(0, 0, -.07), F(0, 0, .07)], [.019 * s, .019 * s], 'leather', 'Weapon', 10, 4, groove=.12, lobes=8, pre=False) + self.ell('Sword_Pommel', F(0, 0, -.09), (.03 * s, .03 * s, .026 * s), 'trim', 'Weapon', 12, 8, pre=False, axes=[u.cross(f), f, u]) + self.plate('Sword_Guard', [F(-.12, 0, .075), F(-.02, 0, .09), F(.02, 0, .09), F(.12, 0, .075), F(.02, 0, .065), F(-.02, 0, .065)], + F(0, 0, .077), .026 * s, 'trim', 'Weapon', normal=f, pre=False) + blade = [F(-.028, 0, .09), F(.028, 0, .09), F(.024, 0, .62), F(0, 0, .72), F(-.024, 0, .62)] + self.plate('Sword_Blade', blade, F(0, 0, .38), .016 * s, 'metal', 'Weapon', normal=f, pre=False) + self.plate('Sword_Blade_Back', blade, F(0, 0, .38), .016 * s, 'metal', 'Weapon', normal=-f, pre=False) + self.tube('Sword_Fuller', [F(0, -.008, .12), F(0, -.008, .5)], [.004 * s, .003 * s], 'dark', 'Weapon', 6, 4, pre=False) + grip_r = .019 * s + else: + self.tube('Hammer_Haft', [F(0, 0, -.14), F(0, 0, .2), F(0, 0, .46)], [.022 * s, .021 * s, .024 * s], 'wood', 'Weapon', 10, 10, groove=.05, pre=False) + for k, z in enumerate((-.03, .1, .38)): + self.ring('Hammer_Band_%d' % k, F(0, 0, z), u, .026 * s, .018 * s, 'trim', 'Weapon', 12, pre=False) + side_v = u.cross(f) + self.tube('Hammer_Head', [F(-.13, 0, .5), F(0, 0, .5), F(.13, 0, .5)], [.058 * s, .066 * s, .058 * s], 'metal', 'Weapon', 8, 8, + pre=False, ref=u, smooth=False) + for q in (-1, 1): + self.ring('Hammer_Face_%d' % q, F(q * .14, 0, .5), side_v, .062 * s, .02 * s, 'trim', 'Weapon', 8, pre=False) + self.plate('Hammer_Rune', [F(-.03, -.07, .47), F(.03, -.07, .47), F(.03, -.07, .53), F(-.03, -.07, .53)], F(0, -.07, .5), .01 * s, + 'glow', 'Weapon', normal=f, pre=False) + grip_r = .022 * s + self.fist('R', 'leather' if sp['tag'] == 'Mensch' else 'skin', grip_r) + # Shield on the left arm. + G = self.weapon_frame('L') + fL = self.f['L'] + self.tube('Shield_Handle', [G(0, 0, -.06), G(0, 0, .06)], [.018 * s, .018 * s], 'leather', 'Offhand', 8, 3, pre=False) + self.fist('L', 'leather' if sp['tag'] == 'Mensch' else 'skin', .018 * s) + if sp['shield'] == 'kite': + outline = [(0, .28), (.19, .22), (.2, .03), (.11, -.2), (0, -.36), (-.11, -.2), (-.2, .03), (-.19, .22)] + rim = [G(x * 1.07, .05, z * 1.06) for x, z in outline] + face = [G(x, .068, z) for x, z in outline] + self.plate('Shield_Rim', rim, G(0, .05, -.02), .03 * s, 'metal', 'Offhand', normal=fL, pre=False) + self.plate('Shield_Face', face, G(0, .07, -.02), .03 * s, 'cloth', 'Offhand', normal=fL, pre=False) + self.plate('Shield_Cross_V', [G(-.028, .1, .22), G(.028, .1, .22), G(.028, .1, -.28), G(-.028, .1, -.28)], G(0, .1, -.03), + .012 * s, 'trim', 'Offhand', normal=fL, pre=False) + self.plate('Shield_Cross_H', [G(-.15, .1, .07), G(.15, .1, .07), G(.15, .1, .02), G(-.15, .1, .02)], G(0, .1, .045), + .012 * s, 'trim', 'Offhand', normal=fL, pre=False) + else: + outline = [(cos(2 * pi * k / 20) * .27, sin(2 * pi * k / 20) * .27) for k in range(20)] + self.plate('Shield_Face', [G(x, .06, z) for x, z in outline], G(0, .07, 0), .045 * s, 'wood', 'Offhand', normal=fL, pre=False) + self.ring('Shield_Rim', G(0, .055, 0), fL, .275 * s, .045 * s, 'metal', 'Offhand', 24, pre=False) + self.ell('Shield_Boss', G(0, .1, 0), (.07 * s, .07 * s, .04 * s), 'trim', 'Offhand', 14, 8, pre=False, + axes=[fL.cross(self.u['L']), self.u['L'], fL]) + for k in range(8): + a = 2 * pi * k / 8 + self.ell('Shield_Rivet_%d' % k, G(cos(a) * .22, .1, sin(a) * .22), (.014 * s, .014 * s, .012 * s), 'trim', 'Offhand', 8, 5, pre=False) + for k, (x0, z0, x1, z1) in enumerate([(-.1, .12, -.02, -.1), (.02, .12, .1, -.1), (-.06, .0, .06, .0)]): + self.tube('Shield_Rune_%d' % k, [G(x0, .1, z0), G(x1, .1, z1)], [.009 * s, .009 * s], 'glow', 'Offhand', 6, 3, pre=False) + elif st == 'bow': + B = self.weapon_frame('L') + fL = self.f['L'] + pts = [B(0, -.07, -.62), B(0, .02, -.5), B(0, -.02, -.25), B(0, 0, 0), B(0, -.02, .25), B(0, .02, .5), B(0, -.07, .62)] + self.tube('Bow_Limbs', pts, [.009 * s, .016 * s, .02 * s, .024 * s, .02 * s, .016 * s, .009 * s], 'wood', 'Offhand', 8, 34, flatten=.7, pre=False) + self.tube('Bow_Grip', [B(0, 0, -.07), B(0, 0, .07)], [.026 * s, .026 * s], 'leather', 'Offhand', 10, 4, groove=.1, lobes=8, pre=False) + for q in (-1, 1): + self.cone('Bow_Tip_%d' % q, B(0, -.07, q * .6), B(0, -.11, q * .66), .012 * s, 'trim', 'Offhand', 6, pre=False) + self.plate('Bow_Leaf_%d' % q, [B(-.035, .0, q * .16), B(0, .01, q * .1), B(.035, .0, q * .16), B(0, .01, q * .26)], + B(0, .02, q * .17), .008 * s, 'trim', 'Offhand', normal=fL, pre=False) + # String: tips on the bow, middle on the draw hand; the weights stretch it when drawn. + top, bottom, nock = B(0, -.08, .6), B(0, -.08, -.6), self.G['R'].copy() + vs, fs, ws = [], [], [] + n = 24 + for j in range(n + 1): + t = j / n + p = top.lerp(nock, t * 2) if t < .5 else nock.lerp(bottom, (t - .5) * 2) + pull = 1 - abs(t - .5) * 2 + for k in range(4): + a = 2 * pi * k / 4 + vs.append(p + V((cos(a), sin(a), 0)) * .003 * s) + ws.append({'Offhand': 1 - pull, 'Weapon': pull} if pull < 1 else {'Weapon': 1}) + if j: + for k in range(4): + a0, b0 = (j - 1) * 4 + k, (j - 1) * 4 + (k + 1) % 4 + fs.append((a0, b0, b0 + 4, a0 + 4)) + self.mesh('Bow_String', vs, fs, 'cloth2', 'Offhand', ws, True, None, False) + # Draw hand pinching the nock, and the arrow on its own bone (hidden after release). + self.fist('L', 'leather', .026 * s) + self.open_hand('R', 'leather', spread=.04) + aim = (self.G['L'] - self.G['R']).normalized() + tip = self.G['R'] + aim * .7 * s + self.tube('Arrow_Shaft', [self.G['R'] - aim * .02 * s, tip], [.006 * s, .006 * s], 'wood', 'Arrow', 6, 4, pre=False) + self.cone('Arrow_Head', tip, tip + aim * .07 * s, .016 * s, 'trim', 'Arrow', 4, pre=False) + for k in range(3): + a = 2 * pi * k / 3 + off = V((cos(a), 0, sin(a))) * .02 * s + self.plate('Arrow_Fletch_%d' % k, [self.G['R'] + aim * .03 * s, self.G['R'] + aim * .03 * s + off, + self.G['R'] + aim * .12 * s + off * .6, self.G['R'] + aim * .13 * s], + self.G['R'] + aim * .08 * s + off * .5, .003 * s, 'cloth2', 'Arrow', normal=V((sin(a), 0, -cos(a))), pre=False) + elif st == 'staff': + F = self.weapon_frame('R') + u = self.u['R'] + self.tube('Staff_Shaft', [F(0, 0, -.5), F(0, .01, .1), F(0, 0, .55)], [.012 * s, .014 * s, .016 * s], 'wood', 'Weapon', 8, 16, groove=.06, pre=False) + for k, z in enumerate((-.06, .08, .5)): + self.ring('Staff_Band_%d' % k, F(0, 0, z), u, .018 * s, .014 * s, 'trim', 'Weapon', 10, pre=False) + for q in (-1, 1): + self.tube('Staff_Crescent_%d' % q, [F(0, 0, .55), F(q * .08, 0, .62), F(q * .085, 0, .74), F(q * .04, 0, .8)], + [.012 * s, .011 * s, .008 * s, .003 * s], 'trim', 'Weapon', 8, 14, pre=False) + self.ell('Staff_Orb', F(0, 0, .69), (.05 * s, .05 * s, .05 * s), 'glow', 'Weapon', 14, 10, pre=False) + for q in (-1, 1): + self.tube('Staff_Ribbon_%d' % q, [F(q * .02, 0, .5), F(q * .05, .02, .38), F(q * .04, .04, .26)], [.008 * s, .007 * s, .003 * s], + 'cloth', 'Weapon', 6, 8, flatten=.25, pre=False) + self.fist('R', 'skin', .014 * s) + self.open_hand('L', 'skin', spread=.16) + elif st == 'club2h': + F = self.weapon_frame('R') + u, f = self.u['R'], self.f['R'] + side_v = u.cross(f) + self.tube('Club_Haft', [F(0, 0, -.16), F(0, 0, .25), F(0, 0, .5)], [.034 * s, .036 * s, .05 * s], 'wood', 'Weapon', 10, 12, groove=.08, pre=False) + self.ell('Club_Head', F(0, 0, .66), (.12 * s, .12 * s, .2 * s), 'stone', 'Weapon', 14, 10, pre=False, axes=[side_v, f, u], + bumps=.12, smooth=False) + for k in range(7): + a = 2 * pi * k / 7 + radial = side_v * cos(a) + f * sin(a) + base = F(0, 0, .62 + .06 * (k % 2)) + radial * .1 * s + self.shard('Club_Spike_%d' % k, [base, base + radial * .05 * s, base + radial * .09 * s + u * .02 * s], .03 * s, 'stone', 'Weapon', pre=False) + for k, z in enumerate((.02, .44)): + self.ring('Club_Wrap_%d' % k, F(0, 0, z), u, .042 * s, .05 * s, 'leather', 'Weapon', 12, pre=False) + self.fist('R', 'skin', .036 * s) + self.fist('L', 'skin', .036 * s) + elif st == 'axe': + F = self.weapon_frame('R') + u, f = self.u['R'], self.f['R'] + self.tube('Axe_Haft', [F(0, 0, -.2), F(0, 0, .2), F(0, 0, .58)], [.022 * s, .022 * s, .025 * s], 'wood', 'Weapon', 10, 10, groove=.05, pre=False) + for k, z in enumerate((-.06, .08, .22)): + self.ring('Axe_Wrap_%d' % k, F(0, 0, z), u, .026 * s, .03 * s, 'leather', 'Weapon', 10, pre=False) + blade = [F(0, .02, .6), F(0, .16, .72), F(0, .24, .64), F(0, .25, .46), F(0, .17, .36), F(0, .02, .44)] + self.plate('Axe_Blade', blade, F(0, .13, .54), .018 * s, 'metal', 'Weapon', normal=u.cross(f), pre=False) + self.plate('Axe_Blade_Back', blade, F(0, .13, .54), .018 * s, 'metal', 'Weapon', normal=-u.cross(f), pre=False) + self.tube('Axe_Edge', [F(0, .16, .72), F(0, .235, .66), F(0, .25, .55), F(0, .24, .46), F(0, .17, .36)], [.006 * s] * 5, 'bone', 'Weapon', + 6, 14, pre=False) + self.shard('Axe_Spike', [F(0, -.02, .53), F(0, -.1, .52), F(0, -.18, .55)], .03 * s, 'metal', 'Weapon', pre=False) + self.ring('Axe_Collar', F(0, 0, .53), u, .03 * s, .1 * s, 'metal', 'Weapon', 10, pre=False) + self.fist('R', 'skin', .022 * s) + self.fist_free('L', 'skin') + elif st == 'claws': + for side, _ in SIDES: + self.open_hand(side, 'skin', claws='bone', spread=.18) + + # ------------------------------------------------------------ shared gear pieces + def build(self): + getattr(self, 'gear_' + self.key[2:])() + self.build_weapon() + + def armour_loft(self, name, z0, z1, grow, kind, bone_fn=None, sides=24, steps=12, bulge=.07): + secs = [(z, rx + grow, ry + grow) for z, rx, ry in self.torso_sections if z0 - .001 <= z <= z1 + .001] + return self.loft(name, secs, kind, bone_fn or self.torso_weights, sides, steps, front_bulge=bulge) + + def section_at(self, z): + secs = self.torso_sections + for a, b in zip(secs, secs[1:]): + if a[0] <= z <= b[0]: + t = (z - a[0]) / (b[0] - a[0]) + return a[1] + (b[1] - a[1]) * t, a[2] + (b[2] - a[2]) * t + return secs[0][1], secs[0][2] + + def pauldron(self, side, sx, kind, trim, layers=3, size=1.0, spikes=0, spike_kind='bone'): + s = self.s + a = self.sh[side] + out = V((sx, 0, 0)) + for k in range(layers): + c = a + out * (.03 + .018 * k) * s * size + V((0, 0, (.035 - .035 * k) * s * size)) + self.ell('Pauldron_%s%d' % (side, k), c, ((.085 - .008 * k) * s * size, (.09 - .006 * k) * s * size, (.058 - .006 * k) * s * size), + kind, 'UpperArm.' + side, 16, 9, smooth=False, pre=False) + self.ring('Pauldron_Trim_' + side, a + out * .03 * s * size, (sx * .4, 0, 1), .087 * s * size, .014 * s, trim, 'UpperArm.' + side, 18, pre=False) + for k in range(spikes): + base = a + out * (.05 * s * size) + V((0, (k - (spikes - 1) / 2) * .045 * s * size, .07 * s * size)) + self.shard('Pauldron_Spike_%s%d' % (side, k), [base, base + V((sx * .05, 0, .09)) * s * size, base + V((sx * .08, 0, .16)) * s * size], + .022 * s * size, spike_kind, 'UpperArm.' + side, pre=False) + + def bracer(self, side, kind, trim=None, frac=(.55, .9), grow=.012): + a, b = self.elbow[side], self.W[side] + p0, p1 = a.lerp(b, frac[0]), a.lerp(b, frac[1]) + lb, s = self.sp['limb'], self.s + self.tube('Bracer_' + side, [p0, p0.lerp(p1, .5), p1], [(.046 + grow) * lb * s, (.047 + grow) * lb * s, (.037 + grow) * lb * s], kind, + 'Forearm.' + side, 14, 6, smooth=False, pre=False) + if trim: + self.ring('Bracer_Trim_' + side, p1, b - a, (.04 + grow) * lb * s, .012 * s, trim, 'Forearm.' + side, 14, pre=False) + + def belt(self, kind, buckle, z=None, grow=.012): + z = z if z is not None else self.waist - .02 + rx, ry = self.section_at(z) + self.loft('Belt', [(z - .025, rx + grow, ry + grow), (z + .025, rx + grow, ry + grow)], kind, self.torso_weights, 24, 2) + y = -ry - grow - .004 + self.plate('Buckle', [(-.03, y, z + .028), (.03, y, z + .028), (.03, y, z - .028), (-.03, y, z - .028)], (0, y, z), .012, buckle, 'Pelvis') + return rx + grow, ry + grow + + def hanging(self, name, outline, center, depth, kind, normal, top, bottom, share=.5): + return self.plate(name, outline, center, depth, kind, 'Pelvis', normal=normal, + weights=lambda v: self.skirt_weights(top, bottom, share)(v[2], v[0])) + + # ------------------------------------------------------------ people + def gear_mensch(self): + self.build_body('cloth', 'metal', 'metal', 'metal', chest=1.04) + H, w = self.hip, self.sp['w'] + self.armour_loft('Breastplate', H + .13, H + .55, .016, 'metal', bulge=.1) + rx, ry = self.section_at(H + .13) + self.loft('Breastplate_Rim', [(H + .12, rx + .022, ry + .022), (H + .145, rx + .022, ry + .022)], 'trim', self.torso_weights, 24, 2) + self.ring('Gorget', (0, 0, self.neck - .01), (0, 0, 1), .082, .05, 'metal', 'Neck', 18) + self.ring('Gorget_Trim', (0, 0, self.neck + .018), (0, 0, 1), .078, .012, 'trim', 'Neck', 18) + # Chest emblem: a gold sun on the breastplate. + rx, ry = self.section_at(H + .40) + ey = -(ry * 1.1 + .016) - .004 + self.ell('Emblem', (0, ey, H + .40), (.035, .012, .035), 'trim', 'Chest', 12, 6, smooth=False) + for k in range(8): + a = 2 * pi * k / 8 + self.shard('Emblem_Ray_%d' % k, [(cos(a) * .035, ey + .002, H + .40 + sin(a) * .035), (cos(a) * .07, ey + .008, H + .40 + sin(a) * .07)], .012, 'trim', 'Chest') + # Faulds and tabard. + for k, z in enumerate((H + .08, H + .02, H - .04)): + rx, ry = self.section_at(max(H - .07, z)) + g = .02 + .008 * k + self.loft('Fauld_%d' % k, [(z - .035, rx + g + .006, ry + g + .006), (z + .03, rx + g, ry + g)], 'metal', self.skirt_weights(H + .1, H - .08, .2), 24, 2, + smooth=False) + rx, ry = self.belt('leather', 'trim', H + .115) + for q, y in ((-1, -ry - .03), (1, ry + .03)): + out = [(-.12, y, H + .1), (.12, y, H + .1), (.11, y, H - .3), (0, y, H - .36), (-.11, y, H - .3)] + self.hanging('Tabard_%d' % q, out, (0, y, H - .1), .014, 'cloth', (0, q, 0), H + .1, H - .36) + trim = [(-.115, y + q * .005, H - .22), (.115, y + q * .005, H - .22), (.105, y + q * .005, H - .29), (0, y + q * .005, H - .345), + (-.105, y + q * .005, H - .29)] + self.hanging('Tabard_Trim_%d' % q, trim, (0, y + q * .005, H - .28), .008, 'trim', (0, q, 0), H + .1, H - .36) + for side, sx in SIDES: + self.pauldron(side, sx, 'metal', 'trim', 3) + self.ell('Couter_' + side, self.elbow[side], (.052 * self.s, .052 * self.s, .052 * self.s), 'metal', 'Forearm.' + side, 14, 9, pre=False, smooth=False) + self.bracer(side, 'metal', 'trim', (.5, .92), .006) + hp, kn, an, toe = self.leg[side] + self.ell('Poleyn_' + side, kn + V((0, -.03, 0)), (.058, .045, .058), 'metal', 'Shin.' + side, 14, 9, smooth=False, + weights=lambda v, side=side: blend('Thigh.' + side, 'Shin.' + side, .6)) + self.ring('Poleyn_Trim_' + side, kn + V((0, -.02, .0)), (0, 1, .2), .052, .012, 'trim', 'Shin.' + side, 14) + # Great helm with visor slit, gold cross and a white plume. + hd, hc = self.sp['head'], self.head_c + self.ell('Helm', (0, -.004, hc + .004), (.1 * hd, .112 * hd, .122 * hd), 'metal', 'Head', 20, 14) + self.ring('Helm_Crown', (0, -.004, hc + .06 * hd), (0, 0, 1), .096 * hd, .018, 'trim', 'Head', 20) + self.plate('Visor_Slit', [(-.07, -.108 * hd, hc + .018), (.07, -.108 * hd, hc + .018), (.07, -.104 * hd, hc + .002), (-.07, -.104 * hd, hc + .002)], + (0, -.11 * hd, hc + .01), .008, 'dark', 'Head') + self.plate('Helm_Cross_V', [(-.012, -.115 * hd, hc + .1), (.012, -.115 * hd, hc + .1), (.012, -.112 * hd, hc - .08), (-.012, -.112 * hd, hc - .08)], + (0, -.118 * hd, hc + .01), .008, 'trim', 'Head') + for k in range(4): + self.ell('Breath_%d' % k, ((k - 1.5) * .018, -.106 * hd, hc - .045), (.005, .004, .005), 'dark', 'Head', 6, 4) + self.tube('Plume', [(0, .0, hc + .12), (0, .06, hc + .2), (0, .16, hc + .2), (0, .24, hc + .08), (0, .26, hc - .04)], + [.022, .04, .045, .03, .006], 'cloth2', 'Head', 12, 20, groove=.25, lobes=7) + + def gear_elf(self): + self.build_body('leather', 'cloth', 'cloth2', 'leather', chest=.96) + H, hd, hc = self.hip, self.sp['head'], self.head_c + self.armour_loft('Jerkin', H + .1, H + .5, .008, 'cloth', bulge=.08) + # Leaf plates over chest and shoulders. + for k, (x, z, sc) in enumerate([(-.07, H + .42, 1), (.07, H + .42, 1), (-.05, H + .3, .8), (.05, H + .3, .8), (0, H + .2, .7)]): + rx, ry = self.section_at(z) + y = -ry * 1.05 - .012 + self.plate('Leaf_Plate_%d' % k, [(x, y, z + .06 * sc), (x + .045 * sc, y, z + .01 * sc), (x, y, z - .06 * sc), (x - .045 * sc, y, z + .01 * sc)], + (x, y - .01, z), .014, 'cloth2', 'Chest' if z > self.chest else 'Spine') + self.tube('Leaf_Vein_%d' % k, [(x, y - .012, z + .05 * sc), (x, y - .012, z - .05 * sc)], [.004, .003], 'trim', 'Chest' if z > self.chest else 'Spine', 6, 3) + rx, ry = self.belt('leather', 'trim', H + .1) + for q, y in ((-1, -ry - .02), (1, ry + .02)): + for k, x in enumerate((-.08, .0, .08)): + out = [(x - .045, y, H + .08), (x + .045, y, H + .08), (x + .035, y, H - .2), (x, y, H - .25), (x - .035, y, H - .2)] + self.hanging('Tunic_%d_%d' % (q, k), out, (x, y, H - .06), .01, 'cloth', (0, q, 0), H + .08, H - .25, .55) + lb = self.sp['limb'] + for side, sx in SIDES: + hp, kn, an, toe = self.leg[side] + self.tube('Boot_Shaft_' + side, [an + V((0, 0, .02)), an.lerp(kn, .5), kn + V((0, -.01, -.03))], [.04 * lb + .012, .062 * lb + .012, .058 * lb + .012], + 'leather', 'Shin.' + side, 14, 10) + self.plate('Boot_Cuff_' + side, [kn + V((-.05, -.045, .0)), kn + V((.05, -.045, 0)), kn + V((0, -.06, .06))], kn + V((0, -.06, .01)), .012, + 'trim', 'Shin.' + side) + self.bracer(side, 'leather', 'trim', (.45, .92), .004) + self.pauldron(side, sx, 'leather', 'trim', 2, .8) + # Cloak on its own bone, hood gathered at the neck. + cloak = [(0, .12, self.shoulder), (0, .18, self.waist), (0, .23, self.knee + .05)] + self.tube('Cloak', cloak, [.2, .23, .27], 'cloth2', 'Chest', 18, 16, flatten=.18, ref=(1, 0, 0), + weight_fn=lambda t: blend('Chest', 'Cloak', t * 1.2)) + self.tube('Hood', [(-.11, .02, self.neck - .02), (0, .1, self.neck + .02), (.11, .02, self.neck - .02)], [.05, .065, .05], 'cloth2', 'Chest', 12, 12) + self.ring('Cloak_Clasp', (0, -.1, self.neck - .03), (0, 1, 0), .02, .01, 'trim', 'Chest', 10) + self.face('skin', 'dark', 'elf', nose=.9, jaw=.88, brow=.8) + self.ell('Hair_Cap', (0, .03 * hd, hc + .035 * hd), (.093 * hd, .092 * hd, .09 * hd), 'hair', 'Head', 18, 12) + for side, sx in SIDES: + self.tube('Hair_Strand_' + side, [(sx * .05 * hd, -.07 * hd, hc + .08 * hd), (sx * .085 * hd, -.05 * hd, hc + .02 * hd), (sx * .09 * hd, -.02 * hd, hc - .08 * hd)], + [.018 * hd, .016 * hd, .006 * hd], 'hair', 'Head', 10, 10, groove=.2, lobes=4) + self.tube('Hair_Tail', [(0, .08 * hd, hc + .02), (0, .12 * hd, hc - .1), (0, .14, self.shoulder - .05), (0, .15, self.chest - .02)], + [.04, .036, .028, .012], 'hair', 'Head', 12, 16, groove=.2, lobes=6, + weight_fn=lambda t: {'Head': 1} if t < .3 else blend('Head', 'Chest', (t - .3) / .4)) + self.ring('Circlet', (0, -.005, hc + .05 * hd), (0, .15, 1), .094 * hd, .01, 'trim', 'Head', 20) + self.ell('Circlet_Gem', (0, -.1 * hd, hc + .055 * hd), (.012, .008, .014), 'glow', 'Head', 10, 6) + # Quiver with arrows across the back. + q0, q1 = V((.1, .17, self.shoulder + .06)), V((-.08, .16, self.waist + .02)) + up = (q0 - q1).normalized() + self.tube('Quiver', [q0, q1], [.045, .04], 'leather', 'Chest', 12, 4) + self.ring('Quiver_Trim', q0.lerp(q1, .08), up, .048, .02, 'trim', 'Chest', 12) + for k in range(4): + off = V(((k - 1.5) * .018, .0, 0)) + self.tube('Quiver_Arrow_%d' % k, [q0 + off, q0 + off + up * .1], [.005, .005], 'wood', 'Chest', 6, 3) + self.plate('Quiver_Fletch_%d' % k, [q0 + off + up * .05, q0 + off + up * .1 + V((0, -.012, 0)), q0 + off + up * .12, q0 + off + up * .07 + V((0, .012, 0))], + q0 + off + up * .085, .004, 'cloth', 'Chest', normal=(1, 0, 0)) + self.extra_bones.append(('Cloak', V((0, .12, self.shoulder)), V((0, .2, self.waist)), 'Chest')) + + def gear_zwerg(self): + self.build_body('cloth', 'cloth', 'cloth', 'leather', chest=1.05, belly=1.12) + H, hd, hc = self.hip, self.sp['head'], self.head_c + self.armour_loft('Cuirass', H + .1, H + .55, .02, 'metal', bulge=.12) + for k in range(3): + z = H + .16 + .1 * k + rx, ry = self.section_at(z) + self.loft('Cuirass_Band_%d' % k, [(z - .012, rx + .03, ry * 1.05 + .03), (z + .012, rx + .03, ry * 1.05 + .03)], 'trim', self.torso_weights, 24, 2) + rx, ry = self.belt('leather', 'trim', H + .08, .03) + self.plate('Belt_Plate', [(-.06, -ry - .012, H + .12), (.06, -ry - .012, H + .12), (.07, -ry - .012, H + .04), (-.07, -ry - .012, H + .04)], + (0, -ry - .03, H + .08), .025, 'trim', 'Pelvis') + w, d = self.sp['w'], self.sp['d'] + self.loft('Mail_Skirt', [(H + .06, .17 * w + .02, .13 * d + .03), (H - .1, .19 * w + .03, .14 * d + .03), (H - .24, .2 * w + .035, .15 * d + .035)], 'metal', + self.skirt_weights(H + .06, H - .24, .5), 24, 8, scallop=.04) + for side, sx in SIDES: + self.pauldron(side, sx, 'metal', 'trim', 3, 1.25) + self.bracer(side, 'metal', 'trim', (.35, .92), .012) + hp, kn, an, toe = self.leg[side] + self.plate('Greave_' + side, [kn + V((-.05, -.05, -.02)), kn + V((.05, -.05, -.02)), an + V((.045, -.05, .05)), an + V((-.045, -.05, .05))], + kn.lerp(an, .5) + V((0, -.08, 0)), .03, 'metal', 'Shin.' + side) + self.ell('Knee_' + side, kn + V((0, -.045, 0)), (.058, .04, .055), 'trim', 'Shin.' + side, 12, 8, smooth=False) + self.face('skin', 'dark', 'human', nose=1.35, jaw=1.05, brow=1.4) + # Beard, braids and moustache. + self.ell('Beard', (0, -.062 * hd, hc - .09 * hd), (.09 * hd, .07 * hd, .1 * hd), 'hair', 'Head', 18, 12, bumps=.06) + for side, sx in SIDES: + self.tube('Moustache_' + side, [(0, -.1 * hd, hc - .032 * hd), (sx * .04 * hd, -.1 * hd, hc - .045 * hd), (sx * .07 * hd, -.085 * hd, hc - .085 * hd)], + [.016 * hd, .018 * hd, .008 * hd], 'hair', 'Head', 10, 10) + top = V((sx * .035 * hd, -.09 * hd, hc - .14 * hd)) + self.tube('Braid_' + side, [top, top + V((0, -.02, -.07)), top + V((sx * .005, -.022, -.15))], [.022, .02, .012], 'hair', 'Head', 10, 12, + groove=.25, lobes=3, weight_fn=lambda t: blend('Head', 'Chest', t)) + self.ring('Braid_Bead_' + side, top + V((sx * .004, -.022, -.12)), (0, 0, 1), .02, .025, 'trim', 'Chest', 10) + # Dome helmet with nose guard and short horns. + self.ell('Helm', (0, .004, hc + .03 * hd), (.1 * hd, .11 * hd, .094 * hd), 'metal', 'Head', 20, 12) + self.ring('Helm_Rim', (0, .0, hc + .012 * hd), (0, 0, 1), .1 * hd, .03, 'trim', 'Head', 20) + self.plate('Nose_Guard', [(-.014, -.112 * hd, hc + .05 * hd), (.014, -.112 * hd, hc + .05 * hd), (.01, -.118 * hd, hc - .03 * hd), (-.01, -.118 * hd, hc - .03 * hd)], + (0, -.122 * hd, hc + .01), .01, 'metal', 'Head') + for side, sx in SIDES: + self.tube('Horn_' + side, [(sx * .08 * hd, 0, hc + .05 * hd), (sx * .14 * hd, -.01, hc + .08 * hd), (sx * .17 * hd, -.03, hc + .15 * hd)], + [.025 * hd, .018 * hd, .002], 'bone', 'Head', 10, 10) + + def gear_troll(self): + self.build_body('skin', 'skin', 'skin', None, chest=1.08, belly=1.2) + H, hd, hc = self.hip, self.sp['head'], self.head_c + rx, ry = self.belt('leather', 'bone', H + .06, .02) + for q, y in ((-1, -ry - .02), (1, ry + .02)): + out = [(-.14, y, H + .05), (.14, y, H + .05), (.12, y, H - .2), (.02, y, H - .3), (-.1, y, H - .24)] + self.hanging('Loincloth_%d' % q, out, (0, y, H - .08), .016, 'leather', (0, q, 0), H + .05, H - .3) + # Stone growths along shoulders, back and forearms. + growths = [((.12, .08, self.shoulder + .02), (.2, .14, self.shoulder + .14)), ((-.12, .08, self.shoulder + .02), (-.22, .12, self.shoulder + .12)), + ((0, .13, self.chest + .05), (0, .26, self.chest + .12)), ((.06, .13, self.chest - .08), (.1, .25, self.chest - .02)), + ((-.07, .12, self.chest - .06), (-.12, .23, self.chest + .02)), ((0, .13, self.waist + .05), (0, .22, self.waist + .1))] + for k, (a, b) in enumerate(growths): + self.shard('Stone_Growth_%d' % k, [a, V(a).lerp(V(b), .55), b], .05, 'stone', 'Chest' if a[2] > self.chest - .1 else 'Spine', sides=5) + for side, sx in SIDES: + a, b = self.elbow[side], self.W[side] + for k in range(2): + p = a.lerp(b, .3 + .3 * k) + out = (p - self.sh[side]).cross(V((0, 0, 1))).normalized() * (-sx) + self.shard('Arm_Stone_%s%d' % (side, k), [p, p + out * .06 * self.s + V((0, .04, .04)) * self.s], .035 * self.s, 'stone', 'Forearm.' + side, + pre=False, sides=5) + hp, kn, an, toe = self.leg[side] + for k, dx in enumerate((-.03, 0, .03)): + self.ell('Toe_%s%d' % (side, k), toe + V((dx * 1.3, -.01, .01)), (.022, .03, .02), 'skin', 'Foot.' + side, 10, 7) + self.tube('Foot_' + side, [an + V((0, .04, -.05)), an.lerp(toe, .5), toe], [.06, .07, .05], 'skin', 'Foot.' + side, 12, 10, flatten=.65) + self.ring('Ankle_Wrap_' + side, an + V((0, 0, .04)), (0, 0, 1), .06, .04, 'leather', 'Shin.' + side, 12) + self.face('skin', 'eye', 'troll', nose=1.5, jaw=1.35, brow=1.7) + for side, sx in SIDES: + self.cone('Tusk_' + side, (sx * .04 * hd, -.09 * hd, hc - .075 * hd), (sx * .055 * hd, -.12 * hd, hc - .0 * hd), .014 * hd, 'bone', 'Head', 8) + self.tube('Hair_Tuft', [(0, -.02, hc + .1 * hd), (0, .05, hc + .13 * hd), (0, .12, hc + .08 * hd)], [.02, .03, .008], 'hair', 'Head', 8, 8, groove=.3) + self.ring('Necklace', (0, 0, self.neck - .03), (0, -.3, 1), .1, .015, 'leather', 'Chest', 14) + for k in range(5): + a = pi * (.25 + .5 * k / 4) + self.cone('Necklace_Tooth_%d' % k, (cos(a) * .1, -sin(a) * .1, self.neck - .05), (cos(a) * .1, -sin(a) * .11, self.neck - .1), .012, 'bone', 'Chest', 6) + + def gear_fee(self): + self.build_body('cloth2', 'skin', 'skin', 'cloth2', chest=.92) + H, hd, hc = self.hip, self.sp['head'], self.head_c + w, d = self.sp['w'], self.sp['d'] + self.loft('Dress', [(self.chest + .06, .18 * w, .115 * d), (self.waist, .15 * w, .11 * d), (H, .19 * w, .15 * d), (H - .18, .25 * w, .21 * d), + (H - .32, .3 * w, .26 * d)], 'cloth', self.skirt_weights(H + .02, H - .32, .45), 28, 16, scallop=.08) + self.loft('Bodice_Trim', [(self.chest + .05, .182 * w + .006, .118 * d + .006), (self.chest + .07, .18 * w + .006, .116 * d + .006)], 'trim', self.torso_weights, 24, 2) + self.loft('Sash', [(self.waist - .02, .15 * w + .01, .11 * d + .01), (self.waist + .02, .15 * w + .01, .11 * d + .01)], 'trim', self.torso_weights, 24, 2) + for side, sx in SIDES: + self.ring('Bracelet_' + side, self.elbow[side].lerp(self.W[side], .85), self.W[side] - self.elbow[side], .036 * self.s * .74 + .004, .01, 'trim', + 'Forearm.' + side, 12, pre=False) + self.ell('Puff_Sleeve_' + side, self.sh[side] + V((sx * .02, 0, -.01)) * self.s, (.07 * self.s, .07 * self.s, .06 * self.s), 'cloth', 'UpperArm.' + side, 14, 9, + pre=False, bumps=.08) + self.face('skin', 'dark', 'elf', nose=.7, jaw=.85, brow=.6) + self.ell('Hair', (0, .018, hc + .02 * hd), (.098 * hd, .108 * hd, .104 * hd), 'hair', 'Head', 18, 12, bumps=.05) + self.ell('Hair_Bun', (0, .06 * hd, hc + .1 * hd), (.05 * hd, .05 * hd, .045 * hd), 'hair', 'Head', 14, 9, bumps=.1) + for side, sx in SIDES: + self.tube('Hair_Lock_' + side, [(sx * .075 * hd, -.05 * hd, hc + .0), (sx * .085 * hd, -.05 * hd, hc - .08 * hd), (sx * .08 * hd, -.03 * hd, hc - .15 * hd)], + [.02, .018, .006], 'hair', 'Head', 10, 10, groove=.2, lobes=4) + for k in range(6): + a = pi * (-.1 + 1.2 * k / 5) + c = (cos(a) * .09 * hd, -sin(a) * .09 * hd, hc + .07 * hd) + self.ell('Flower_%d' % k, c, (.02, .02, .012), 'cloth2', 'Head', 8, 5, bumps=.3) + self.ell('Flower_Heart_%d' % k, (c[0], c[1] - .005, c[2] + .008), (.007, .007, .006), 'glow', 'Head', 6, 4) + # Two pairs of luminous wings with a vein frame. + for side, sx in SIDES: + root = V((sx * .04, .1, self.chest + .04)) + for k, (span, rise, width) in enumerate(((.46, .2, .2), (.32, -.14, .13))): + tip = root + V((sx * span, .08, rise)) + mid = root.lerp(tip, .55) + perp = V((0, 0, 1)) if k == 0 else V((0, 0, -1)) + outline = [root, mid + perp * width * .9 + V((0, .02, 0)), tip + perp * width * .3, tip, tip - perp * width * .25, mid - perp * width * .35] + self.plate('Wing_%s%d' % (side, k), outline, mid + V((0, .012, 0)), .01, 'glow', 'Wing.' + side, normal=(0, 1, 0)) + self.tube('Wing_Vein_%s%d' % (side, k), [root, mid + perp * width * .3, tip], [.008, .006, .002], 'trim', 'Wing.' + side, 6, 10) + self.extra_bones.append(('Wing.' + side, V((sx * .04, .1, self.chest + .04)), V((sx * .3, .16, self.chest + .1)), 'Chest')) + + def gear_ork(self): + self.build_body('skin', 'skin', 'leather', 'leather', chest=1.1, belly=1.05) + H, hd, hc = self.hip, self.sp['head'], self.head_c + rx, ry = self.belt('leather', 'metal', H + .07, .02) + for k in range(8): + a = 2 * pi * (k + .5) / 8 + x, y = cos(a) * (rx + .012), sin(a) * (ry + .012) + n = V((cos(a), sin(a), 0)) + t = V((-sin(a), cos(a), 0)) * .032 + out = [V((x, y, H + .06)) + t, V((x, y, H + .06)) - t, V((x, y, H - .22)) - t * .8, V((x, y, H - .26)), V((x, y, H - .22)) + t * .8] + self.hanging('Kilt_%d' % k, out, V((x, y, H - .08)), .014, 'leather' if k % 2 else 'trim', n, H + .06, H - .26) + # Harness straps crossing the chest. + for q in (-1, 1): + pts = [] + for z, x in ((self.shoulder, q * .15), (self.chest, q * .02), (self.waist + .03, -q * .12)): + rx2, ry2 = self.section_at(z) + pts.append((x, -ry2 * 1.08 - .008, z)) + self.tube('Strap_%d' % q, pts, [.018, .018, .018], 'leather', 'Chest', 8, 12, flatten=.4, + weight_fn=lambda t: self.torso_weights(self.shoulder - t * (self.shoulder - self.waist))) + rx2, ry2 = self.section_at(self.chest) + self.ring('Strap_Ring', (0, -ry2 * 1.08 - .012, self.chest), (0, 1, 0), .03, .012, 'metal', 'Chest', 12) + self.pauldron('L', 1, 'metal', 'trim', 3, 1.3, spikes=3, spike_kind='metal') + self.ell('Fur_Mantle', self.sh['R'] + V((.0, .03, .03)) * self.s, (.11 * self.s, .1 * self.s, .07 * self.s), 'fur', 'UpperArm.R', 16, 9, pre=False, bumps=.2) + # Skull trophy on the belt. + sk = V((-.1, -ry - .03, H + .02)) + self.ell('Trophy_Skull', sk, (.04, .045, .045), 'bone', 'Pelvis', 12, 8) + for sx in (-1, 1): + self.ell('Trophy_Eye_%d' % sx, sk + V((sx * .016, -.036, .01)), (.011, .006, .01), 'dark', 'Pelvis', 8, 5) + for side, sx in SIDES: + self.bracer(side, 'metal', 'leather', (.45, .9), .01) + p = self.elbow[side].lerp(self.W[side], .65) + out = (p - self.sh[side]).cross(V((0, 0, 1))).normalized() * (-sx) + for k in range(2): + q = self.elbow[side].lerp(self.W[side], .55 + .2 * k) + self.shard('Bracer_Spike_%s%d' % (side, k), [q + out * .05 * self.s, q + out * .1 * self.s], .015 * self.s, 'metal', 'Forearm.' + side, pre=False) + hp, kn, an, toe = self.leg[side] + self.ell('Toe_Cap_' + side, toe + V((0, .03, .015)), (.05, .06, .035), 'metal', 'Foot.' + side, 12, 8, smooth=False) + self.face('skin', 'dark', 'orc', nose=1.2, jaw=1.3, brow=1.5) + for side, sx in SIDES: + self.cone('Tusk_' + side, (sx * .035 * hd, -.09 * hd, hc - .07 * hd), (sx * .045 * hd, -.11 * hd, hc - .02 * hd), .012 * hd, 'bone', 'Head', 8) + self.plate('War_Paint_' + side, [(sx * .02, -.094 * hd, hc + .03), (sx * .06, -.08 * hd, hc + .03), (sx * .055, -.082 * hd, hc - .005), (sx * .02, -.096 * hd, hc)], + (sx * .04, -.095 * hd, hc + .015), .004, 'trim', 'Head') + for k in range(6): + z = hc + .1 * hd - abs(k - 2.5) * .006 + self.shard('Mohawk_%d' % k, [(0, -.06 * hd + k * .03 * hd, z), (0, -.06 * hd + k * .03 * hd + .02, z + .08 - abs(k - 2.5) * .01)], .024, 'hair', 'Head', sides=4) + + def gear_biest(self): + self.build_body('fur', 'fur', 'fur', None, chest=1.08) + H, hd, hc = self.hip, self.sp['head'], self.head_c + self.armour_loft('Chest_Fur', self.chest - .1, self.chest + .12, .004, 'fur2', bulge=.12) + rx, ry = self.belt('leather', 'metal', H + .07, .016) + for q, y in ((-1, -ry - .02), (1, ry + .02)): + out = [(-.1, y, H + .06), (.1, y, H + .06), (.08, y, H - .18), (0, y, H - .24), (-.08, y, H - .18)] + self.hanging('Loincloth_%d' % q, out, (0, y, H - .06), .014, 'cloth', (0, q, 0), H + .06, H - .24) + # Mane tufts around neck and shoulders. + for k in range(14): + a = 2 * pi * k / 14 + base = V((cos(a) * .12, sin(a) * .1 + .02, self.neck - .02)) + tip = base + V((cos(a) * .07, sin(a) * .07 + .03, -.05 + .03 * sin(3 * a))) + self.shard('Mane_%d' % k, [base, base.lerp(tip, .5) + V((0, 0, .02)), tip], .04, 'fur2' if k % 3 == 0 else 'fur', 'Chest', sides=5) + # Bone necklace. + self.ring('Necklace', (0, -.01, self.neck - .06), (0, -.4, 1), .12, .01, 'leather', 'Chest', 14) + for k in range(5): + a = pi * (.2 + .6 * k / 4) + self.cone('Necklace_Fang_%d' % k, (cos(a) * .12, -sin(a) * .115, self.neck - .075), (cos(a) * .12, -sin(a) * .125, self.neck - .13), .01, 'bone', 'Chest', 6) + for side, sx in SIDES: + self.bracer(side, 'leather', 'metal', (.5, .9), .012) + hp, kn, an, toe = self.leg[side] + self.tube('Paw_' + side, [an + V((0, .02, -.02)), an.lerp(toe, .6), toe], [.045, .052, .04], 'fur', 'Foot.' + side, 12, 10, flatten=.7) + for k, dx in enumerate((-.025, 0, .025)): + self.cone('Foot_Claw_%s%d' % (side, k), toe + V((dx, -.02, .0)), toe + V((dx * 1.3, -.07, -.02)), .01, 'bone', 'Foot.' + side, 6) + # Beast head: long snout, ram horns, animal ears, amber eyes. + self.ell('Skull', (0, .0, hc), (.085 * hd, .1 * hd, .1 * hd), 'fur', 'Head', 20, 14) + self.ell('Snout', (0, -.1 * hd, hc - .04 * hd), (.05 * hd, .09 * hd, .045 * hd), 'fur', 'Head', 16, 10, axes=[(1, 0, 0), (0, 1, .3), (0, -.3, 1)]) + self.ell('Muzzle_Light', (0, -.12 * hd, hc - .065 * hd), (.042 * hd, .07 * hd, .03 * hd), 'fur2', 'Head', 14, 8) + self.ell('Nose', (0, -.18 * hd, hc - .02 * hd), (.022 * hd, .014 * hd, .015 * hd), 'dark', 'Head', 10, 6) + self.ell('Brow', (0, -.07 * hd, hc + .03 * hd), (.07 * hd, .03 * hd, .02 * hd), 'fur', 'Head', 14, 8) + for side, sx in SIDES: + self.ell('Eye_' + side, (sx * .038 * hd, -.08 * hd, hc + .012 * hd), (.014 * hd, .008 * hd, .01 * hd), 'eye', 'Head', 10, 7) + self.cone('Fang_' + side, (sx * .025 * hd, -.16 * hd, hc - .07 * hd), (sx * .025 * hd, -.16 * hd, hc - .1 * hd), .007 * hd, 'bone', 'Head', 6) + self.tube('Ear_' + side, [(sx * .07 * hd, .02, hc + .04 * hd), (sx * .12 * hd, .04, hc + .06 * hd), (sx * .16 * hd, .06, hc + .05 * hd)], + [.025, .02, .002], 'fur', 'Head', 8, 8, flatten=.4, ref=(0, 0, 1)) + pts = [V((sx * .06 * hd, .02, hc + .07 * hd))] + for q in range(1, 9): + a = q / 8 * 1.6 * pi + pts.append(V((sx * (.09 + .07 * (1 - cos(a * .5))) * hd, .06 - .08 * sin(a), hc + .08 * hd + .09 * sin(a * .5 + .3) - .06 * q / 8))) + self.tube('Horn_' + side, pts, [.03 * hd, .027 * hd, .022 * hd, .016 * hd, .004], 'bone', 'Head', 10, 30, groove=.08, lobes=3) + # Tail on two bones. + t0 = V((0, .13, H + .02)) + self.tube('Tail', [t0, t0 + V((0, .14, -.08)), t0 + V((0, .26, -.22)), t0 + V((0, .3, -.4))], [.04, .032, .022, .01], 'fur', 'Tail.1', 12, 20, + weight_fn=lambda t: blend('Pelvis', 'Tail.1', t * 4) if t < .25 else blend('Tail.1', 'Tail.2', (t - .25) / .5)) + self.ell('Tail_Tuft', t0 + V((0, .3, -.43)), (.04, .04, .06), 'fur2', 'Tail.2', 10, 7, bumps=.25) + self.extra_bones.append(('Tail.1', t0, t0 + V((0, .2, -.14)), 'Pelvis')) + self.extra_bones.append(('Tail.2', t0 + V((0, .2, -.14)), t0 + V((0, .3, -.4)), 'Tail.1')) + + # ------------------------------------------------------------ rig + def rig(self): + for ob in self.parts: + bm = bmesh.new() + bm.from_mesh(ob.data) + bmesh.ops.remove_doubles(bm, verts=list(bm.verts), dist=.0000001) + bmesh.ops.triangulate(bm, faces=list(bm.faces)) + bmesh.ops.recalc_face_normals(bm, faces=list(bm.faces)) + if bm.calc_volume(signed=True) < 0: + bmesh.ops.reverse_faces(bm, faces=list(bm.faces)) + bm.to_mesh(ob.data) + bm.free() + ob.data.update() + bpy.ops.object.armature_add(enter_editmode=True) + rig = bpy.context.object + rig.name = 'PeopleRig' + ed = rig.data.edit_bones + ed.remove(ed[0]) + X, s = self.X, self.s + + def bone(name, head, tail, parent=None): + b = ed.new(name) + b.head, b.tail = head, tail + if parent: + b.parent = ed[parent] + self.defs[name] = (V(head), V(tail), parent) + + H = self.hip + bone('Root', (0, 0, 0), (0, 0, .15 * s)) + bone('Pelvis', X((0, .0, H - .06)), X((0, .0, self.waist)), 'Root') + bone('Spine', X((0, .0, self.waist)), X((0, .0, self.chest)), 'Pelvis') + bone('Chest', X((0, .0, self.chest)), X((0, .0, self.neck)), 'Spine') + bone('Neck', X((0, .0, self.neck)), X((0, -.01, self.head_c - .05)), 'Chest') + bone('Head', X((0, -.01, self.head_c - .05)), X((0, -.01, self.head_c + .12 * self.sp['head'])), 'Neck') + for side, sx in SIDES: + bone('Clavicle.' + side, X((sx * .05, .012, self.shoulder - .02)), self.sh[side], 'Chest') + bone('UpperArm.' + side, self.sh[side], self.elbow[side], 'Clavicle.' + side) + bone('Forearm.' + side, self.elbow[side], self.W[side], 'UpperArm.' + side) + fore = (self.W[side] - self.elbow[side]).normalized() + bone('Hand.' + side, self.W[side], self.W[side] + fore * .08 * s, 'Forearm.' + side) + hp, kn, an, toe = self.leg[side] + bone('Thigh.' + side, X(hp), X(kn), 'Pelvis') + bone('Shin.' + side, X(kn), X(an), 'Thigh.' + side) + bone('Foot.' + side, X(an), X(toe), 'Shin.' + side) + # Grip bones: the hands follow these; the weapon meshes are weighted to them. + if self.weapon_parent == 'Offhand': + order = [('Offhand', 'Chest'), ('Weapon', 'Offhand')] + elif self.target['L'] == 'Weapon': + order = [('Weapon', 'Chest'), ('Offhand', 'Weapon')] + else: + order = [('Weapon', 'Chest'), ('Offhand', 'Chest')] + for name, parent in order: + side = 'R' if name == 'Weapon' else 'L' + bone(name, self.G[side], self.G[side] + self.u[side] * .25 * s, parent) + if self.sp['style'] == 'bow': + aim = (self.G['L'] - self.G['R']).normalized() + bone('Arrow', self.G['R'], self.G['R'] + aim * .3 * s, 'Weapon') + self.scaled.add('Arrow') + for name, head, tail, parent in self.extra_bones: + bone(name, X(head), X(tail), parent) + bpy.ops.object.mode_set(mode='OBJECT') + for ob in self.parts: + mod = ob.modifiers.new('Weighted body', 'ARMATURE') + mod.object = rig + ob.parent = rig + self.rig_ob = rig + + def pose_rotation(self, pb, r): + q = Quaternion((0, 0, 1), r[2]) @ Quaternion((0, 1, 0), r[1]) @ Quaternion((1, 0, 0), r[0]) + basis = pb.bone.matrix_local.to_quaternion() + return basis.inverted() @ q @ basis + + def aim_bone(self, name, head, tail): + pb = self.rig_ob.pose.bones[name] + rest = pb.bone.matrix_local + direction = self.defs[name][1] - self.defs[name][0] + rotation = direction.rotation_difference(tail - head).to_matrix().to_4x4() + pose = rotation @ rest + pose.translation = head + pb.matrix = pose + bpy.context.view_layer.update() + + def delta(self, name): + return self.rig_ob.pose.bones[name].matrix @ self.rig_ob.data.bones[name].matrix_local.inverted() + + # ------------------------------------------------------------ animation + def animate(self, name, t, dur): + rs, locs, scl = {}, {}, {} + s, L = self.s, self.L + st = self.sp['style'] + + def rot(b, x=0, y=0, z=0): + old = rs.get(b, (0, 0, 0)) + rs[b] = (old[0] + x, old[1] + y, old[2] + z) + + def loc(b, x=0, y=0, z=0): + old = locs.get(b, (0, 0, 0)) + locs[b] = (old[0] + x, old[1] + y, old[2] + z) + + flap = 0.0 + if name == 'idle': + breath = sin(2 * pi * t / dur) + rot('Spine', .012 * breath) + rot('Chest', -.014 * breath, 0, .006 * breath) + rot('Head', .01 * breath, 0, -.02 * sin(2 * pi * t / dur)) + loc('Weapon', 0, 0, .006 * L * breath) + loc('Offhand', 0, 0, .005 * L * sin(2 * pi * t / dur + 1)) + flap = sin(2 * pi * t * 4) + if self.hover: + loc('Root', 0, 0, .025 * s * sin(2 * pi * t * 2 / dur)) + if st == 'bow': + loc('Offhand', 0, .14 * L, -.28 * L) + rot('Offhand', .35) + elif name in ('walk', 'start', 'stop'): + fade = 1 if name == 'walk' else sin(pi * .5 * t / dur) if name == 'start' else cos(pi * .5 * t / dur) + ph = 2 * pi * t / 1.1 + for side, sx in SIDES: + q = ph + (0 if side == 'L' else pi) + if self.hover: + rot('Thigh.' + side, -.12 * sin(q) * fade) + rot('Shin.' + side, .18 * fade) + continue + thigh = -.42 * sin(q) * fade + knee = .62 * max(0.0, cos(q)) * fade + .06 * fade + if self.sp.get('digitigrade'): + knee = -knee + rot('Thigh.' + side, thigh) + rot('Shin.' + side, knee) + rot('Foot.' + side, -(thigh + knee) * .6) + rot('Pelvis', 0, 0, .05 * sin(ph) * fade) + rot('Spine', .03 * fade, 0, -.04 * sin(ph) * fade) + rot('Chest', 0, 0, -.02 * sin(ph) * fade) + loc('Root', 0, 0, (-.018 * s * (1 - cos(2 * ph)) * .5 if not self.hover else .02 * s * sin(2 * ph)) * fade) + loc('Weapon', 0, .015 * L * sin(ph) * fade, .012 * L * sin(2 * ph) * fade) + loc('Offhand', 0, -.015 * L * sin(ph) * fade, .01 * L * sin(2 * ph + 1) * fade) + flap = sin(2 * pi * t * 6 / 1.1) + if st == 'bow': + loc('Offhand', 0, .14 * L, -.28 * L) + rot('Offhand', .35) + rot('Cloak', .12 * fade + .04 * sin(2 * ph) * fade) + elif name in ('attack_a', 'attack_b', 'ability'): + self.attack(name, t, rot, loc, scl) + flap = sin(2 * pi * t * 5) + elif name == 'hit': + q = curve(t, [(0, 0), (.066667, 1), (.2, .4), (.5, 0)]) + rot('Spine', -.1 * q, 0, -.05 * q) + rot('Chest', -.06 * q) + rot('Head', -.14 * q, 0, .08 * q) + loc('Root', 0, .04 * s * q, 0) + loc('Weapon', 0, .04 * L * q, .03 * L * q) + loc('Offhand', 0, .03 * L * q, .02 * L * q) + flap = sin(2 * pi * t * 6) + if st == 'bow': + loc('Offhand', 0, .14 * L * (1 - q), -.28 * L * (1 - q)) + rot('Offhand', .35 * (1 - q)) + elif name == 'death': + q = curve(t, [(0, 0), (.3, .12), (.8, .6), (1.3, 1), (1.5, .97), (2, 1)]) + rot('Root', -1.38 * q) + drop = self.hover if self.hover else 0.0 + loc('Root', 0, .02 * s * q, (.05 * s - drop) * q) + rot('Spine', .1 * q) + rot('Head', -.25 * q, 0, .2 * q) + loc('Weapon', 0, .08 * L * q, -.06 * L * q) + loc('Offhand', 0, .06 * L * q, -.08 * L * q) + for side, sx in SIDES: + rot('Thigh.' + side, -.35 * q) + rot('Shin.' + side, (.55 if not self.sp.get('digitigrade') else -.4) * q) + flap = sin(2 * pi * t * 6) * (1 - q) + rot('Wing.L', 0, -.4 * q) + rot('Wing.R', 0, .4 * q) + if 'Wing.L' in self.defs: + rot('Wing.L', 0, .45 * flap, -.2 * flap) + rot('Wing.R', 0, -.45 * flap, .2 * flap) + if 'Tail.1' in self.defs: + period = dur if name in ('idle', 'walk') else 1.3 + rot('Tail.1', .1 * sin(2 * pi * t / period), 0, .3 * sin(2 * pi * t / period)) + rot('Tail.2', .05 * sin(2 * pi * t / period), 0, .4 * sin(2 * pi * t / period - .8)) + self.apply(rs, locs, scl, name, t) + + def attack(self, name, t, rot, loc, scl): + st, s, L = self.sp['style'], self.s, self.L + alt, charged = name == 'attack_b', name == 'ability' + if st in ('blade_shield', 'axe'): + if charged and st == 'blade_shield': + bash = curve(t, [(0, 0), (.55, -.45), (.82, -.45), (.9, 1), (1.05, 1), (1.3, 0)]) + loc('Offhand', .02 * L * bash, -.16 * L * max(0.0, bash) + .06 * L * max(0.0, -bash), .02 * L * abs(bash)) + loc('Weapon', 0, .06 * L * abs(bash), .05 * L * abs(bash)) + rot('Chest', 0, 0, .28 * bash) + rot('Spine', .08 * max(0.0, bash)) + loc('Root', 0, -.1 * s * max(0.0, bash), 0) + rot('Thigh.L', -.25 * max(0.0, bash)) + rot('Shin.L', .2 * max(0.0, bash)) + return + if charged: + # War cry: both arms up, head back, then a forward stamp. + cry = curve(t, [(0, 0), (.5, 1), (.85, 1), (.9, .4), (1.1, .2), (1.3, 0)]) + loc('Weapon', -.05 * L * cry, .06 * L * cry, .4 * L * cry) + rot('Weapon', -.5 * cry) + loc('Offhand', .05 * L * cry, .05 * L * cry, .42 * L * cry) + rot('Head', -.3 * cry) + rot('Chest', -.12 * cry) + stamp = curve(t, [(0, 0), (.8, 0), (.9, 1), (1.1, 1), (1.3, 0)]) + rot('Thigh.R', -.3 * stamp) + loc('Root', 0, -.06 * s * stamp, 0) + return + if not alt: + up = curve(t, [(0, 0), (.45, 1), (.78, 1), (.9, -.7), (1.05, -.7), (1.3, 0)]) + rot('Weapon', -1.3 * up, 0, -.3 * up) + if up > 0: + loc('Weapon', -.04 * L * up, .1 * L * up, .3 * L * up) + else: + loc('Weapon', -.08 * L * up, .16 * L * up, .02 * L * up) + rot('Chest', -.1 * min(0.0, up), 0, .28 * up) + rot('Spine', -.06 * max(0.0, up) + .1 * max(0.0, -up)) + if st == 'axe': + loc('Offhand', 0, .1 * L * max(0.0, up), .05 * L * max(0.0, up)) + else: + side = curve(t, [(0, 0), (.45, 1), (.78, 1), (.9, -.8), (1.05, -.8), (1.3, 0)]) + sgn = 1 if st == 'blade_shield' else -1 + rot('Weapon', .5 * abs(side), 1.25 * side * sgn, 0) + loc('Weapon', .14 * L * side * sgn * -1, .06 * L * side, .1 * L * abs(side)) + rot('Chest', 0, 0, .32 * side * sgn) + rot('Thigh.R', .15 * curve(t, [(0, 0), (.45, 1), (.9, -1), (1.3, 0)])) + rot('Thigh.L', -.2 * curve(t, [(0, 0), (.45, 0), (.9, 1), (1.3, 0)])) + rot('Shin.L', .2 * curve(t, [(0, 0), (.45, 0), (.9, 1), (1.3, 0)])) + elif st == 'club2h': + if not alt: + amp = 1.12 if charged else 1.0 + up = curve(t, [(0, 0), (.5, 1), (.8, 1), (.9, -.6), (1.1, -.6), (1.3, 0)]) + rot('Weapon', -1.25 * up * amp) + if up > 0: + loc('Weapon', .04 * L * up, .08 * L * up, .3 * L * up * amp) + else: + loc('Weapon', 0, .12 * L * up, .08 * L * up) + rot('Spine', -.16 * max(0.0, up) + .14 * max(0.0, -up)) + rot('Chest', -.08 * max(0.0, up) + .1 * max(0.0, -up)) + if charged: + loc('Root', 0, 0, -.06 * s * max(0.0, -up)) + for side, _ in SIDES: + rot('Thigh.' + side, -.2 * max(0.0, -up)) + rot('Shin.' + side, .35 * max(0.0, -up)) + else: + side = curve(t, [(0, 0), (.5, 1), (.8, 1), (.9, -.8), (1.1, -.8), (1.3, 0)]) + rot('Weapon', .35 * abs(side), -1.0 * side, 0) + loc('Weapon', -.12 * L * side, .06 * L * side, .06 * L * abs(side)) + rot('Chest', 0, 0, -.3 * side) + rot('Spine', 0, 0, -.12 * side) + elif st == 'bow': + raise_bow = curve(t, [(0, 0), (.25, 1), (1.12, 1), (1.3, 0)]) + loc('Offhand', 0, .14 * L * (1 - raise_bow), -.28 * L * (1 - raise_bow)) + rot('Offhand', .35 * (1 - raise_bow), -.3 * raise_bow if alt else 0, 0) + full = .45 if not charged else .5 + draw = curve(t, [(0, 0), (.3, 0), (.82, 1), (.9, 1), (.933333, 1.12), (1.1, 1.12), (1.28, 0), (1.3, 0)]) + loc('Weapon', 0, full * L * min(draw, 1.0), .02 * L * max(0.0, draw - 1) * 8) + rot('Chest', 0, 0, -.25 * raise_bow) + rot('Head', 0, 0, .35 * raise_bow) + rot('Spine', -.04 * raise_bow if not charged else -.08 * raise_bow) + scale = 1.0 if t < .9 or t > 1.22 else .001 + scl['Arrow'] = (scale, scale, scale) + elif st == 'staff': + if charged: + up = curve(t, [(0, 0), (.6, 1), (.85, 1), (.9, 1.1), (1.1, 1), (1.3, 0)]) + loc('Weapon', -.04 * L * up, .05 * L * up, .36 * L * up) + loc('Offhand', .04 * L * up, .02 * L * up, .34 * L * up) + rot('Offhand', -.8 * up) + rot('Head', -.2 * up) + loc('Root', 0, 0, .08 * s * up) + elif not alt: + th = curve(t, [(0, 0), (.5, -.5), (.8, -.6), (.9, 1), (1.05, 1), (1.3, 0)]) + loc('Weapon', 0, -.2 * L * th, .06 * L * abs(th)) + rot('Weapon', .5 * th) + loc('Offhand', 0, -.12 * L * max(0.0, th), .08 * L * max(0.0, th)) + rot('Chest', 0, 0, -.15 * th) + else: + cast = curve(t, [(0, 0), (.5, -.4), (.82, -.5), (.9, 1), (1.05, 1), (1.3, 0)]) + loc('Offhand', .04 * L * cast, -.2 * L * max(0.0, cast) + .08 * L * max(0.0, -cast), .1 * L * abs(cast)) + rot('Offhand', -.4 * max(0.0, cast)) + rot('Chest', 0, 0, .2 * cast) + elif st == 'claws': + if charged: + pounce = curve(t, [(0, 0), (.55, -.5), (.8, -.6), (.9, 1), (1.05, 1), (1.3, 0)]) + loc('Root', 0, -.35 * s * max(0.0, pounce), -.05 * s * max(0.0, -pounce) + .04 * s * max(0.0, pounce)) + rot('Spine', .2 * max(0.0, pounce) - .1 * max(0.0, -pounce)) + for side, sx in SIDES: + b = 'Weapon' if side == 'R' else 'Offhand' + loc(b, 0, -.1 * L * max(0.0, pounce) + .08 * L * max(0.0, -pounce), .25 * L * max(0.0, -pounce) - .1 * L * max(0.0, pounce)) + rot(b, .6 * max(0.0, pounce)) + rot('Thigh.' + side, .3 * max(0.0, -pounce) - .2 * max(0.0, pounce)) + rot('Shin.' + side, .4 * max(0.0, -pounce)) + else: + b, sx = ('Weapon', -1) if not alt else ('Offhand', 1) + sw = curve(t, [(0, 0), (.5, 1), (.8, 1), (.9, -.8), (1.05, -.8), (1.3, 0)]) + if sw > 0: + loc(b, sx * .12 * L * sw, .1 * L * sw, .28 * L * sw) + else: + loc(b, sx * .16 * L * sw, .12 * L * sw, .12 * L * sw) + rot(b, -.6 * sw, 0, -sx * .4 * sw) + rot('Chest', .08 * max(0.0, -sw), 0, -sx * .3 * sw) + rot('Spine', .06 * max(0.0, -sw)) + loc('Root', 0, -.05 * s * max(0.0, -sw), 0) + + def apply(self, rs, locs, scl, name, t): + rig = self.rig_ob + for pb in rig.pose.bones: + pb.rotation_mode = 'QUATERNION' + pb.rotation_quaternion = self.pose_rotation(pb, rs.get(pb.name, (0, 0, 0))) + pb.location = pb.bone.matrix_local.to_3x3().inverted() @ V(locs.get(pb.name, (0, 0, 0))) + pb.scale = scl.get(pb.name, (1, 1, 1)) + bpy.context.view_layer.update() + chest = self.delta('Chest') + for side, _ in SIDES: + target = self.delta(self.target[side]) + c = target @ self.W[side] + a = rig.pose.bones['UpperArm.' + side].head.copy() + elbow, reach, error = ik_elbow(a, c, self.l1, self.l2, chest @ self.pole[side]) + self.aim_bone('UpperArm.' + side, a, elbow) + self.aim_bone('Forearm.' + side, elbow, reach) + hand = target @ rig.data.bones['Hand.' + side].matrix_local + hand.translation = c + rig.pose.bones['Hand.' + side].matrix = hand + bpy.context.view_layer.update() + self.errors.append((error, name, round(t, 3), side)) + + def bake(self): + rig = self.rig_ob + for name, (dur, loop) in CLIPS.items(): + rig.animation_data_clear() + rig.animation_data_create() + action = bpy.data.actions.new(name) + action.use_fake_user = True + rig.animation_data.action = action + for frame in range(round(dur * 30) + 1): + self.animate(name, frame / 30, dur) + for pb in rig.pose.bones: + pb.keyframe_insert('rotation_quaternion', frame=frame) + pb.keyframe_insert('location', frame=frame) + if pb.name in self.scaled: + pb.keyframe_insert('scale', frame=frame) + action['loop'] = loop + action['contact_seconds'] = CONTACT if name in ('attack_a', 'attack_b', 'ability') else -1 + for layer in action.layers: + for strip in layer.strips: + for bag in strip.channelbags: + for fc in bag.fcurves: + for key in fc.keyframe_points: + key.interpolation = 'LINEAR' + rig.animation_data_clear() + rig.animation_data_create() + rig.animation_data.action = bpy.data.actions['idle'] + bpy.context.scene.render.fps = 30 + bpy.context.scene.frame_set(0) + + def export(self): + bpy.ops.wm.save_as_mainfile(filepath=str(SRC / ('peoples/%s.blend' % self.key))) + source_parts = len(self.parts) + bpy.ops.object.select_all(action='DESELECT') + for ob in self.parts: + ob.select_set(True) + bpy.context.view_layer.objects.active = self.parts[0] + bpy.ops.object.join() + body = bpy.context.object + body.name = '%sBody' % self.sp['tag'] + body.select_set(True) + self.rig_ob.select_set(True) + bpy.context.view_layer.objects.active = self.rig_ob + path = OUT / ('%s.glb' % self.key) + bpy.ops.export_scene.gltf(filepath=str(path), export_format='GLB', use_selection=True, export_animations=True, + export_animation_mode='ACTIONS', export_force_sampling=True, export_frame_range=False, + export_anim_slide_to_zero=True, export_skins=True, export_all_influences=False, + export_def_bones=True, export_anim_single_armature=True, export_materials='EXPORT', export_yup=True) + raw = path.read_bytes() + doc = json.loads(raw[20:20 + struct.unpack_from(' verteidigung > teamhilfe > fernkampf > druck > bossjagd), keinen Zyklus. In gemischten Teams sind alle Profile im Band 24–64 %. +- Bossjagd als reines Teamprofil 0/10, in Mischteams 24 %. Die Risikoabschätzung ist besser geworden, die Jagd bleibt teuer. + ## Bekannt -- Ein exakt gespiegeltes Match bleibt unentschieden, wird also nicht beendet (Testlimit). Das ist ein Balancebefund für M3. -- Die Figuren der Monsterwellen sind nur über Teamring und Lichtfarbe dem Team zugeordnet. -- Bäume und Felsen stammen aus dem Vergleichspack und sind einfach; das Kartenbild ist funktional, noch nicht endgültig (M3). -- Der Bossjäger-Bot greift zu früh an (Fehlschläge in Runde 5–7, Sieg in Runde 8); nach den Szenarien wiegt der Boss die geschwächte Lane eher zu wenig auf (M3). -- Die Auswertung der Bot-Profile ist verzerrt, weil in jeder Partie dieselben Profilpaare gegeneinander stehen (M3: variierte Teams). +- Ein exakt gespiegeltes Match bleibt unentschieden, wird also nicht beendet (Testlimit). Das ist der erwartbare Grenzfall identischen Spiels; Asymmetrie kommt aus Helden, Champions und Boss. +- Die Monsterwellen sind nur über Teamring und Lichtfarbe dem Team zugeordnet. - Beim Beenden meldet Godot eine noch belegte Ressource (Hintergrundladen der Menübühne); ohne Folgen im Spiel. +- Die sieben Volksrepräsentanten sind noch nicht spielbar (Daten und Modelle liegen vor, Plan in VOELKER.md). ## Blockiert - Nativer Windows-11-Test: keine Windows-Hardware. +- Kompatibilitätsmodus (OpenGL) für Luneraria: erst mit M4-Exporten gezielt prüfen. ## Nächster Schritt -M3: mehrere Seeds und Teamzusammensetzungen, Seitentausch, Gegenstrategien, hohe Wellenindizes als Belastungstest, Boss- und Profilbalance, visuelle Überarbeitung der Karte, danach Repräsentanten der fehlenden Völker. +M4: autoritatives Host/Join für sechs Slots (Direktverbindung, Bots für freie Slots), synchronisierte Phasen und Zustandsabgleich, reale Mehrprozessprüfungen (Loopback), reproduzierbare Linux-/Windows-Exports, entpackbare Demo, klare Testmatrix. Öffentliches Matchmaking bleibt außerhalb des Umfangs. diff --git a/docs/luneraria/VOELKER.md b/docs/luneraria/VOELKER.md new file mode 100644 index 0000000..01a86a4 --- /dev/null +++ b/docs/luneraria/VOELKER.md @@ -0,0 +1,40 @@ +# Völker und Repräsentanten + +Stand: 28. September 2026 (M3). Trennung der Daten: **Volk** · **Bund** · **Klasse** · **Kampfrolle**. Ein Volk ist kein eigener Kampfmechanismus; Rollen kommen aus dem gemeinsamen Truppen-/Heldenkatalog. + +## Repräsentanten M3 + +Jedes der sieben Zielvölker besitzt genau ein eigenständiges Blender-Modell mit gewichtetem Skelett (22–24 Knochen, 9 Clips: idle, start, walk, stop, attack_a, attack_b, hit, ability, death). Erzeugt mit `assets-source/luneraria/build_peoples.py`, exportiert nach `assets/luneraria/peoples/p_*.glb`, Manifest `assets/luneraria/peoples_manifest.json`, Prüfbefund `assets-source/luneraria/peoples-audit.json`. + +| Volk | ID | Name | Klasse | Kampfrolle | Besonderheit | +| --- | --- | --- | --- | --- | --- | +| Menschen | `p_mensch` | Ritter | Ritter | Front | Schild + Schwert, Plattenrüstung | +| Elfen | `p_elf` | Waldläuferin | Waldläuferin | Fernkampf | Bogen, leichte Rüstung, spitze Ohren | +| Zwerge | `p_zwerg` | Schildträger | Schildträger | Front | Vollbart, Hornhelm, Rundschild | +| Trolle | `p_troll` | Steinhauer | Steinhauer | Fläche | massive Keule, Felsplatten | +| Feen | `p_fee` | Lichtheilerin | Lichtheilerin | Unterstützung | Flügel, Glüheffekte | +| Orks | `p_ork` | Kriegshäuptling | Kriegshäuptling | Nahkampf | Krallenwaffe, Fell | +| Biestmenschen | `p_biest` | Klauenjäger | Klauenjäger | Nahkampf | Bestienanatomie, Klauen | + +Sichtprüfung: `screenshots/peoples-ruhe.png`, `-kontakt`, `-laufen`, `-faehigkeit`, `-tod`, `-detail-a/b`. Werkzeug: `tools/luneraria/people_lineup.tscn`. + +## Was noch nicht implementiert ist + +- Keine der sieben Figuren ist derzeit **spielbar**. Die laufenden Bünde Glutpakt und Wurzelbund behalten ihre vorhandenen Modelle (Glutkonstrukte, Wurzelwesen). +- Es gibt **keine** Volkswahl im Menü und **keine** volksspezifischen Boni oder Fähigkeiten. +- Die Repräsentanten sind Bestandteil der Assetmatrix und der Entwicklungsplanung, nicht des Matchrosters. + +## Entwicklungsplan (nach M4) + +1. **Volksdaten in den Katalog:** `people`, `bund`, `klasse`, `rolle` als getrennte Felder in `DataCatalog` / `LuneContent`. Einheitentypen verweisen auf ein Volk, ein Bund bleibt eine spielbare Fraktion mit eigener Aufwertungslinie. +2. **Spielbare Vertretung pro Volk:** je ein normaler Truppentyp und ein Aufwertungsschritt aus den Repräsentanten-Animationen, übernommen in die bestehenden Rollen Front / Nahkampf / Fernkampf / Fläche / Unterstützung. Kein eigenes Kampfsystem pro Volk. +3. **Bündekopplung:** Jeder Bund öffnet eine Teilmenge der Völker (z. B. Wurzelbund: Elfen, Feen, Biestmenschen; Glutpakt: Menschen, Orks, Zwerge, Trolle). Volk formt Startboni und Heldenauswahl, nicht die Grundregeln. +4. **Helden pro Volk:** je ein Held aus dem Repräsentantenmodell mit zwei Talentrichtungen, analog zu Ringborke / Quellgeflecht / Funkenlanze. Vorläufige Bestandshelden bleiben erhalten. +5. **Balancierung über Rollen, nicht Sammelboni:** Volkssynergien sind höchstens eine kleine, sichtbare Verstärkung (z. B. Formationsschild), nie der einzige Hebel. +6. **Inhaltliche Ausweitung:** weitere Klassen pro Volk (Aufwertungen, Champion-Varianten) erst nach stabilen sechs Rollen und laufendem Netzwerkmodus. + +## Bestandsschutz + +- Ausgearbeitete Modelle (Ringborke, Funkenleser, Basaltbastion, Quellgeflecht, Funkenlanze, Glasballiste, Kernkristalle) bleiben unverändert erhalten. +- Nietenkonvent (`m*`) und Nebelchor (`n*`) bleiben als Reserve in der Assetmatrix und werden nicht als Völker ausgegeben. +- Die LP_*-Materialien der Völkermodelle laufen über denselben Kluft-/Sanctuary-Pfad wie die übrigen Figuren (`asset_library.gd`). diff --git a/docs/luneraria/logs/m1-acceptance.json b/docs/luneraria/logs/m1-acceptance.json index 406a38b..a5fdde6 100644 --- a/docs/luneraria/logs/m1-acceptance.json +++ b/docs/luneraria/logs/m1-acceptance.json @@ -2,9 +2,9 @@ "checks": 38, "failures": [], "object_counts": [ - 3850.0, - 3850.0, - 3850.0, - 3850.0 + 3363.0, + 3363.0, + 3363.0, + 3363.0 ] } \ No newline at end of file diff --git a/docs/luneraria/logs/m2-acceptance.json b/docs/luneraria/logs/m2-acceptance.json index 4a4eb17..1eaf455 100644 --- a/docs/luneraria/logs/m2-acceptance.json +++ b/docs/luneraria/logs/m2-acceptance.json @@ -1,29 +1,5 @@ { "boss_log": [ - { - "event": "failed", - "round": 5 - }, - { - "event": "failed", - "round": 6 - }, - { - "event": "failed", - "round": 7 - }, - { - "chooser": 4, - "event": "won", - "round": 8, - "team": 1 - }, - { - "event": "deployed", - "lane": "top", - "round": 9, - "team": 1 - }, { "chooser": 4, "event": "won", @@ -31,14 +7,13 @@ "team": 1 } ], - "checks": 15, + "checks": 14, "failures": [], "rounds": 12, "seen": [ - "grotto", + "announce", "hero_field", "hero_items", - "announce", - "boss" + "grotto" ] } \ No newline at end of file diff --git a/docs/luneraria/logs/m3/balance-before-summary.txt b/docs/luneraria/logs/m3/balance-before-summary.txt new file mode 100644 index 0000000..cfa0dc3 --- /dev/null +++ b/docs/luneraria/logs/m3/balance-before-summary.txt @@ -0,0 +1,6 @@ +Partien 40 · Ergebnisse {'blau': 20, 'rot': 20} · Testlimit 0 · Zeitfenster-Kämpfe 1 · längster Kampf 180 s +Wellen bis Ende: min 7 · median 9 · max 14 +Seitentausch: dieselbe Zusammensetzung gewinnt beide Male in 20/20 Paaren +Profile (Siege/Einsätze): {'ausgewogen': '24/48 (50 %)', 'bossjagd': '2/34 (6 %)', 'druck': '28/34 (82 %)', 'fernkampf': '26/48 (54 %)', 'teamhilfe': '28/48 (58 %)', 'verteidigung': '12/28 (43 %)'} +Bünde: {'glutpakt': '68/124 (55 %)', 'wurzelbund': '52/116 (45 %)'} +Boss: {'won': 18, 'deployed': 10, 'failed': 63} · Heldenstufen am Ende (Median): 4 diff --git a/docs/luneraria/logs/m3/balance-before.json b/docs/luneraria/logs/m3/balance-before.json new file mode 100644 index 0000000..1c522da --- /dev/null +++ b/docs/luneraria/logs/m3/balance-before.json @@ -0,0 +1,1738 @@ +[ + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "wurzelbund", + "wurzelbund" + ], + "index": 0, + "normal": { + "boss": [], + "hero_levels": [ + 3, + 2, + 2, + 0, + 2, + 0 + ], + "lane_losses_at_end": [ + 1, + 1 + ], + "max_combat": 180.000000000007, + "result": "blau", + "rounds": 7, + "timeouts": 1, + "total_gold_curve": [ + 743, + 745, + 1055, + 1242, + 1023, + 1788, + 458 + ] + }, + "profiles": [ + "ausgewogen", + "druck", + "teamhilfe", + "teamhilfe", + "verteidigung", + "ausgewogen" + ], + "seed": 1000, + "swapped": { + "boss": [], + "hero_levels": [ + 2, + 2, + 2, + 3, + 2, + 2 + ], + "lane_losses_at_end": [ + 1, + 1 + ], + "max_combat": 102.599999999996, + "result": "rot", + "rounds": 8, + "timeouts": 0, + "total_gold_curve": [ + 743, + 745, + 905, + 972, + 1293, + 1048, + 1640, + 816 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "wurzelbund", + "glutpakt" + ], + "index": 1, + "normal": { + "boss": [ + "5:won:0", + "6:deployed:0" + ], + "hero_levels": [ + 3, + 3, + 3, + 6, + 4, + 5 + ], + "lane_losses_at_end": [ + 2, + 1 + ], + "max_combat": 124.249999999995, + "result": "rot", + "rounds": 8, + "timeouts": 0, + "total_gold_curve": [ + 843, + 750, + 970, + 812, + 1318, + 2223, + 1627, + 1124 + ] + }, + "profiles": [ + "bossjagd", + "bossjagd", + "teamhilfe", + "fernkampf", + "teamhilfe", + "fernkampf" + ], + "seed": 1001, + "swapped": { + "boss": [ + "5:won:1", + "6:deployed:1" + ], + "hero_levels": [ + 5, + 4, + 4, + 0, + 0, + 0 + ], + "lane_losses_at_end": [ + 0, + 2 + ], + "max_combat": 119.349999999995, + "result": "blau", + "rounds": 8, + "timeouts": 0, + "total_gold_curve": [ + 843, + 750, + 970, + 812, + 1168, + 2118, + 1607, + 1134 + ] + } + }, + { + "bunds": [ + "glutpakt", + "wurzelbund", + "wurzelbund", + "wurzelbund", + "glutpakt", + "glutpakt" + ], + "index": 2, + "normal": { + "boss": [ + "5:failed:", + "6:failed:", + "7:failed:", + "8:failed:", + "9:won:0" + ], + "hero_levels": [ + 0, + 0, + 0, + 5, + 5, + 5 + ], + "lane_losses_at_end": [ + 2, + 0 + ], + "max_combat": 84.4499999999973, + "result": "rot", + "rounds": 9, + "timeouts": 0, + "total_gold_curve": [ + 753, + 780, + 930, + 962, + 988, + 1273, + 1215, + 1330, + 1087 + ] + }, + "profiles": [ + "bossjagd", + "ausgewogen", + "teamhilfe", + "druck", + "ausgewogen", + "verteidigung" + ], + "seed": 1002, + "swapped": { + "boss": [ + "5:failed:", + "6:failed:", + "7:failed:", + "8:failed:", + "9:won:1" + ], + "hero_levels": [ + 5, + 5, + 5, + 0, + 0, + 0 + ], + "lane_losses_at_end": [ + 0, + 2 + ], + "max_combat": 84.4499999999973, + "result": "blau", + "rounds": 9, + "timeouts": 0, + "total_gold_curve": [ + 753, + 780, + 930, + 962, + 988, + 1273, + 1215, + 1330, + 1087 + ] + } + }, + { + "bunds": [ + "glutpakt", + "wurzelbund", + "wurzelbund", + "wurzelbund", + "wurzelbund", + "wurzelbund" + ], + "index": 3, + "normal": { + "boss": [], + "hero_levels": [ + 7, + 7, + 4, + 6, + 0, + 0 + ], + "lane_losses_at_end": [ + 0, + 3 + ], + "max_combat": 110.899999999996, + "result": "blau", + "rounds": 13, + "timeouts": 0, + "total_gold_curve": [ + 838, + 695, + 875, + 967, + 978, + 968, + 1180, + 1370, + 1173, + 1745, + 1803, + 1867, + 1635 + ] + }, + "profiles": [ + "fernkampf", + "druck", + "ausgewogen", + "druck", + "fernkampf", + "verteidigung" + ], + "seed": 1003, + "swapped": { + "boss": [], + "hero_levels": [ + 6, + 0, + 0, + 7, + 7, + 4 + ], + "lane_losses_at_end": [ + 3, + 0 + ], + "max_combat": 110.899999999996, + "result": "rot", + "rounds": 13, + "timeouts": 0, + "total_gold_curve": [ + 838, + 695, + 875, + 967, + 978, + 968, + 1180, + 1370, + 1173, + 1745, + 1803, + 1867, + 1635 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "wurzelbund", + "wurzelbund", + "glutpakt", + "glutpakt", + "glutpakt" + ], + "index": 4, + "normal": { + "boss": [], + "hero_levels": [ + 0, + 0, + 3, + 0, + 0, + 0 + ], + "lane_losses_at_end": [ + 1, + 1 + ], + "max_combat": 108.399999999996, + "result": "blau", + "rounds": 9, + "timeouts": 0, + "total_gold_curve": [ + 848, + 725, + 985, + 897, + 1148, + 1203, + 1255, + 1350, + 980 + ] + }, + "profiles": [ + "ausgewogen", + "teamhilfe", + "fernkampf", + "ausgewogen", + "verteidigung", + "fernkampf" + ], + "seed": 1004, + "swapped": { + "boss": [], + "hero_levels": [ + 0, + 0, + 0, + 0, + 0, + 3 + ], + "lane_losses_at_end": [ + 1, + 1 + ], + "max_combat": 108.399999999996, + "result": "rot", + "rounds": 9, + "timeouts": 0, + "total_gold_curve": [ + 848, + 725, + 985, + 897, + 1148, + 1203, + 1255, + 1350, + 980 + ] + } + }, + { + "bunds": [ + "glutpakt", + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt" + ], + "index": 5, + "normal": { + "boss": [ + "5:won:1", + "6:deployed:1" + ], + "hero_levels": [ + 5, + 5, + 4, + 3, + 3, + 3 + ], + "lane_losses_at_end": [ + 1, + 3 + ], + "max_combat": 116.849999999996, + "result": "blau", + "rounds": 8, + "timeouts": 0, + "total_gold_curve": [ + 853, + 785, + 995, + 907, + 1178, + 2338, + 1920, + 1058 + ] + }, + "profiles": [ + "teamhilfe", + "fernkampf", + "verteidigung", + "fernkampf", + "bossjagd", + "bossjagd" + ], + "seed": 1005, + "swapped": { + "boss": [ + "5:won:0", + "6:deployed:0" + ], + "hero_levels": [ + 3, + 3, + 3, + 5, + 5, + 4 + ], + "lane_losses_at_end": [ + 3, + 1 + ], + "max_combat": 116.849999999996, + "result": "rot", + "rounds": 8, + "timeouts": 0, + "total_gold_curve": [ + 853, + 785, + 995, + 907, + 1178, + 2338, + 1920, + 1058 + ] + } + }, + { + "bunds": [ + "glutpakt", + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt" + ], + "index": 6, + "normal": { + "boss": [ + "5:failed:", + "6:failed:", + "7:failed:" + ], + "hero_levels": [ + 0, + 0, + 0, + 0, + 2, + 0 + ], + "lane_losses_at_end": [ + 1, + 1 + ], + "max_combat": 88.6999999999971, + "result": "rot", + "rounds": 7, + "timeouts": 0, + "total_gold_curve": [ + 803, + 755, + 980, + 992, + 1158, + 1373, + 1085 + ] + }, + "profiles": [ + "bossjagd", + "ausgewogen", + "teamhilfe", + "fernkampf", + "teamhilfe", + "ausgewogen" + ], + "seed": 1006, + "swapped": { + "boss": [ + "5:failed:", + "6:failed:", + "7:failed:", + "8:failed:" + ], + "hero_levels": [ + 3, + 4, + 2, + 0, + 0, + 0 + ], + "lane_losses_at_end": [ + 0, + 3 + ], + "max_combat": 99.2499999999965, + "result": "blau", + "rounds": 8, + "timeouts": 0, + "total_gold_curve": [ + 803, + 755, + 980, + 842, + 1098, + 1063, + 1495, + 1408 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt", + "glutpakt" + ], + "index": 7, + "normal": { + "boss": [], + "hero_levels": [ + 0, + 0, + 0, + 6, + 5, + 5 + ], + "lane_losses_at_end": [ + 2, + 0 + ], + "max_combat": 112.249999999996, + "result": "rot", + "rounds": 12, + "timeouts": 0, + "total_gold_curve": [ + 753, + 740, + 965, + 837, + 878, + 933, + 1190, + 1035, + 1223, + 1680, + 1633, + 1484 + ] + }, + "profiles": [ + "ausgewogen", + "ausgewogen", + "ausgewogen", + "teamhilfe", + "teamhilfe", + "ausgewogen" + ], + "seed": 1007, + "swapped": { + "boss": [], + "hero_levels": [ + 6, + 5, + 5, + 0, + 0, + 0 + ], + "lane_losses_at_end": [ + 0, + 2 + ], + "max_combat": 112.249999999996, + "result": "blau", + "rounds": 12, + "timeouts": 0, + "total_gold_curve": [ + 753, + 740, + 965, + 837, + 878, + 933, + 1190, + 1035, + 1223, + 1680, + 1633, + 1484 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "wurzelbund", + "wurzelbund", + "glutpakt", + "glutpakt", + "glutpakt" + ], + "index": 8, + "normal": { + "boss": [ + "5:failed:", + "6:failed:", + "7:failed:", + "8:won:0", + "9:deployed:0" + ], + "hero_levels": [ + 2, + 2, + 2, + 6, + 4, + 7 + ], + "lane_losses_at_end": [ + 2, + 1 + ], + "max_combat": 134.999999999996, + "result": "rot", + "rounds": 10, + "timeouts": 0, + "total_gold_curve": [ + 753, + 755, + 875, + 807, + 958, + 1223, + 1220, + 1555, + 3153, + 1104 + ] + }, + "profiles": [ + "teamhilfe", + "ausgewogen", + "bossjagd", + "druck", + "ausgewogen", + "druck" + ], + "seed": 1008, + "swapped": { + "boss": [ + "5:failed:", + "6:failed:", + "7:failed:", + "8:won:1", + "9:deployed:1" + ], + "hero_levels": [ + 6, + 4, + 7, + 2, + 2, + 2 + ], + "lane_losses_at_end": [ + 1, + 2 + ], + "max_combat": 134.999999999996, + "result": "blau", + "rounds": 10, + "timeouts": 0, + "total_gold_curve": [ + 753, + 755, + 875, + 807, + 958, + 1223, + 1220, + 1555, + 3153, + 1104 + ] + } + }, + { + "bunds": [ + "glutpakt", + "wurzelbund", + "glutpakt", + "glutpakt", + "wurzelbund", + "glutpakt" + ], + "index": 9, + "normal": { + "boss": [ + "5:failed:", + "6:failed:", + "7:failed:", + "8:failed:", + "9:won:1" + ], + "hero_levels": [ + 5, + 6, + 5, + 0, + 0, + 0 + ], + "lane_losses_at_end": [ + 0, + 2 + ], + "max_combat": 93.7499999999968, + "result": "blau", + "rounds": 9, + "timeouts": 0, + "total_gold_curve": [ + 758, + 790, + 905, + 952, + 1008, + 1338, + 1275, + 1105, + 1265 + ] + }, + "profiles": [ + "verteidigung", + "druck", + "druck", + "bossjagd", + "ausgewogen", + "teamhilfe" + ], + "seed": 1009, + "swapped": { + "boss": [ + "5:failed:", + "6:failed:", + "7:failed:", + "8:failed:", + "9:won:0" + ], + "hero_levels": [ + 0, + 0, + 0, + 5, + 6, + 5 + ], + "lane_losses_at_end": [ + 2, + 0 + ], + "max_combat": 93.7499999999968, + "result": "rot", + "rounds": 9, + "timeouts": 0, + "total_gold_curve": [ + 758, + 790, + 905, + 952, + 1008, + 1338, + 1275, + 1105, + 1265 + ] + } + }, + { + "bunds": [ + "glutpakt", + "wurzelbund", + "wurzelbund", + "wurzelbund", + "glutpakt", + "glutpakt" + ], + "index": 10, + "normal": { + "boss": [ + "5:failed:", + "6:failed:", + "7:failed:", + "8:failed:", + "9:won:1" + ], + "hero_levels": [ + 5, + 4, + 3, + 0, + 0, + 0 + ], + "lane_losses_at_end": [ + 0, + 1 + ], + "max_combat": 94.8999999999968, + "result": "blau", + "rounds": 9, + "timeouts": 0, + "total_gold_curve": [ + 753, + 755, + 915, + 832, + 983, + 1118, + 1295, + 1080, + 1221 + ] + }, + "profiles": [ + "druck", + "ausgewogen", + "ausgewogen", + "teamhilfe", + "bossjagd", + "ausgewogen" + ], + "seed": 1010, + "swapped": { + "boss": [ + "5:failed:", + "6:failed:", + "7:failed:", + "8:failed:", + "9:won:0" + ], + "hero_levels": [ + 0, + 0, + 0, + 5, + 4, + 3 + ], + "lane_losses_at_end": [ + 1, + 0 + ], + "max_combat": 94.8999999999968, + "result": "rot", + "rounds": 9, + "timeouts": 0, + "total_gold_curve": [ + 753, + 755, + 915, + 832, + 983, + 1118, + 1295, + 1080, + 1221 + ] + } + }, + { + "bunds": [ + "glutpakt", + "glutpakt", + "glutpakt", + "glutpakt", + "glutpakt", + "wurzelbund" + ], + "index": 11, + "normal": { + "boss": [ + "5:failed:", + "6:failed:", + "7:failed:", + "8:failed:" + ], + "hero_levels": [ + 0, + 0, + 0, + 3, + 2, + 2 + ], + "lane_losses_at_end": [ + 1, + 0 + ], + "max_combat": 87.7999999999972, + "result": "rot", + "rounds": 8, + "timeouts": 0, + "total_gold_curve": [ + 853, + 770, + 1015, + 907, + 1198, + 1158, + 1540, + 711 + ] + }, + "profiles": [ + "bossjagd", + "fernkampf", + "fernkampf", + "teamhilfe", + "verteidigung", + "teamhilfe" + ], + "seed": 1011, + "swapped": { + "boss": [ + "5:failed:", + "6:failed:", + "7:failed:", + "8:failed:" + ], + "hero_levels": [ + 3, + 2, + 2, + 0, + 0, + 0 + ], + "lane_losses_at_end": [ + 0, + 1 + ], + "max_combat": 87.7999999999972, + "result": "blau", + "rounds": 8, + "timeouts": 0, + "total_gold_curve": [ + 853, + 770, + 1015, + 907, + 1198, + 1158, + 1540, + 711 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "glutpakt", + "glutpakt", + "glutpakt", + "glutpakt" + ], + "index": 12, + "normal": { + "boss": [], + "hero_levels": [ + 3, + 3, + 2, + 2, + 2, + 3 + ], + "lane_losses_at_end": [ + 1, + 1 + ], + "max_combat": 86.8999999999972, + "result": "blau", + "rounds": 8, + "timeouts": 0, + "total_gold_curve": [ + 903, + 710, + 1020, + 1002, + 1298, + 888, + 1781, + 1058 + ] + }, + "profiles": [ + "fernkampf", + "fernkampf", + "teamhilfe", + "teamhilfe", + "ausgewogen", + "fernkampf" + ], + "seed": 1012, + "swapped": { + "boss": [], + "hero_levels": [ + 2, + 2, + 0, + 3, + 3, + 3 + ], + "lane_losses_at_end": [ + 1, + 1 + ], + "max_combat": 100.449999999996, + "result": "rot", + "rounds": 9, + "timeouts": 0, + "total_gold_curve": [ + 903, + 710, + 1020, + 852, + 1233, + 1093, + 1204, + 1649, + 800 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "wurzelbund", + "wurzelbund", + "glutpakt", + "wurzelbund", + "glutpakt" + ], + "index": 13, + "normal": { + "boss": [], + "hero_levels": [ + 0, + 0, + 0, + 6, + 5, + 5 + ], + "lane_losses_at_end": [ + 3, + 0 + ], + "max_combat": 91.7499999999969, + "result": "rot", + "rounds": 12, + "timeouts": 0, + "total_gold_curve": [ + 843, + 690, + 890, + 932, + 923, + 1003, + 1165, + 1075, + 1353, + 1595, + 1793, + 1474 + ] + }, + "profiles": [ + "teamhilfe", + "druck", + "fernkampf", + "fernkampf", + "druck", + "ausgewogen" + ], + "seed": 1013, + "swapped": { + "boss": [], + "hero_levels": [ + 6, + 5, + 5, + 0, + 0, + 0 + ], + "lane_losses_at_end": [ + 0, + 3 + ], + "max_combat": 91.7499999999969, + "result": "blau", + "rounds": 12, + "timeouts": 0, + "total_gold_curve": [ + 843, + 690, + 890, + 932, + 923, + 1003, + 1165, + 1075, + 1353, + 1595, + 1793, + 1474 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "glutpakt", + "glutpakt", + "wurzelbund" + ], + "index": 14, + "normal": { + "boss": [ + "5:won:1", + "6:deployed:1" + ], + "hero_levels": [ + 4, + 5, + 5, + 2, + 3, + 3 + ], + "lane_losses_at_end": [ + 1, + 3 + ], + "max_combat": 121.649999999995, + "result": "blau", + "rounds": 8, + "timeouts": 0, + "total_gold_curve": [ + 803, + 810, + 920, + 847, + 1213, + 2248, + 1640, + 1393 + ] + }, + "profiles": [ + "bossjagd", + "druck", + "fernkampf", + "bossjagd", + "bossjagd", + "bossjagd" + ], + "seed": 1014, + "swapped": { + "boss": [ + "5:won:0", + "6:deployed:0" + ], + "hero_levels": [ + 2, + 3, + 3, + 4, + 5, + 5 + ], + "lane_losses_at_end": [ + 3, + 1 + ], + "max_combat": 121.649999999995, + "result": "rot", + "rounds": 8, + "timeouts": 0, + "total_gold_curve": [ + 803, + 810, + 920, + 847, + 1213, + 2248, + 1640, + 1393 + ] + } + }, + { + "bunds": [ + "glutpakt", + "wurzelbund", + "glutpakt", + "wurzelbund", + "glutpakt", + "glutpakt" + ], + "index": 15, + "normal": { + "boss": [], + "hero_levels": [ + 6, + 5, + 0, + 6, + 7, + 6 + ], + "lane_losses_at_end": [ + 2, + 1 + ], + "max_combat": 155.450000000001, + "result": "rot", + "rounds": 13, + "timeouts": 0, + "total_gold_curve": [ + 898, + 705, + 960, + 972, + 1008, + 1033, + 1140, + 1270, + 1383, + 1700, + 1753, + 1502, + 1700 + ] + }, + "profiles": [ + "fernkampf", + "fernkampf", + "verteidigung", + "druck", + "fernkampf", + "ausgewogen" + ], + "seed": 1015, + "swapped": { + "boss": [], + "hero_levels": [ + 6, + 7, + 6, + 6, + 5, + 0 + ], + "lane_losses_at_end": [ + 1, + 2 + ], + "max_combat": 155.450000000001, + "result": "blau", + "rounds": 13, + "timeouts": 0, + "total_gold_curve": [ + 898, + 705, + 960, + 972, + 1008, + 1033, + 1140, + 1270, + 1383, + 1700, + 1753, + 1502, + 1700 + ] + } + }, + { + "bunds": [ + "glutpakt", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt", + "glutpakt" + ], + "index": 16, + "normal": { + "boss": [], + "hero_levels": [ + 3, + 0, + 2, + 7, + 5, + 6 + ], + "lane_losses_at_end": [ + 2, + 0 + ], + "max_combat": 169.300000000004, + "result": "rot", + "rounds": 14, + "timeouts": 0, + "total_gold_curve": [ + 758, + 825, + 1010, + 1012, + 993, + 1003, + 1100, + 1100, + 1143, + 1350, + 1498, + 1587, + 2283, + 1370 + ] + }, + "profiles": [ + "teamhilfe", + "verteidigung", + "verteidigung", + "teamhilfe", + "verteidigung", + "ausgewogen" + ], + "seed": 1016, + "swapped": { + "boss": [], + "hero_levels": [ + 7, + 5, + 6, + 3, + 0, + 2 + ], + "lane_losses_at_end": [ + 0, + 2 + ], + "max_combat": 169.300000000004, + "result": "blau", + "rounds": 14, + "timeouts": 0, + "total_gold_curve": [ + 758, + 825, + 1010, + 1012, + 993, + 1003, + 1100, + 1100, + 1143, + 1350, + 1498, + 1587, + 2283, + 1370 + ] + } + }, + { + "bunds": [ + "glutpakt", + "glutpakt", + "glutpakt", + "wurzelbund", + "glutpakt", + "wurzelbund" + ], + "index": 17, + "normal": { + "boss": [ + "5:failed:", + "6:failed:", + "7:failed:", + "8:won:1", + "9:deployed:1" + ], + "hero_levels": [ + 4, + 4, + 5, + 6, + 3, + 5 + ], + "lane_losses_at_end": [ + 1, + 2 + ], + "max_combat": 116.049999999996, + "result": "blau", + "rounds": 10, + "timeouts": 0, + "total_gold_curve": [ + 893, + 720, + 950, + 952, + 1203, + 958, + 1579, + 1529, + 2847, + 1113 + ] + }, + "profiles": [ + "fernkampf", + "verteidigung", + "fernkampf", + "druck", + "fernkampf", + "bossjagd" + ], + "seed": 1017, + "swapped": { + "boss": [ + "5:failed:", + "6:failed:", + "7:failed:", + "8:won:0", + "9:deployed:0" + ], + "hero_levels": [ + 6, + 3, + 5, + 4, + 4, + 5 + ], + "lane_losses_at_end": [ + 2, + 1 + ], + "max_combat": 116.049999999996, + "result": "rot", + "rounds": 10, + "timeouts": 0, + "total_gold_curve": [ + 893, + 720, + 950, + 952, + 1203, + 958, + 1579, + 1529, + 2847, + 1113 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt", + "glutpakt" + ], + "index": 18, + "normal": { + "boss": [ + "5:failed:", + "6:failed:", + "7:failed:", + "8:won:0" + ], + "hero_levels": [ + 0, + 0, + 3, + 3, + 2, + 3 + ], + "lane_losses_at_end": [ + 2, + 0 + ], + "max_combat": 83.5499999999974, + "result": "rot", + "rounds": 8, + "timeouts": 0, + "total_gold_curve": [ + 753, + 780, + 890, + 927, + 1073, + 1128, + 1805, + 958 + ] + }, + "profiles": [ + "verteidigung", + "ausgewogen", + "bossjagd", + "druck", + "teamhilfe", + "druck" + ], + "seed": 1018, + "swapped": { + "boss": [ + "5:failed:", + "6:failed:", + "7:failed:", + "8:won:1" + ], + "hero_levels": [ + 3, + 2, + 3, + 0, + 0, + 3 + ], + "lane_losses_at_end": [ + 0, + 2 + ], + "max_combat": 83.5499999999974, + "result": "blau", + "rounds": 8, + "timeouts": 0, + "total_gold_curve": [ + 753, + 780, + 890, + 927, + 1073, + 1128, + 1805, + 958 + ] + } + }, + { + "bunds": [ + "glutpakt", + "glutpakt", + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund" + ], + "index": 19, + "normal": { + "boss": [ + "5:failed:", + "6:failed:", + "7:failed:" + ], + "hero_levels": [ + 0, + 0, + 2, + 0, + 0, + 0 + ], + "lane_losses_at_end": [ + 1, + 1 + ], + "max_combat": 94.0499999999968, + "result": "blau", + "rounds": 7, + "timeouts": 0, + "total_gold_curve": [ + 798, + 770, + 940, + 997, + 1203, + 1418, + 1254 + ] + }, + "profiles": [ + "druck", + "teamhilfe", + "teamhilfe", + "fernkampf", + "verteidigung", + "bossjagd" + ], + "seed": 1019, + "swapped": { + "boss": [ + "5:failed:", + "6:failed:", + "7:failed:" + ], + "hero_levels": [ + 0, + 0, + 0, + 0, + 0, + 2 + ], + "lane_losses_at_end": [ + 1, + 1 + ], + "max_combat": 94.0499999999968, + "result": "rot", + "rounds": 7, + "timeouts": 0, + "total_gold_curve": [ + 798, + 770, + 940, + 997, + 1203, + 1418, + 1254 + ] + } + } +] \ No newline at end of file diff --git a/docs/luneraria/logs/m3/balance-before.log b/docs/luneraria/logs/m3/balance-before.log new file mode 100644 index 0000000..e4867e8 --- /dev/null +++ b/docs/luneraria/logs/m3/balance-before.log @@ -0,0 +1,26 @@ +Godot Engine v4.7.2.stable.fedora.ed1daf0bf - https://godotengine.org + +BALANCE 00 ["ausgewogen", "druck", "teamhilfe"] | ["teamhilfe", "verteidigung", "ausgewogen"] -> blau (7) / swapped rot (8) +BALANCE 01 ["bossjagd", "bossjagd", "teamhilfe"] | ["fernkampf", "teamhilfe", "fernkampf"] -> rot (8) / swapped blau (8) +BALANCE 02 ["bossjagd", "ausgewogen", "teamhilfe"] | ["druck", "ausgewogen", "verteidigung"] -> rot (9) / swapped blau (9) +BALANCE 03 ["fernkampf", "druck", "ausgewogen"] | ["druck", "fernkampf", "verteidigung"] -> blau (13) / swapped rot (13) +BALANCE 04 ["ausgewogen", "teamhilfe", "fernkampf"] | ["ausgewogen", "verteidigung", "fernkampf"] -> blau (9) / swapped rot (9) +BALANCE 05 ["teamhilfe", "fernkampf", "verteidigung"] | ["fernkampf", "bossjagd", "bossjagd"] -> blau (8) / swapped rot (8) +BALANCE 06 ["bossjagd", "ausgewogen", "teamhilfe"] | ["fernkampf", "teamhilfe", "ausgewogen"] -> rot (7) / swapped blau (8) +BALANCE 07 ["ausgewogen", "ausgewogen", "ausgewogen"] | ["teamhilfe", "teamhilfe", "ausgewogen"] -> rot (12) / swapped blau (12) +BALANCE 08 ["teamhilfe", "ausgewogen", "bossjagd"] | ["druck", "ausgewogen", "druck"] -> rot (10) / swapped blau (10) +BALANCE 09 ["verteidigung", "druck", "druck"] | ["bossjagd", "ausgewogen", "teamhilfe"] -> blau (9) / swapped rot (9) +BALANCE 10 ["druck", "ausgewogen", "ausgewogen"] | ["teamhilfe", "bossjagd", "ausgewogen"] -> blau (9) / swapped rot (9) +BALANCE 11 ["bossjagd", "fernkampf", "fernkampf"] | ["teamhilfe", "verteidigung", "teamhilfe"] -> rot (8) / swapped blau (8) +BALANCE 12 ["fernkampf", "fernkampf", "teamhilfe"] | ["teamhilfe", "ausgewogen", "fernkampf"] -> blau (8) / swapped rot (9) +BALANCE 13 ["teamhilfe", "druck", "fernkampf"] | ["fernkampf", "druck", "ausgewogen"] -> rot (12) / swapped blau (12) +BALANCE 14 ["bossjagd", "druck", "fernkampf"] | ["bossjagd", "bossjagd", "bossjagd"] -> blau (8) / swapped rot (8) +BALANCE 15 ["fernkampf", "fernkampf", "verteidigung"] | ["druck", "fernkampf", "ausgewogen"] -> rot (13) / swapped blau (13) +BALANCE 16 ["teamhilfe", "verteidigung", "verteidigung"] | ["teamhilfe", "verteidigung", "ausgewogen"] -> rot (14) / swapped blau (14) +BALANCE 17 ["fernkampf", "verteidigung", "fernkampf"] | ["druck", "fernkampf", "bossjagd"] -> blau (10) / swapped rot (10) +BALANCE 18 ["verteidigung", "ausgewogen", "bossjagd"] | ["druck", "teamhilfe", "druck"] -> rot (8) / swapped blau (8) +BALANCE 19 ["druck", "teamhilfe", "teamhilfe"] | ["fernkampf", "verteidigung", "bossjagd"] -> blau (7) / swapped rot (7) +WARNING: 16 ObjectDB instances were leaked at exit (run with `--verbose` for details). + at: cleanup (core/object/object.cpp:2536) +ERROR: 1 resources still in use at exit (run with --verbose for details). + at: clear (core/io/resource.cpp:822) diff --git a/docs/luneraria/logs/m3/balance-step1-summary.txt b/docs/luneraria/logs/m3/balance-step1-summary.txt new file mode 100644 index 0000000..d8980ec --- /dev/null +++ b/docs/luneraria/logs/m3/balance-step1-summary.txt @@ -0,0 +1,6 @@ +Partien 40 · Ergebnisse {'blau': 19, 'rot': 21} · Testlimit 0 · Zeitfenster-Kämpfe 1 · längster Kampf 180 s +Wellen bis Ende: min 7 · median 12 · max 14 +Seitentausch: dieselbe Zusammensetzung gewinnt beide Male in 19/20 Paaren +Profile (Siege/Einsätze): {'ausgewogen': '26/48 (54 %)', 'bossjagd': '9/34 (26 %)', 'druck': '22/34 (65 %)', 'fernkampf': '29/48 (60 %)', 'teamhilfe': '28/48 (58 %)', 'verteidigung': '6/28 (21 %)'} +Bünde: {'glutpakt': '69/124 (56 %)', 'wurzelbund': '51/116 (44 %)'} +Boss: {} · Heldenstufen am Ende (Median): 5 diff --git a/docs/luneraria/logs/m3/balance-step1.json b/docs/luneraria/logs/m3/balance-step1.json new file mode 100644 index 0000000..4afcf2a --- /dev/null +++ b/docs/luneraria/logs/m3/balance-step1.json @@ -0,0 +1,1673 @@ +[ + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "wurzelbund", + "wurzelbund" + ], + "index": 0, + "normal": { + "boss": [], + "hero_levels": [ + 3, + 2, + 2, + 0, + 2, + 0 + ], + "lane_losses_at_end": [ + 1, + 1 + ], + "max_combat": 180.000000000007, + "result": "blau", + "rounds": 7, + "timeouts": 1, + "total_gold_curve": [ + 743, + 745, + 1055, + 1242, + 1023, + 1788, + 458 + ] + }, + "profiles": [ + "ausgewogen", + "druck", + "teamhilfe", + "teamhilfe", + "verteidigung", + "ausgewogen" + ], + "seed": 1000, + "swapped": { + "boss": [], + "hero_levels": [ + 2, + 2, + 2, + 3, + 2, + 2 + ], + "lane_losses_at_end": [ + 1, + 1 + ], + "max_combat": 102.599999999996, + "result": "rot", + "rounds": 8, + "timeouts": 0, + "total_gold_curve": [ + 743, + 745, + 905, + 972, + 1293, + 1048, + 1640, + 816 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "wurzelbund", + "glutpakt" + ], + "index": 1, + "normal": { + "boss": [], + "hero_levels": [ + 0, + 0, + 0, + 3, + 5, + 3 + ], + "lane_losses_at_end": [ + 2, + 0 + ], + "max_combat": 76.8499999999978, + "result": "rot", + "rounds": 10, + "timeouts": 0, + "total_gold_curve": [ + 843, + 750, + 970, + 812, + 1168, + 938, + 1387, + 1297, + 1450, + 1166 + ] + }, + "profiles": [ + "bossjagd", + "bossjagd", + "teamhilfe", + "fernkampf", + "teamhilfe", + "fernkampf" + ], + "seed": 1001, + "swapped": { + "boss": [], + "hero_levels": [ + 6, + 3, + 5, + 3, + 0, + 0 + ], + "lane_losses_at_end": [ + 1, + 2 + ], + "max_combat": 136.049999999997, + "result": "blau", + "rounds": 9, + "timeouts": 0, + "total_gold_curve": [ + 843, + 750, + 970, + 812, + 1018, + 1293, + 1450, + 1460, + 1111 + ] + } + }, + { + "bunds": [ + "glutpakt", + "wurzelbund", + "wurzelbund", + "wurzelbund", + "glutpakt", + "glutpakt" + ], + "index": 2, + "normal": { + "boss": [], + "hero_levels": [ + 4, + 4, + 0, + 0, + 0, + 4 + ], + "lane_losses_at_end": [ + 1, + 1 + ], + "max_combat": 100.199999999996, + "result": "blau", + "rounds": 9, + "timeouts": 0, + "total_gold_curve": [ + 753, + 780, + 930, + 962, + 988, + 1273, + 1530, + 1335, + 837 + ] + }, + "profiles": [ + "bossjagd", + "ausgewogen", + "teamhilfe", + "druck", + "ausgewogen", + "verteidigung" + ], + "seed": 1002, + "swapped": { + "boss": [], + "hero_levels": [ + 0, + 0, + 4, + 4, + 4, + 0 + ], + "lane_losses_at_end": [ + 1, + 1 + ], + "max_combat": 100.199999999996, + "result": "rot", + "rounds": 9, + "timeouts": 0, + "total_gold_curve": [ + 753, + 780, + 930, + 962, + 988, + 1273, + 1530, + 1335, + 837 + ] + } + }, + { + "bunds": [ + "glutpakt", + "wurzelbund", + "wurzelbund", + "wurzelbund", + "wurzelbund", + "wurzelbund" + ], + "index": 3, + "normal": { + "boss": [], + "hero_levels": [ + 7, + 7, + 4, + 6, + 0, + 0 + ], + "lane_losses_at_end": [ + 0, + 3 + ], + "max_combat": 110.899999999996, + "result": "blau", + "rounds": 13, + "timeouts": 0, + "total_gold_curve": [ + 838, + 695, + 875, + 967, + 978, + 968, + 1180, + 1370, + 1173, + 1745, + 1803, + 1867, + 1635 + ] + }, + "profiles": [ + "fernkampf", + "druck", + "ausgewogen", + "druck", + "fernkampf", + "verteidigung" + ], + "seed": 1003, + "swapped": { + "boss": [], + "hero_levels": [ + 6, + 0, + 0, + 7, + 7, + 4 + ], + "lane_losses_at_end": [ + 3, + 0 + ], + "max_combat": 110.899999999996, + "result": "rot", + "rounds": 13, + "timeouts": 0, + "total_gold_curve": [ + 838, + 695, + 875, + 967, + 978, + 968, + 1180, + 1370, + 1173, + 1745, + 1803, + 1867, + 1635 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "wurzelbund", + "wurzelbund", + "glutpakt", + "glutpakt", + "glutpakt" + ], + "index": 4, + "normal": { + "boss": [], + "hero_levels": [ + 0, + 0, + 3, + 0, + 0, + 0 + ], + "lane_losses_at_end": [ + 1, + 1 + ], + "max_combat": 108.399999999996, + "result": "blau", + "rounds": 9, + "timeouts": 0, + "total_gold_curve": [ + 848, + 725, + 985, + 897, + 1148, + 1203, + 1255, + 1350, + 980 + ] + }, + "profiles": [ + "ausgewogen", + "teamhilfe", + "fernkampf", + "ausgewogen", + "verteidigung", + "fernkampf" + ], + "seed": 1004, + "swapped": { + "boss": [], + "hero_levels": [ + 0, + 0, + 0, + 0, + 0, + 3 + ], + "lane_losses_at_end": [ + 1, + 1 + ], + "max_combat": 108.399999999996, + "result": "rot", + "rounds": 9, + "timeouts": 0, + "total_gold_curve": [ + 848, + 725, + 985, + 897, + 1148, + 1203, + 1255, + 1350, + 980 + ] + } + }, + { + "bunds": [ + "glutpakt", + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt" + ], + "index": 5, + "normal": { + "boss": [], + "hero_levels": [ + 5, + 5, + 5, + 4, + 3, + 3 + ], + "lane_losses_at_end": [ + 0, + 3 + ], + "max_combat": 111.749999999996, + "result": "blau", + "rounds": 12, + "timeouts": 0, + "total_gold_curve": [ + 853, + 785, + 995, + 907, + 1028, + 1038, + 1115, + 1310, + 1248, + 1930, + 1853, + 1529 + ] + }, + "profiles": [ + "teamhilfe", + "fernkampf", + "verteidigung", + "fernkampf", + "bossjagd", + "bossjagd" + ], + "seed": 1005, + "swapped": { + "boss": [], + "hero_levels": [ + 4, + 3, + 3, + 5, + 5, + 5 + ], + "lane_losses_at_end": [ + 3, + 0 + ], + "max_combat": 111.749999999996, + "result": "rot", + "rounds": 12, + "timeouts": 0, + "total_gold_curve": [ + 853, + 785, + 995, + 907, + 1028, + 1038, + 1115, + 1310, + 1248, + 1930, + 1853, + 1529 + ] + } + }, + { + "bunds": [ + "glutpakt", + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt" + ], + "index": 6, + "normal": { + "boss": [], + "hero_levels": [ + 0, + 4, + 0, + 5, + 3, + 4 + ], + "lane_losses_at_end": [ + 2, + 1 + ], + "max_combat": 87.6999999999972, + "result": "rot", + "rounds": 9, + "timeouts": 0, + "total_gold_curve": [ + 803, + 755, + 980, + 992, + 1158, + 1103, + 1685, + 1100, + 1372 + ] + }, + "profiles": [ + "bossjagd", + "ausgewogen", + "teamhilfe", + "fernkampf", + "teamhilfe", + "ausgewogen" + ], + "seed": 1006, + "swapped": { + "boss": [], + "hero_levels": [ + 2, + 4, + 3, + 3, + 4, + 2 + ], + "lane_losses_at_end": [ + 1, + 1 + ], + "max_combat": 82.4999999999975, + "result": "rot", + "rounds": 9, + "timeouts": 0, + "total_gold_curve": [ + 803, + 755, + 980, + 842, + 1098, + 1063, + 1470, + 1625, + 1002 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt", + "glutpakt" + ], + "index": 7, + "normal": { + "boss": [], + "hero_levels": [ + 0, + 0, + 0, + 6, + 5, + 5 + ], + "lane_losses_at_end": [ + 2, + 0 + ], + "max_combat": 112.249999999996, + "result": "rot", + "rounds": 12, + "timeouts": 0, + "total_gold_curve": [ + 753, + 740, + 965, + 837, + 878, + 933, + 1190, + 1035, + 1223, + 1680, + 1633, + 1484 + ] + }, + "profiles": [ + "ausgewogen", + "ausgewogen", + "ausgewogen", + "teamhilfe", + "teamhilfe", + "ausgewogen" + ], + "seed": 1007, + "swapped": { + "boss": [], + "hero_levels": [ + 6, + 5, + 5, + 0, + 0, + 0 + ], + "lane_losses_at_end": [ + 0, + 2 + ], + "max_combat": 112.249999999996, + "result": "blau", + "rounds": 12, + "timeouts": 0, + "total_gold_curve": [ + 753, + 740, + 965, + 837, + 878, + 933, + 1190, + 1035, + 1223, + 1680, + 1633, + 1484 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "wurzelbund", + "wurzelbund", + "glutpakt", + "glutpakt", + "glutpakt" + ], + "index": 8, + "normal": { + "boss": [], + "hero_levels": [ + 0, + 2, + 0, + 7, + 6, + 7 + ], + "lane_losses_at_end": [ + 3, + 0 + ], + "max_combat": 93.9499999999968, + "result": "rot", + "rounds": 13, + "timeouts": 0, + "total_gold_curve": [ + 753, + 755, + 875, + 807, + 958, + 1073, + 1100, + 1050, + 1303, + 1540, + 1673, + 2047, + 1492 + ] + }, + "profiles": [ + "teamhilfe", + "ausgewogen", + "bossjagd", + "druck", + "ausgewogen", + "druck" + ], + "seed": 1008, + "swapped": { + "boss": [], + "hero_levels": [ + 7, + 6, + 7, + 0, + 2, + 0 + ], + "lane_losses_at_end": [ + 0, + 3 + ], + "max_combat": 93.9499999999968, + "result": "blau", + "rounds": 13, + "timeouts": 0, + "total_gold_curve": [ + 753, + 755, + 875, + 807, + 958, + 1073, + 1100, + 1050, + 1303, + 1540, + 1673, + 2047, + 1492 + ] + } + }, + { + "bunds": [ + "glutpakt", + "wurzelbund", + "glutpakt", + "glutpakt", + "wurzelbund", + "glutpakt" + ], + "index": 9, + "normal": { + "boss": [], + "hero_levels": [ + 0, + 0, + 5, + 0, + 4, + 0 + ], + "lane_losses_at_end": [ + 1, + 0 + ], + "max_combat": 147.799999999999, + "result": "rot", + "rounds": 9, + "timeouts": 0, + "total_gold_curve": [ + 758, + 790, + 905, + 952, + 1008, + 1338, + 1310, + 1120, + 1376 + ] + }, + "profiles": [ + "verteidigung", + "druck", + "druck", + "bossjagd", + "ausgewogen", + "teamhilfe" + ], + "seed": 1009, + "swapped": { + "boss": [], + "hero_levels": [ + 0, + 4, + 0, + 0, + 0, + 5 + ], + "lane_losses_at_end": [ + 0, + 1 + ], + "max_combat": 147.799999999999, + "result": "blau", + "rounds": 9, + "timeouts": 0, + "total_gold_curve": [ + 758, + 790, + 905, + 952, + 1008, + 1338, + 1310, + 1120, + 1376 + ] + } + }, + { + "bunds": [ + "glutpakt", + "wurzelbund", + "wurzelbund", + "wurzelbund", + "glutpakt", + "glutpakt" + ], + "index": 10, + "normal": { + "boss": [], + "hero_levels": [ + 7, + 5, + 6, + 0, + 2, + 0 + ], + "lane_losses_at_end": [ + 0, + 2 + ], + "max_combat": 98.1499999999966, + "result": "blau", + "rounds": 13, + "timeouts": 0, + "total_gold_curve": [ + 753, + 755, + 915, + 832, + 983, + 968, + 1040, + 1020, + 1148, + 1530, + 1703, + 2107, + 1332 + ] + }, + "profiles": [ + "druck", + "ausgewogen", + "ausgewogen", + "teamhilfe", + "bossjagd", + "ausgewogen" + ], + "seed": 1010, + "swapped": { + "boss": [], + "hero_levels": [ + 0, + 2, + 0, + 7, + 5, + 6 + ], + "lane_losses_at_end": [ + 2, + 0 + ], + "max_combat": 98.1499999999966, + "result": "rot", + "rounds": 13, + "timeouts": 0, + "total_gold_curve": [ + 753, + 755, + 915, + 832, + 983, + 968, + 1040, + 1020, + 1148, + 1530, + 1703, + 2107, + 1332 + ] + } + }, + { + "bunds": [ + "glutpakt", + "glutpakt", + "glutpakt", + "glutpakt", + "glutpakt", + "wurzelbund" + ], + "index": 11, + "normal": { + "boss": [], + "hero_levels": [ + 4, + 6, + 6, + 3, + 0, + 0 + ], + "lane_losses_at_end": [ + 1, + 2 + ], + "max_combat": 87.2999999999972, + "result": "blau", + "rounds": 11, + "timeouts": 0, + "total_gold_curve": [ + 853, + 770, + 1015, + 907, + 1048, + 1013, + 1080, + 1565, + 1413, + 2000, + 927 + ] + }, + "profiles": [ + "bossjagd", + "fernkampf", + "fernkampf", + "teamhilfe", + "verteidigung", + "teamhilfe" + ], + "seed": 1011, + "swapped": { + "boss": [], + "hero_levels": [ + 3, + 0, + 0, + 4, + 6, + 6 + ], + "lane_losses_at_end": [ + 2, + 1 + ], + "max_combat": 87.2999999999972, + "result": "rot", + "rounds": 11, + "timeouts": 0, + "total_gold_curve": [ + 853, + 770, + 1015, + 907, + 1048, + 1013, + 1080, + 1565, + 1413, + 2000, + 927 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "glutpakt", + "glutpakt", + "glutpakt", + "glutpakt" + ], + "index": 12, + "normal": { + "boss": [], + "hero_levels": [ + 3, + 3, + 2, + 2, + 2, + 3 + ], + "lane_losses_at_end": [ + 1, + 1 + ], + "max_combat": 86.8999999999972, + "result": "blau", + "rounds": 8, + "timeouts": 0, + "total_gold_curve": [ + 903, + 710, + 1020, + 1002, + 1298, + 888, + 1781, + 1058 + ] + }, + "profiles": [ + "fernkampf", + "fernkampf", + "teamhilfe", + "teamhilfe", + "ausgewogen", + "fernkampf" + ], + "seed": 1012, + "swapped": { + "boss": [], + "hero_levels": [ + 2, + 2, + 0, + 3, + 3, + 3 + ], + "lane_losses_at_end": [ + 1, + 1 + ], + "max_combat": 100.449999999996, + "result": "rot", + "rounds": 9, + "timeouts": 0, + "total_gold_curve": [ + 903, + 710, + 1020, + 852, + 1233, + 1093, + 1204, + 1649, + 800 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "wurzelbund", + "wurzelbund", + "glutpakt", + "wurzelbund", + "glutpakt" + ], + "index": 13, + "normal": { + "boss": [], + "hero_levels": [ + 0, + 0, + 0, + 6, + 5, + 5 + ], + "lane_losses_at_end": [ + 3, + 0 + ], + "max_combat": 91.7499999999969, + "result": "rot", + "rounds": 12, + "timeouts": 0, + "total_gold_curve": [ + 843, + 690, + 890, + 932, + 923, + 1003, + 1165, + 1075, + 1353, + 1595, + 1793, + 1474 + ] + }, + "profiles": [ + "teamhilfe", + "druck", + "fernkampf", + "fernkampf", + "druck", + "ausgewogen" + ], + "seed": 1013, + "swapped": { + "boss": [], + "hero_levels": [ + 6, + 5, + 5, + 0, + 0, + 0 + ], + "lane_losses_at_end": [ + 0, + 3 + ], + "max_combat": 91.7499999999969, + "result": "blau", + "rounds": 12, + "timeouts": 0, + "total_gold_curve": [ + 843, + 690, + 890, + 932, + 923, + 1003, + 1165, + 1075, + 1353, + 1595, + 1793, + 1474 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "glutpakt", + "glutpakt", + "wurzelbund" + ], + "index": 14, + "normal": { + "boss": [], + "hero_levels": [ + 5, + 5, + 5, + 0, + 0, + 3 + ], + "lane_losses_at_end": [ + 0, + 3 + ], + "max_combat": 117.249999999995, + "result": "blau", + "rounds": 12, + "timeouts": 0, + "total_gold_curve": [ + 803, + 810, + 920, + 847, + 1063, + 1098, + 1150, + 1300, + 1213, + 2015, + 1918, + 1177 + ] + }, + "profiles": [ + "bossjagd", + "druck", + "fernkampf", + "bossjagd", + "bossjagd", + "bossjagd" + ], + "seed": 1014, + "swapped": { + "boss": [], + "hero_levels": [ + 0, + 0, + 3, + 5, + 5, + 5 + ], + "lane_losses_at_end": [ + 3, + 0 + ], + "max_combat": 117.249999999995, + "result": "rot", + "rounds": 12, + "timeouts": 0, + "total_gold_curve": [ + 803, + 810, + 920, + 847, + 1063, + 1098, + 1150, + 1300, + 1213, + 2015, + 1918, + 1177 + ] + } + }, + { + "bunds": [ + "glutpakt", + "wurzelbund", + "glutpakt", + "wurzelbund", + "glutpakt", + "glutpakt" + ], + "index": 15, + "normal": { + "boss": [], + "hero_levels": [ + 6, + 5, + 0, + 6, + 7, + 6 + ], + "lane_losses_at_end": [ + 2, + 1 + ], + "max_combat": 155.450000000001, + "result": "rot", + "rounds": 13, + "timeouts": 0, + "total_gold_curve": [ + 898, + 705, + 960, + 972, + 1008, + 1033, + 1140, + 1270, + 1383, + 1700, + 1753, + 1502, + 1700 + ] + }, + "profiles": [ + "fernkampf", + "fernkampf", + "verteidigung", + "druck", + "fernkampf", + "ausgewogen" + ], + "seed": 1015, + "swapped": { + "boss": [], + "hero_levels": [ + 6, + 7, + 6, + 6, + 5, + 0 + ], + "lane_losses_at_end": [ + 1, + 2 + ], + "max_combat": 155.450000000001, + "result": "blau", + "rounds": 13, + "timeouts": 0, + "total_gold_curve": [ + 898, + 705, + 960, + 972, + 1008, + 1033, + 1140, + 1270, + 1383, + 1700, + 1753, + 1502, + 1700 + ] + } + }, + { + "bunds": [ + "glutpakt", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt", + "glutpakt" + ], + "index": 16, + "normal": { + "boss": [], + "hero_levels": [ + 3, + 0, + 2, + 7, + 5, + 6 + ], + "lane_losses_at_end": [ + 2, + 0 + ], + "max_combat": 169.300000000004, + "result": "rot", + "rounds": 14, + "timeouts": 0, + "total_gold_curve": [ + 758, + 825, + 1010, + 1012, + 993, + 1003, + 1100, + 1100, + 1143, + 1350, + 1498, + 1587, + 2283, + 1370 + ] + }, + "profiles": [ + "teamhilfe", + "verteidigung", + "verteidigung", + "teamhilfe", + "verteidigung", + "ausgewogen" + ], + "seed": 1016, + "swapped": { + "boss": [], + "hero_levels": [ + 7, + 5, + 6, + 3, + 0, + 2 + ], + "lane_losses_at_end": [ + 0, + 2 + ], + "max_combat": 169.300000000004, + "result": "blau", + "rounds": 14, + "timeouts": 0, + "total_gold_curve": [ + 758, + 825, + 1010, + 1012, + 993, + 1003, + 1100, + 1100, + 1143, + 1350, + 1498, + 1587, + 2283, + 1370 + ] + } + }, + { + "bunds": [ + "glutpakt", + "glutpakt", + "glutpakt", + "wurzelbund", + "glutpakt", + "wurzelbund" + ], + "index": 17, + "normal": { + "boss": [], + "hero_levels": [ + 9, + 6, + 8, + 8, + 7, + 7 + ], + "lane_losses_at_end": [ + 1, + 2 + ], + "max_combat": 91.6999999999969, + "result": "blau", + "rounds": 13, + "timeouts": 0, + "total_gold_curve": [ + 893, + 720, + 950, + 952, + 1053, + 1063, + 1320, + 1370, + 1368, + 1185, + 1838, + 1462, + 1575 + ] + }, + "profiles": [ + "fernkampf", + "verteidigung", + "fernkampf", + "druck", + "fernkampf", + "bossjagd" + ], + "seed": 1017, + "swapped": { + "boss": [], + "hero_levels": [ + 8, + 7, + 7, + 9, + 6, + 8 + ], + "lane_losses_at_end": [ + 2, + 1 + ], + "max_combat": 91.6999999999969, + "result": "rot", + "rounds": 13, + "timeouts": 0, + "total_gold_curve": [ + 893, + 720, + 950, + 952, + 1053, + 1063, + 1320, + 1370, + 1368, + 1185, + 1838, + 1462, + 1575 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt", + "glutpakt" + ], + "index": 18, + "normal": { + "boss": [], + "hero_levels": [ + 0, + 0, + 3, + 3, + 2, + 3 + ], + "lane_losses_at_end": [ + 1, + 1 + ], + "max_combat": 109.249999999996, + "result": "rot", + "rounds": 9, + "timeouts": 0, + "total_gold_curve": [ + 753, + 780, + 890, + 927, + 1073, + 978, + 1495, + 1470, + 1048 + ] + }, + "profiles": [ + "verteidigung", + "ausgewogen", + "bossjagd", + "druck", + "teamhilfe", + "druck" + ], + "seed": 1018, + "swapped": { + "boss": [], + "hero_levels": [ + 3, + 2, + 3, + 0, + 0, + 3 + ], + "lane_losses_at_end": [ + 1, + 1 + ], + "max_combat": 109.249999999996, + "result": "blau", + "rounds": 9, + "timeouts": 0, + "total_gold_curve": [ + 753, + 780, + 890, + 927, + 1073, + 978, + 1495, + 1470, + 1048 + ] + } + }, + { + "bunds": [ + "glutpakt", + "glutpakt", + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund" + ], + "index": 19, + "normal": { + "boss": [], + "hero_levels": [ + 0, + 0, + 2, + 2, + 0, + 2 + ], + "lane_losses_at_end": [ + 2, + 1 + ], + "max_combat": 90.899999999997, + "result": "blau", + "rounds": 7, + "timeouts": 0, + "total_gold_curve": [ + 798, + 770, + 940, + 997, + 1203, + 1568, + 1352 + ] + }, + "profiles": [ + "druck", + "teamhilfe", + "teamhilfe", + "fernkampf", + "verteidigung", + "bossjagd" + ], + "seed": 1019, + "swapped": { + "boss": [], + "hero_levels": [ + 2, + 0, + 2, + 0, + 0, + 2 + ], + "lane_losses_at_end": [ + 1, + 2 + ], + "max_combat": 90.899999999997, + "result": "rot", + "rounds": 7, + "timeouts": 0, + "total_gold_curve": [ + 798, + 770, + 940, + 997, + 1203, + 1568, + 1352 + ] + } + } +] \ No newline at end of file diff --git a/docs/luneraria/logs/m3/balance-step1.log b/docs/luneraria/logs/m3/balance-step1.log new file mode 100644 index 0000000..6db0c7a --- /dev/null +++ b/docs/luneraria/logs/m3/balance-step1.log @@ -0,0 +1,26 @@ +Godot Engine v4.7.2.stable.fedora.ed1daf0bf - https://godotengine.org + +BALANCE 00 ["ausgewogen", "druck", "teamhilfe"] | ["teamhilfe", "verteidigung", "ausgewogen"] -> blau (7) / swapped rot (8) +BALANCE 01 ["bossjagd", "bossjagd", "teamhilfe"] | ["fernkampf", "teamhilfe", "fernkampf"] -> rot (10) / swapped blau (9) +BALANCE 02 ["bossjagd", "ausgewogen", "teamhilfe"] | ["druck", "ausgewogen", "verteidigung"] -> blau (9) / swapped rot (9) +BALANCE 03 ["fernkampf", "druck", "ausgewogen"] | ["druck", "fernkampf", "verteidigung"] -> blau (13) / swapped rot (13) +BALANCE 04 ["ausgewogen", "teamhilfe", "fernkampf"] | ["ausgewogen", "verteidigung", "fernkampf"] -> blau (9) / swapped rot (9) +BALANCE 05 ["teamhilfe", "fernkampf", "verteidigung"] | ["fernkampf", "bossjagd", "bossjagd"] -> blau (12) / swapped rot (12) +BALANCE 06 ["bossjagd", "ausgewogen", "teamhilfe"] | ["fernkampf", "teamhilfe", "ausgewogen"] -> rot (9) / swapped rot (9) +BALANCE 07 ["ausgewogen", "ausgewogen", "ausgewogen"] | ["teamhilfe", "teamhilfe", "ausgewogen"] -> rot (12) / swapped blau (12) +BALANCE 08 ["teamhilfe", "ausgewogen", "bossjagd"] | ["druck", "ausgewogen", "druck"] -> rot (13) / swapped blau (13) +BALANCE 09 ["verteidigung", "druck", "druck"] | ["bossjagd", "ausgewogen", "teamhilfe"] -> rot (9) / swapped blau (9) +BALANCE 10 ["druck", "ausgewogen", "ausgewogen"] | ["teamhilfe", "bossjagd", "ausgewogen"] -> blau (13) / swapped rot (13) +BALANCE 11 ["bossjagd", "fernkampf", "fernkampf"] | ["teamhilfe", "verteidigung", "teamhilfe"] -> blau (11) / swapped rot (11) +BALANCE 12 ["fernkampf", "fernkampf", "teamhilfe"] | ["teamhilfe", "ausgewogen", "fernkampf"] -> blau (8) / swapped rot (9) +BALANCE 13 ["teamhilfe", "druck", "fernkampf"] | ["fernkampf", "druck", "ausgewogen"] -> rot (12) / swapped blau (12) +BALANCE 14 ["bossjagd", "druck", "fernkampf"] | ["bossjagd", "bossjagd", "bossjagd"] -> blau (12) / swapped rot (12) +BALANCE 15 ["fernkampf", "fernkampf", "verteidigung"] | ["druck", "fernkampf", "ausgewogen"] -> rot (13) / swapped blau (13) +BALANCE 16 ["teamhilfe", "verteidigung", "verteidigung"] | ["teamhilfe", "verteidigung", "ausgewogen"] -> rot (14) / swapped blau (14) +BALANCE 17 ["fernkampf", "verteidigung", "fernkampf"] | ["druck", "fernkampf", "bossjagd"] -> blau (13) / swapped rot (13) +BALANCE 18 ["verteidigung", "ausgewogen", "bossjagd"] | ["druck", "teamhilfe", "druck"] -> rot (9) / swapped blau (9) +BALANCE 19 ["druck", "teamhilfe", "teamhilfe"] | ["fernkampf", "verteidigung", "bossjagd"] -> blau (7) / swapped rot (7) +WARNING: 18 ObjectDB instances were leaked at exit (run with `--verbose` for details). + at: cleanup (core/object/object.cpp:2536) +ERROR: 1 resources still in use at exit (run with --verbose for details). + at: clear (core/io/resource.cpp:822) diff --git a/docs/luneraria/logs/m3/balance-step2-summary.txt b/docs/luneraria/logs/m3/balance-step2-summary.txt new file mode 100644 index 0000000..397003b --- /dev/null +++ b/docs/luneraria/logs/m3/balance-step2-summary.txt @@ -0,0 +1,6 @@ +Partien 40 · Ergebnisse {'blau': 20, 'rot': 20} · Testlimit 0 · Zeitfenster-Kämpfe 3 · längster Kampf 180 s +Wellen bis Ende: min 7 · median 10 · max 14 +Seitentausch: dieselbe Zusammensetzung gewinnt beide Male in 20/20 Paaren +Profile (Siege/Einsätze): {'ausgewogen': '26/48 (54 %)', 'bossjagd': '10/34 (29 %)', 'druck': '24/34 (71 %)', 'fernkampf': '28/48 (58 %)', 'teamhilfe': '28/48 (58 %)', 'verteidigung': '4/28 (14 %)'} +Bünde: {'glutpakt': '64/124 (52 %)', 'wurzelbund': '56/116 (48 %)'} +Boss: {'won': 21, 'deployed': 12, 'failed': 2} · Heldenstufen am Ende (Median): 3 diff --git a/docs/luneraria/logs/m3/balance-step2.json b/docs/luneraria/logs/m3/balance-step2.json new file mode 100644 index 0000000..ca192cc --- /dev/null +++ b/docs/luneraria/logs/m3/balance-step2.json @@ -0,0 +1,1725 @@ +[ + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "wurzelbund", + "wurzelbund" + ], + "index": 0, + "matchup": false, + "normal": { + "boss": [], + "hero_levels": [ + 3, + 2, + 2, + 0, + 2, + 0 + ], + "lane_losses_at_end": [ + 1, + 1 + ], + "max_combat": 180.000000000007, + "result": "blau", + "rounds": 7, + "timeouts": 1, + "total_gold_curve": [ + 743, + 745, + 1055, + 1242, + 1023, + 1788, + 458 + ] + }, + "profiles": [ + "ausgewogen", + "druck", + "teamhilfe", + "teamhilfe", + "verteidigung", + "ausgewogen" + ], + "seed": 1000, + "swapped": { + "boss": [], + "hero_levels": [ + 2, + 2, + 2, + 3, + 2, + 2 + ], + "lane_losses_at_end": [ + 1, + 1 + ], + "max_combat": 102.599999999996, + "result": "rot", + "rounds": 8, + "timeouts": 0, + "total_gold_curve": [ + 743, + 745, + 905, + 972, + 1293, + 1048, + 1640, + 816 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "wurzelbund", + "glutpakt" + ], + "index": 1, + "matchup": false, + "normal": { + "boss": [ + "9:won:0" + ], + "hero_levels": [ + 0, + 0, + 0, + 0, + 3, + 0 + ], + "lane_losses_at_end": [ + 2, + 0 + ], + "max_combat": 82.9499999999974, + "result": "rot", + "rounds": 9, + "timeouts": 0, + "total_gold_curve": [ + 843, + 750, + 970, + 812, + 1168, + 938, + 1387, + 1297, + 1244 + ] + }, + "profiles": [ + "bossjagd", + "bossjagd", + "teamhilfe", + "fernkampf", + "teamhilfe", + "fernkampf" + ], + "seed": 1001, + "swapped": { + "boss": [ + "8:won:1" + ], + "hero_levels": [ + 5, + 2, + 4, + 3, + 0, + 0 + ], + "lane_losses_at_end": [ + 1, + 2 + ], + "max_combat": 87.3499999999972, + "result": "blau", + "rounds": 8, + "timeouts": 0, + "total_gold_curve": [ + 843, + 750, + 970, + 812, + 1018, + 1293, + 1450, + 1386 + ] + } + }, + { + "bunds": [ + "glutpakt", + "wurzelbund", + "wurzelbund", + "wurzelbund", + "glutpakt", + "glutpakt" + ], + "index": 2, + "matchup": false, + "normal": { + "boss": [ + "9:won:0", + "10:deployed:0" + ], + "hero_levels": [ + 5, + 5, + 0, + 2, + 2, + 5 + ], + "lane_losses_at_end": [ + 2, + 1 + ], + "max_combat": 114.949999999996, + "result": "blau", + "rounds": 10, + "timeouts": 0, + "total_gold_curve": [ + 753, + 780, + 930, + 962, + 988, + 1273, + 1530, + 1335, + 1568, + 1270 + ] + }, + "profiles": [ + "bossjagd", + "ausgewogen", + "teamhilfe", + "druck", + "ausgewogen", + "verteidigung" + ], + "seed": 1002, + "swapped": { + "boss": [ + "9:won:1", + "10:deployed:1" + ], + "hero_levels": [ + 2, + 2, + 5, + 5, + 5, + 0 + ], + "lane_losses_at_end": [ + 1, + 2 + ], + "max_combat": 114.949999999996, + "result": "rot", + "rounds": 10, + "timeouts": 0, + "total_gold_curve": [ + 753, + 780, + 930, + 962, + 988, + 1273, + 1530, + 1335, + 1568, + 1270 + ] + } + }, + { + "bunds": [ + "glutpakt", + "wurzelbund", + "wurzelbund", + "wurzelbund", + "wurzelbund", + "wurzelbund" + ], + "index": 3, + "matchup": false, + "normal": { + "boss": [], + "hero_levels": [ + 7, + 7, + 4, + 6, + 0, + 0 + ], + "lane_losses_at_end": [ + 0, + 3 + ], + "max_combat": 110.899999999996, + "result": "blau", + "rounds": 13, + "timeouts": 0, + "total_gold_curve": [ + 838, + 695, + 875, + 967, + 978, + 968, + 1180, + 1370, + 1173, + 1745, + 1803, + 1867, + 1635 + ] + }, + "profiles": [ + "fernkampf", + "druck", + "ausgewogen", + "druck", + "fernkampf", + "verteidigung" + ], + "seed": 1003, + "swapped": { + "boss": [], + "hero_levels": [ + 6, + 0, + 0, + 7, + 7, + 4 + ], + "lane_losses_at_end": [ + 3, + 0 + ], + "max_combat": 110.899999999996, + "result": "rot", + "rounds": 13, + "timeouts": 0, + "total_gold_curve": [ + 838, + 695, + 875, + 967, + 978, + 968, + 1180, + 1370, + 1173, + 1745, + 1803, + 1867, + 1635 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "wurzelbund", + "wurzelbund", + "glutpakt", + "glutpakt", + "glutpakt" + ], + "index": 4, + "matchup": false, + "normal": { + "boss": [], + "hero_levels": [ + 0, + 0, + 3, + 0, + 0, + 0 + ], + "lane_losses_at_end": [ + 1, + 1 + ], + "max_combat": 108.399999999996, + "result": "blau", + "rounds": 9, + "timeouts": 0, + "total_gold_curve": [ + 848, + 725, + 985, + 897, + 1148, + 1203, + 1255, + 1350, + 980 + ] + }, + "profiles": [ + "ausgewogen", + "teamhilfe", + "fernkampf", + "ausgewogen", + "verteidigung", + "fernkampf" + ], + "seed": 1004, + "swapped": { + "boss": [], + "hero_levels": [ + 0, + 0, + 0, + 0, + 0, + 3 + ], + "lane_losses_at_end": [ + 1, + 1 + ], + "max_combat": 108.399999999996, + "result": "rot", + "rounds": 9, + "timeouts": 0, + "total_gold_curve": [ + 848, + 725, + 985, + 897, + 1148, + 1203, + 1255, + 1350, + 980 + ] + } + }, + { + "bunds": [ + "glutpakt", + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt" + ], + "index": 5, + "matchup": false, + "normal": { + "boss": [ + "9:won:1", + "10:deployed:1" + ], + "hero_levels": [ + 4, + 4, + 5, + 3, + 2, + 2 + ], + "lane_losses_at_end": [ + 1, + 2 + ], + "max_combat": 135.799999999997, + "result": "blau", + "rounds": 11, + "timeouts": 0, + "total_gold_curve": [ + 853, + 785, + 995, + 907, + 1028, + 1038, + 1115, + 1310, + 1458, + 3420, + 1472 + ] + }, + "profiles": [ + "teamhilfe", + "fernkampf", + "verteidigung", + "fernkampf", + "bossjagd", + "bossjagd" + ], + "seed": 1005, + "swapped": { + "boss": [ + "9:won:0", + "10:deployed:0" + ], + "hero_levels": [ + 3, + 2, + 2, + 4, + 4, + 5 + ], + "lane_losses_at_end": [ + 2, + 1 + ], + "max_combat": 135.799999999997, + "result": "rot", + "rounds": 11, + "timeouts": 0, + "total_gold_curve": [ + 853, + 785, + 995, + 907, + 1028, + 1038, + 1115, + 1310, + 1458, + 3420, + 1472 + ] + } + }, + { + "bunds": [ + "glutpakt", + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt" + ], + "index": 6, + "matchup": false, + "normal": { + "boss": [], + "hero_levels": [ + 0, + 4, + 0, + 5, + 3, + 4 + ], + "lane_losses_at_end": [ + 2, + 1 + ], + "max_combat": 87.6999999999972, + "result": "rot", + "rounds": 9, + "timeouts": 0, + "total_gold_curve": [ + 803, + 755, + 980, + 992, + 1158, + 1103, + 1685, + 1100, + 1372 + ] + }, + "profiles": [ + "bossjagd", + "ausgewogen", + "teamhilfe", + "fernkampf", + "teamhilfe", + "ausgewogen" + ], + "seed": 1006, + "swapped": { + "boss": [ + "9:won:1" + ], + "hero_levels": [ + 3, + 4, + 3, + 3, + 4, + 2 + ], + "lane_losses_at_end": [ + 1, + 1 + ], + "max_combat": 105.949999999996, + "result": "blau", + "rounds": 9, + "timeouts": 0, + "total_gold_curve": [ + 803, + 755, + 980, + 842, + 1098, + 1063, + 1470, + 1625, + 924 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt", + "glutpakt" + ], + "index": 7, + "matchup": false, + "normal": { + "boss": [], + "hero_levels": [ + 0, + 0, + 0, + 6, + 5, + 5 + ], + "lane_losses_at_end": [ + 2, + 0 + ], + "max_combat": 112.249999999996, + "result": "rot", + "rounds": 12, + "timeouts": 0, + "total_gold_curve": [ + 753, + 740, + 965, + 837, + 878, + 933, + 1190, + 1035, + 1223, + 1680, + 1633, + 1484 + ] + }, + "profiles": [ + "ausgewogen", + "ausgewogen", + "ausgewogen", + "teamhilfe", + "teamhilfe", + "ausgewogen" + ], + "seed": 1007, + "swapped": { + "boss": [], + "hero_levels": [ + 6, + 5, + 5, + 0, + 0, + 0 + ], + "lane_losses_at_end": [ + 0, + 2 + ], + "max_combat": 112.249999999996, + "result": "blau", + "rounds": 12, + "timeouts": 0, + "total_gold_curve": [ + 753, + 740, + 965, + 837, + 878, + 933, + 1190, + 1035, + 1223, + 1680, + 1633, + 1484 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "wurzelbund", + "wurzelbund", + "glutpakt", + "glutpakt", + "glutpakt" + ], + "index": 8, + "matchup": false, + "normal": { + "boss": [ + "9:won:0", + "10:deployed:0" + ], + "hero_levels": [ + 2, + 2, + 2, + 4, + 5, + 6 + ], + "lane_losses_at_end": [ + 3, + 1 + ], + "max_combat": 122.999999999995, + "result": "rot", + "rounds": 11, + "timeouts": 0, + "total_gold_curve": [ + 753, + 755, + 875, + 807, + 958, + 1073, + 1100, + 1050, + 1513, + 3190, + 1907 + ] + }, + "profiles": [ + "teamhilfe", + "ausgewogen", + "bossjagd", + "druck", + "ausgewogen", + "druck" + ], + "seed": 1008, + "swapped": { + "boss": [ + "9:won:1", + "10:deployed:1" + ], + "hero_levels": [ + 4, + 5, + 6, + 2, + 2, + 2 + ], + "lane_losses_at_end": [ + 1, + 3 + ], + "max_combat": 122.999999999995, + "result": "blau", + "rounds": 11, + "timeouts": 0, + "total_gold_curve": [ + 753, + 755, + 875, + 807, + 958, + 1073, + 1100, + 1050, + 1513, + 3190, + 1907 + ] + } + }, + { + "bunds": [ + "glutpakt", + "wurzelbund", + "glutpakt", + "glutpakt", + "wurzelbund", + "glutpakt" + ], + "index": 9, + "matchup": false, + "normal": { + "boss": [], + "hero_levels": [ + 0, + 0, + 5, + 0, + 4, + 0 + ], + "lane_losses_at_end": [ + 1, + 0 + ], + "max_combat": 147.799999999999, + "result": "rot", + "rounds": 9, + "timeouts": 0, + "total_gold_curve": [ + 758, + 790, + 905, + 952, + 1008, + 1338, + 1310, + 1120, + 1376 + ] + }, + "profiles": [ + "verteidigung", + "druck", + "druck", + "bossjagd", + "ausgewogen", + "teamhilfe" + ], + "seed": 1009, + "swapped": { + "boss": [], + "hero_levels": [ + 0, + 4, + 0, + 0, + 0, + 5 + ], + "lane_losses_at_end": [ + 0, + 1 + ], + "max_combat": 147.799999999999, + "result": "blau", + "rounds": 9, + "timeouts": 0, + "total_gold_curve": [ + 758, + 790, + 905, + 952, + 1008, + 1338, + 1310, + 1120, + 1376 + ] + } + }, + { + "bunds": [ + "glutpakt", + "wurzelbund", + "wurzelbund", + "wurzelbund", + "glutpakt", + "glutpakt" + ], + "index": 10, + "matchup": false, + "normal": { + "boss": [ + "10:won:1", + "11:deployed:1" + ], + "hero_levels": [ + 4, + 3, + 4, + 3, + 2, + 3 + ], + "lane_losses_at_end": [ + 1, + 2 + ], + "max_combat": 180.000000000007, + "result": "blau", + "rounds": 12, + "timeouts": 1, + "total_gold_curve": [ + 753, + 755, + 915, + 832, + 983, + 968, + 1040, + 1020, + 1148, + 1740, + 3469, + 2124 + ] + }, + "profiles": [ + "druck", + "ausgewogen", + "ausgewogen", + "teamhilfe", + "bossjagd", + "ausgewogen" + ], + "seed": 1010, + "swapped": { + "boss": [ + "10:won:0", + "11:deployed:0" + ], + "hero_levels": [ + 3, + 2, + 3, + 4, + 3, + 4 + ], + "lane_losses_at_end": [ + 2, + 1 + ], + "max_combat": 180.000000000007, + "result": "rot", + "rounds": 12, + "timeouts": 1, + "total_gold_curve": [ + 753, + 755, + 915, + 832, + 983, + 968, + 1040, + 1020, + 1148, + 1740, + 3469, + 2124 + ] + } + }, + { + "bunds": [ + "glutpakt", + "glutpakt", + "glutpakt", + "glutpakt", + "glutpakt", + "wurzelbund" + ], + "index": 11, + "matchup": false, + "normal": { + "boss": [ + "9:won:0", + "10:deployed:0" + ], + "hero_levels": [ + 3, + 5, + 5, + 3, + 2, + 3 + ], + "lane_losses_at_end": [ + 1, + 2 + ], + "max_combat": 87.0499999999972, + "result": "blau", + "rounds": 10, + "timeouts": 0, + "total_gold_curve": [ + 853, + 770, + 1015, + 907, + 1048, + 1013, + 1080, + 1565, + 2103, + 1070 + ] + }, + "profiles": [ + "bossjagd", + "fernkampf", + "fernkampf", + "teamhilfe", + "verteidigung", + "teamhilfe" + ], + "seed": 1011, + "swapped": { + "boss": [ + "9:won:1", + "10:deployed:1" + ], + "hero_levels": [ + 3, + 2, + 3, + 3, + 5, + 5 + ], + "lane_losses_at_end": [ + 2, + 1 + ], + "max_combat": 87.0499999999972, + "result": "rot", + "rounds": 10, + "timeouts": 0, + "total_gold_curve": [ + 853, + 770, + 1015, + 907, + 1048, + 1013, + 1080, + 1565, + 2103, + 1070 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "glutpakt", + "glutpakt", + "glutpakt", + "glutpakt" + ], + "index": 12, + "matchup": false, + "normal": { + "boss": [], + "hero_levels": [ + 3, + 3, + 2, + 2, + 2, + 3 + ], + "lane_losses_at_end": [ + 1, + 1 + ], + "max_combat": 86.8999999999972, + "result": "blau", + "rounds": 8, + "timeouts": 0, + "total_gold_curve": [ + 903, + 710, + 1020, + 1002, + 1298, + 888, + 1781, + 1058 + ] + }, + "profiles": [ + "fernkampf", + "fernkampf", + "teamhilfe", + "teamhilfe", + "ausgewogen", + "fernkampf" + ], + "seed": 1012, + "swapped": { + "boss": [], + "hero_levels": [ + 2, + 2, + 0, + 3, + 3, + 3 + ], + "lane_losses_at_end": [ + 1, + 1 + ], + "max_combat": 100.449999999996, + "result": "rot", + "rounds": 9, + "timeouts": 0, + "total_gold_curve": [ + 903, + 710, + 1020, + 852, + 1233, + 1093, + 1204, + 1649, + 800 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "wurzelbund", + "wurzelbund", + "glutpakt", + "wurzelbund", + "glutpakt" + ], + "index": 13, + "matchup": false, + "normal": { + "boss": [], + "hero_levels": [ + 0, + 0, + 0, + 6, + 5, + 5 + ], + "lane_losses_at_end": [ + 3, + 0 + ], + "max_combat": 91.7499999999969, + "result": "rot", + "rounds": 12, + "timeouts": 0, + "total_gold_curve": [ + 843, + 690, + 890, + 932, + 923, + 1003, + 1165, + 1075, + 1353, + 1595, + 1793, + 1474 + ] + }, + "profiles": [ + "teamhilfe", + "druck", + "fernkampf", + "fernkampf", + "druck", + "ausgewogen" + ], + "seed": 1013, + "swapped": { + "boss": [], + "hero_levels": [ + 6, + 5, + 5, + 0, + 0, + 0 + ], + "lane_losses_at_end": [ + 0, + 3 + ], + "max_combat": 91.7499999999969, + "result": "blau", + "rounds": 12, + "timeouts": 0, + "total_gold_curve": [ + 843, + 690, + 890, + 932, + 923, + 1003, + 1165, + 1075, + 1353, + 1595, + 1793, + 1474 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "glutpakt", + "glutpakt", + "wurzelbund" + ], + "index": 14, + "matchup": false, + "normal": { + "boss": [ + "9:failed:", + "10:won:1" + ], + "hero_levels": [ + 3, + 3, + 3, + 2, + 2, + 2 + ], + "lane_losses_at_end": [ + 1, + 2 + ], + "max_combat": 99.4499999999965, + "result": "blau", + "rounds": 10, + "timeouts": 0, + "total_gold_curve": [ + 803, + 810, + 920, + 847, + 1063, + 1098, + 1150, + 1300, + 1783, + 1600 + ] + }, + "profiles": [ + "bossjagd", + "druck", + "fernkampf", + "bossjagd", + "bossjagd", + "bossjagd" + ], + "seed": 1014, + "swapped": { + "boss": [ + "9:failed:", + "10:won:0" + ], + "hero_levels": [ + 2, + 2, + 2, + 3, + 3, + 3 + ], + "lane_losses_at_end": [ + 2, + 1 + ], + "max_combat": 99.4499999999965, + "result": "rot", + "rounds": 10, + "timeouts": 0, + "total_gold_curve": [ + 803, + 810, + 920, + 847, + 1063, + 1098, + 1150, + 1300, + 1783, + 1600 + ] + } + }, + { + "bunds": [ + "glutpakt", + "wurzelbund", + "glutpakt", + "wurzelbund", + "glutpakt", + "glutpakt" + ], + "index": 15, + "matchup": false, + "normal": { + "boss": [], + "hero_levels": [ + 6, + 5, + 0, + 6, + 7, + 6 + ], + "lane_losses_at_end": [ + 2, + 1 + ], + "max_combat": 155.450000000001, + "result": "rot", + "rounds": 13, + "timeouts": 0, + "total_gold_curve": [ + 898, + 705, + 960, + 972, + 1008, + 1033, + 1140, + 1270, + 1383, + 1700, + 1753, + 1502, + 1700 + ] + }, + "profiles": [ + "fernkampf", + "fernkampf", + "verteidigung", + "druck", + "fernkampf", + "ausgewogen" + ], + "seed": 1015, + "swapped": { + "boss": [], + "hero_levels": [ + 6, + 7, + 6, + 6, + 5, + 0 + ], + "lane_losses_at_end": [ + 1, + 2 + ], + "max_combat": 155.450000000001, + "result": "blau", + "rounds": 13, + "timeouts": 0, + "total_gold_curve": [ + 898, + 705, + 960, + 972, + 1008, + 1033, + 1140, + 1270, + 1383, + 1700, + 1753, + 1502, + 1700 + ] + } + }, + { + "bunds": [ + "glutpakt", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt", + "glutpakt" + ], + "index": 16, + "matchup": false, + "normal": { + "boss": [], + "hero_levels": [ + 3, + 0, + 2, + 7, + 5, + 6 + ], + "lane_losses_at_end": [ + 2, + 0 + ], + "max_combat": 169.300000000004, + "result": "rot", + "rounds": 14, + "timeouts": 0, + "total_gold_curve": [ + 758, + 825, + 1010, + 1012, + 993, + 1003, + 1100, + 1100, + 1143, + 1350, + 1498, + 1587, + 2283, + 1370 + ] + }, + "profiles": [ + "teamhilfe", + "verteidigung", + "verteidigung", + "teamhilfe", + "verteidigung", + "ausgewogen" + ], + "seed": 1016, + "swapped": { + "boss": [], + "hero_levels": [ + 7, + 5, + 6, + 3, + 0, + 2 + ], + "lane_losses_at_end": [ + 0, + 2 + ], + "max_combat": 169.300000000004, + "result": "blau", + "rounds": 14, + "timeouts": 0, + "total_gold_curve": [ + 758, + 825, + 1010, + 1012, + 993, + 1003, + 1100, + 1100, + 1143, + 1350, + 1498, + 1587, + 2283, + 1370 + ] + } + }, + { + "bunds": [ + "glutpakt", + "glutpakt", + "glutpakt", + "wurzelbund", + "glutpakt", + "wurzelbund" + ], + "index": 17, + "matchup": false, + "normal": { + "boss": [ + "8:won:1", + "9:deployed:1" + ], + "hero_levels": [ + 3, + 2, + 3, + 4, + 0, + 4 + ], + "lane_losses_at_end": [ + 1, + 1 + ], + "max_combat": 105.899999999996, + "result": "rot", + "rounds": 9, + "timeouts": 0, + "total_gold_curve": [ + 893, + 720, + 950, + 952, + 1053, + 1063, + 1320, + 1580, + 1340 + ] + }, + "profiles": [ + "fernkampf", + "verteidigung", + "fernkampf", + "druck", + "fernkampf", + "bossjagd" + ], + "seed": 1017, + "swapped": { + "boss": [ + "8:won:0", + "9:deployed:0" + ], + "hero_levels": [ + 4, + 0, + 4, + 3, + 2, + 3 + ], + "lane_losses_at_end": [ + 1, + 1 + ], + "max_combat": 105.899999999996, + "result": "blau", + "rounds": 9, + "timeouts": 0, + "total_gold_curve": [ + 893, + 720, + 950, + 952, + 1053, + 1063, + 1320, + 1580, + 1340 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt", + "glutpakt" + ], + "index": 18, + "matchup": false, + "normal": { + "boss": [ + "8:won:0" + ], + "hero_levels": [ + 0, + 0, + 3, + 0, + 0, + 0 + ], + "lane_losses_at_end": [ + 2, + 0 + ], + "max_combat": 149.55, + "result": "rot", + "rounds": 8, + "timeouts": 0, + "total_gold_curve": [ + 753, + 780, + 890, + 927, + 1073, + 978, + 1495, + 1362 + ] + }, + "profiles": [ + "verteidigung", + "ausgewogen", + "bossjagd", + "druck", + "teamhilfe", + "druck" + ], + "seed": 1018, + "swapped": { + "boss": [ + "8:won:1" + ], + "hero_levels": [ + 0, + 0, + 0, + 0, + 0, + 3 + ], + "lane_losses_at_end": [ + 0, + 2 + ], + "max_combat": 149.55, + "result": "blau", + "rounds": 8, + "timeouts": 0, + "total_gold_curve": [ + 753, + 780, + 890, + 927, + 1073, + 978, + 1495, + 1362 + ] + } + }, + { + "bunds": [ + "glutpakt", + "glutpakt", + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund" + ], + "index": 19, + "matchup": false, + "normal": { + "boss": [ + "7:won:1" + ], + "hero_levels": [ + 0, + 0, + 2, + 2, + 0, + 3 + ], + "lane_losses_at_end": [ + 1, + 1 + ], + "max_combat": 90.899999999997, + "result": "blau", + "rounds": 7, + "timeouts": 0, + "total_gold_curve": [ + 798, + 770, + 940, + 997, + 1203, + 1568, + 1304 + ] + }, + "profiles": [ + "druck", + "teamhilfe", + "teamhilfe", + "fernkampf", + "verteidigung", + "bossjagd" + ], + "seed": 1019, + "swapped": { + "boss": [ + "7:won:0" + ], + "hero_levels": [ + 2, + 0, + 3, + 0, + 0, + 2 + ], + "lane_losses_at_end": [ + 1, + 1 + ], + "max_combat": 90.899999999997, + "result": "rot", + "rounds": 7, + "timeouts": 0, + "total_gold_curve": [ + 798, + 770, + 940, + 997, + 1203, + 1568, + 1304 + ] + } + } +] \ No newline at end of file diff --git a/docs/luneraria/logs/m3/balance-step2.log b/docs/luneraria/logs/m3/balance-step2.log new file mode 100644 index 0000000..b99ab85 --- /dev/null +++ b/docs/luneraria/logs/m3/balance-step2.log @@ -0,0 +1,26 @@ +Godot Engine v4.7.2.stable.fedora.ed1daf0bf - https://godotengine.org + +BALANCE 00 ["ausgewogen", "druck", "teamhilfe"] | ["teamhilfe", "verteidigung", "ausgewogen"] -> blau (7) / swapped rot (8) +BALANCE 01 ["bossjagd", "bossjagd", "teamhilfe"] | ["fernkampf", "teamhilfe", "fernkampf"] -> rot (9) / swapped blau (8) +BALANCE 02 ["bossjagd", "ausgewogen", "teamhilfe"] | ["druck", "ausgewogen", "verteidigung"] -> blau (10) / swapped rot (10) +BALANCE 03 ["fernkampf", "druck", "ausgewogen"] | ["druck", "fernkampf", "verteidigung"] -> blau (13) / swapped rot (13) +BALANCE 04 ["ausgewogen", "teamhilfe", "fernkampf"] | ["ausgewogen", "verteidigung", "fernkampf"] -> blau (9) / swapped rot (9) +BALANCE 05 ["teamhilfe", "fernkampf", "verteidigung"] | ["fernkampf", "bossjagd", "bossjagd"] -> blau (11) / swapped rot (11) +BALANCE 06 ["bossjagd", "ausgewogen", "teamhilfe"] | ["fernkampf", "teamhilfe", "ausgewogen"] -> rot (9) / swapped blau (9) +BALANCE 07 ["ausgewogen", "ausgewogen", "ausgewogen"] | ["teamhilfe", "teamhilfe", "ausgewogen"] -> rot (12) / swapped blau (12) +BALANCE 08 ["teamhilfe", "ausgewogen", "bossjagd"] | ["druck", "ausgewogen", "druck"] -> rot (11) / swapped blau (11) +BALANCE 09 ["verteidigung", "druck", "druck"] | ["bossjagd", "ausgewogen", "teamhilfe"] -> rot (9) / swapped blau (9) +BALANCE 10 ["druck", "ausgewogen", "ausgewogen"] | ["teamhilfe", "bossjagd", "ausgewogen"] -> blau (12) / swapped rot (12) +BALANCE 11 ["bossjagd", "fernkampf", "fernkampf"] | ["teamhilfe", "verteidigung", "teamhilfe"] -> blau (10) / swapped rot (10) +BALANCE 12 ["fernkampf", "fernkampf", "teamhilfe"] | ["teamhilfe", "ausgewogen", "fernkampf"] -> blau (8) / swapped rot (9) +BALANCE 13 ["teamhilfe", "druck", "fernkampf"] | ["fernkampf", "druck", "ausgewogen"] -> rot (12) / swapped blau (12) +BALANCE 14 ["bossjagd", "druck", "fernkampf"] | ["bossjagd", "bossjagd", "bossjagd"] -> blau (10) / swapped rot (10) +BALANCE 15 ["fernkampf", "fernkampf", "verteidigung"] | ["druck", "fernkampf", "ausgewogen"] -> rot (13) / swapped blau (13) +BALANCE 16 ["teamhilfe", "verteidigung", "verteidigung"] | ["teamhilfe", "verteidigung", "ausgewogen"] -> rot (14) / swapped blau (14) +BALANCE 17 ["fernkampf", "verteidigung", "fernkampf"] | ["druck", "fernkampf", "bossjagd"] -> rot (9) / swapped blau (9) +BALANCE 18 ["verteidigung", "ausgewogen", "bossjagd"] | ["druck", "teamhilfe", "druck"] -> rot (8) / swapped blau (8) +BALANCE 19 ["druck", "teamhilfe", "teamhilfe"] | ["fernkampf", "verteidigung", "bossjagd"] -> blau (7) / swapped rot (7) +WARNING: 26 ObjectDB instances were leaked at exit (run with `--verbose` for details). + at: cleanup (core/object/object.cpp:2536) +ERROR: 1 resources still in use at exit (run with --verbose for details). + at: clear (core/io/resource.cpp:822) diff --git a/docs/luneraria/logs/m3/balance-step3-summary.txt b/docs/luneraria/logs/m3/balance-step3-summary.txt new file mode 100644 index 0000000..c1445fc --- /dev/null +++ b/docs/luneraria/logs/m3/balance-step3-summary.txt @@ -0,0 +1,6 @@ +Partien 40 · Ergebnisse {'rot': 20, 'blau': 20} · Testlimit 0 · Zeitfenster-Kämpfe 0 · längster Kampf 135 s +Wellen bis Ende: min 9 · median 11 · max 14 +Seitentausch: dieselbe Zusammensetzung gewinnt beide Male in 20/20 Paaren +Profile (Siege/Einsätze): {'ausgewogen': '28/48 (58 %)', 'bossjagd': '8/34 (24 %)', 'druck': '18/34 (53 %)', 'fernkampf': '26/48 (54 %)', 'teamhilfe': '22/48 (46 %)', 'verteidigung': '18/28 (64 %)'} +Bünde: {'glutpakt': '68/124 (55 %)', 'wurzelbund': '52/116 (45 %)'} +Boss: {'won': 24, 'deployed': 16} · Heldenstufen am Ende (Median): 4 diff --git a/docs/luneraria/logs/m3/balance-step3.json b/docs/luneraria/logs/m3/balance-step3.json new file mode 100644 index 0000000..7647e46 --- /dev/null +++ b/docs/luneraria/logs/m3/balance-step3.json @@ -0,0 +1,1770 @@ +[ + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "wurzelbund", + "wurzelbund" + ], + "index": 0, + "matchup": false, + "normal": { + "boss": [], + "hero_levels": [ + 5, + 5, + 4, + 4, + 2, + 3 + ], + "lane_losses_at_end": [ + 1, + 1 + ], + "max_combat": 111.149999999996, + "result": "rot", + "rounds": 12, + "timeouts": 0, + "total_gold_curve": [ + 743, + 745, + 895, + 762, + 958, + 923, + 1005, + 965, + 1383, + 1670, + 2647, + 1302 + ] + }, + "profiles": [ + "ausgewogen", + "druck", + "teamhilfe", + "teamhilfe", + "verteidigung", + "ausgewogen" + ], + "seed": 1000, + "swapped": { + "boss": [], + "hero_levels": [ + 4, + 2, + 3, + 5, + 5, + 4 + ], + "lane_losses_at_end": [ + 1, + 1 + ], + "max_combat": 111.149999999996, + "result": "blau", + "rounds": 12, + "timeouts": 0, + "total_gold_curve": [ + 743, + 745, + 895, + 762, + 958, + 923, + 1005, + 965, + 1383, + 1670, + 2647, + 1302 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "wurzelbund", + "glutpakt" + ], + "index": 1, + "matchup": false, + "normal": { + "boss": [ + "9:won:0" + ], + "hero_levels": [ + 2, + 2, + 3, + 3, + 3, + 2 + ], + "lane_losses_at_end": [ + 1, + 0 + ], + "max_combat": 95.2999999999967, + "result": "rot", + "rounds": 9, + "timeouts": 0, + "total_gold_curve": [ + 843, + 750, + 970, + 812, + 1018, + 1143, + 1125, + 1774, + 1274 + ] + }, + "profiles": [ + "bossjagd", + "bossjagd", + "teamhilfe", + "fernkampf", + "teamhilfe", + "fernkampf" + ], + "seed": 1001, + "swapped": { + "boss": [ + "9:won:1" + ], + "hero_levels": [ + 3, + 3, + 2, + 2, + 2, + 3 + ], + "lane_losses_at_end": [ + 0, + 1 + ], + "max_combat": 95.2999999999967, + "result": "blau", + "rounds": 9, + "timeouts": 0, + "total_gold_curve": [ + 843, + 750, + 970, + 812, + 1018, + 1143, + 1125, + 1774, + 1274 + ] + } + }, + { + "bunds": [ + "glutpakt", + "wurzelbund", + "wurzelbund", + "wurzelbund", + "glutpakt", + "glutpakt" + ], + "index": 2, + "matchup": false, + "normal": { + "boss": [ + "10:won:0", + "11:deployed:0" + ], + "hero_levels": [ + 3, + 0, + 0, + 4, + 4, + 3 + ], + "lane_losses_at_end": [ + 2, + 1 + ], + "max_combat": 130.999999999996, + "result": "blau", + "rounds": 11, + "timeouts": 0, + "total_gold_curve": [ + 753, + 780, + 920, + 892, + 968, + 963, + 1095, + 1240, + 1572, + 1899, + 2203 + ] + }, + "profiles": [ + "bossjagd", + "ausgewogen", + "teamhilfe", + "druck", + "ausgewogen", + "verteidigung" + ], + "seed": 1002, + "swapped": { + "boss": [ + "10:won:1", + "11:deployed:1" + ], + "hero_levels": [ + 4, + 4, + 3, + 3, + 0, + 0 + ], + "lane_losses_at_end": [ + 1, + 2 + ], + "max_combat": 130.999999999996, + "result": "rot", + "rounds": 11, + "timeouts": 0, + "total_gold_curve": [ + 753, + 780, + 920, + 892, + 968, + 963, + 1095, + 1240, + 1572, + 1899, + 2203 + ] + } + }, + { + "bunds": [ + "glutpakt", + "wurzelbund", + "wurzelbund", + "wurzelbund", + "wurzelbund", + "wurzelbund" + ], + "index": 3, + "matchup": false, + "normal": { + "boss": [], + "hero_levels": [ + 7, + 4, + 4, + 5, + 6, + 3 + ], + "lane_losses_at_end": [ + 1, + 2 + ], + "max_combat": 115.099999999996, + "result": "blau", + "rounds": 12, + "timeouts": 0, + "total_gold_curve": [ + 838, + 695, + 865, + 907, + 953, + 963, + 1140, + 1420, + 1496, + 2068, + 2091, + 1092 + ] + }, + "profiles": [ + "fernkampf", + "druck", + "ausgewogen", + "druck", + "fernkampf", + "verteidigung" + ], + "seed": 1003, + "swapped": { + "boss": [], + "hero_levels": [ + 5, + 6, + 3, + 7, + 4, + 4 + ], + "lane_losses_at_end": [ + 2, + 1 + ], + "max_combat": 115.099999999996, + "result": "rot", + "rounds": 12, + "timeouts": 0, + "total_gold_curve": [ + 838, + 695, + 865, + 907, + 953, + 963, + 1140, + 1420, + 1496, + 2068, + 2091, + 1092 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "wurzelbund", + "wurzelbund", + "glutpakt", + "glutpakt", + "glutpakt" + ], + "index": 4, + "matchup": false, + "normal": { + "boss": [], + "hero_levels": [ + 2, + 2, + 3, + 2, + 5, + 5 + ], + "lane_losses_at_end": [ + 2, + 1 + ], + "max_combat": 91.249999999997, + "result": "rot", + "rounds": 10, + "timeouts": 0, + "total_gold_curve": [ + 848, + 725, + 975, + 827, + 978, + 1108, + 1315, + 1301, + 2232, + 1128 + ] + }, + "profiles": [ + "ausgewogen", + "teamhilfe", + "fernkampf", + "ausgewogen", + "verteidigung", + "fernkampf" + ], + "seed": 1004, + "swapped": { + "boss": [], + "hero_levels": [ + 2, + 5, + 5, + 2, + 2, + 3 + ], + "lane_losses_at_end": [ + 1, + 2 + ], + "max_combat": 91.249999999997, + "result": "blau", + "rounds": 10, + "timeouts": 0, + "total_gold_curve": [ + 848, + 725, + 975, + 827, + 978, + 1108, + 1315, + 1301, + 2232, + 1128 + ] + } + }, + { + "bunds": [ + "glutpakt", + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt" + ], + "index": 5, + "matchup": false, + "normal": { + "boss": [ + "9:won:1" + ], + "hero_levels": [ + 3, + 3, + 3, + 0, + 0, + 0 + ], + "lane_losses_at_end": [ + 0, + 2 + ], + "max_combat": 119.749999999995, + "result": "blau", + "rounds": 9, + "timeouts": 0, + "total_gold_curve": [ + 853, + 785, + 985, + 837, + 1008, + 1028, + 1115, + 1480, + 1722 + ] + }, + "profiles": [ + "teamhilfe", + "fernkampf", + "verteidigung", + "fernkampf", + "bossjagd", + "bossjagd" + ], + "seed": 1005, + "swapped": { + "boss": [ + "9:won:0" + ], + "hero_levels": [ + 0, + 0, + 0, + 3, + 3, + 3 + ], + "lane_losses_at_end": [ + 2, + 0 + ], + "max_combat": 119.749999999995, + "result": "rot", + "rounds": 9, + "timeouts": 0, + "total_gold_curve": [ + 853, + 785, + 985, + 837, + 1008, + 1028, + 1115, + 1480, + 1722 + ] + } + }, + { + "bunds": [ + "glutpakt", + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt" + ], + "index": 6, + "matchup": false, + "normal": { + "boss": [ + "8:won:0", + "9:deployed:0" + ], + "hero_levels": [ + 3, + 5, + 3, + 4, + 0, + 0 + ], + "lane_losses_at_end": [ + 2, + 1 + ], + "max_combat": 110.299999999996, + "result": "blau", + "rounds": 9, + "timeouts": 0, + "total_gold_curve": [ + 803, + 755, + 980, + 842, + 1098, + 958, + 1614, + 1529, + 1258 + ] + }, + "profiles": [ + "bossjagd", + "ausgewogen", + "teamhilfe", + "fernkampf", + "teamhilfe", + "ausgewogen" + ], + "seed": 1006, + "swapped": { + "boss": [ + "8:won:1", + "9:deployed:1" + ], + "hero_levels": [ + 4, + 0, + 0, + 3, + 5, + 3 + ], + "lane_losses_at_end": [ + 1, + 2 + ], + "max_combat": 110.299999999996, + "result": "rot", + "rounds": 9, + "timeouts": 0, + "total_gold_curve": [ + 803, + 755, + 980, + 842, + 1098, + 958, + 1614, + 1529, + 1258 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt", + "glutpakt" + ], + "index": 7, + "matchup": false, + "normal": { + "boss": [], + "hero_levels": [ + 3, + 6, + 3, + 5, + 0, + 4 + ], + "lane_losses_at_end": [ + 1, + 1 + ], + "max_combat": 134.549999999996, + "result": "blau", + "rounds": 11, + "timeouts": 0, + "total_gold_curve": [ + 753, + 740, + 965, + 837, + 1028, + 878, + 1342, + 1277, + 1659, + 1321, + 1454 + ] + }, + "profiles": [ + "ausgewogen", + "ausgewogen", + "ausgewogen", + "teamhilfe", + "teamhilfe", + "ausgewogen" + ], + "seed": 1007, + "swapped": { + "boss": [], + "hero_levels": [ + 5, + 0, + 4, + 3, + 6, + 3 + ], + "lane_losses_at_end": [ + 1, + 1 + ], + "max_combat": 134.549999999996, + "result": "rot", + "rounds": 11, + "timeouts": 0, + "total_gold_curve": [ + 753, + 740, + 965, + 837, + 1028, + 878, + 1342, + 1277, + 1659, + 1321, + 1454 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "wurzelbund", + "wurzelbund", + "glutpakt", + "glutpakt", + "glutpakt" + ], + "index": 8, + "matchup": false, + "normal": { + "boss": [ + "9:won:0", + "10:deployed:0" + ], + "hero_levels": [ + 4, + 3, + 3, + 6, + 6, + 6 + ], + "lane_losses_at_end": [ + 3, + 1 + ], + "max_combat": 122.999999999995, + "result": "rot", + "rounds": 12, + "timeouts": 0, + "total_gold_curve": [ + 753, + 755, + 875, + 807, + 958, + 1073, + 1100, + 1050, + 1513, + 3460, + 2044, + 1122 + ] + }, + "profiles": [ + "teamhilfe", + "ausgewogen", + "bossjagd", + "druck", + "ausgewogen", + "druck" + ], + "seed": 1008, + "swapped": { + "boss": [ + "9:won:1", + "10:deployed:1" + ], + "hero_levels": [ + 6, + 6, + 6, + 4, + 3, + 3 + ], + "lane_losses_at_end": [ + 1, + 3 + ], + "max_combat": 122.999999999995, + "result": "blau", + "rounds": 12, + "timeouts": 0, + "total_gold_curve": [ + 753, + 755, + 875, + 807, + 958, + 1073, + 1100, + 1050, + 1513, + 3460, + 2044, + 1122 + ] + } + }, + { + "bunds": [ + "glutpakt", + "wurzelbund", + "glutpakt", + "glutpakt", + "wurzelbund", + "glutpakt" + ], + "index": 9, + "matchup": false, + "normal": { + "boss": [ + "9:won:1", + "10:deployed:1" + ], + "hero_levels": [ + 4, + 6, + 7, + 3, + 5, + 4 + ], + "lane_losses_at_end": [ + 1, + 2 + ], + "max_combat": 125.649999999995, + "result": "blau", + "rounds": 12, + "timeouts": 0, + "total_gold_curve": [ + 758, + 790, + 895, + 882, + 988, + 1028, + 1120, + 1085, + 1852, + 3059, + 1848, + 1513 + ] + }, + "profiles": [ + "verteidigung", + "druck", + "druck", + "bossjagd", + "ausgewogen", + "teamhilfe" + ], + "seed": 1009, + "swapped": { + "boss": [ + "9:won:0", + "10:deployed:0" + ], + "hero_levels": [ + 3, + 5, + 4, + 4, + 6, + 7 + ], + "lane_losses_at_end": [ + 2, + 1 + ], + "max_combat": 125.649999999995, + "result": "rot", + "rounds": 12, + "timeouts": 0, + "total_gold_curve": [ + 758, + 790, + 895, + 882, + 988, + 1028, + 1120, + 1085, + 1852, + 3059, + 1848, + 1513 + ] + } + }, + { + "bunds": [ + "glutpakt", + "wurzelbund", + "wurzelbund", + "wurzelbund", + "glutpakt", + "glutpakt" + ], + "index": 10, + "matchup": false, + "normal": { + "boss": [ + "9:won:1", + "10:deployed:1" + ], + "hero_levels": [ + 6, + 5, + 5, + 4, + 3, + 3 + ], + "lane_losses_at_end": [ + 1, + 3 + ], + "max_combat": 122.999999999995, + "result": "blau", + "rounds": 12, + "timeouts": 0, + "total_gold_curve": [ + 753, + 755, + 915, + 832, + 983, + 968, + 1040, + 1020, + 1508, + 3120, + 1713, + 1987 + ] + }, + "profiles": [ + "druck", + "ausgewogen", + "ausgewogen", + "teamhilfe", + "bossjagd", + "ausgewogen" + ], + "seed": 1010, + "swapped": { + "boss": [ + "9:won:0", + "10:deployed:0" + ], + "hero_levels": [ + 4, + 3, + 3, + 6, + 5, + 5 + ], + "lane_losses_at_end": [ + 3, + 1 + ], + "max_combat": 122.999999999995, + "result": "rot", + "rounds": 12, + "timeouts": 0, + "total_gold_curve": [ + 753, + 755, + 915, + 832, + 983, + 968, + 1040, + 1020, + 1508, + 3120, + 1713, + 1987 + ] + } + }, + { + "bunds": [ + "glutpakt", + "glutpakt", + "glutpakt", + "glutpakt", + "glutpakt", + "wurzelbund" + ], + "index": 11, + "matchup": false, + "normal": { + "boss": [ + "11:won:0" + ], + "hero_levels": [ + 3, + 3, + 3, + 5, + 2, + 2 + ], + "lane_losses_at_end": [ + 1, + 1 + ], + "max_combat": 125.599999999995, + "result": "rot", + "rounds": 11, + "timeouts": 0, + "total_gold_curve": [ + 853, + 770, + 1005, + 837, + 1028, + 1003, + 1080, + 1435, + 1393, + 2210, + 1509 + ] + }, + "profiles": [ + "bossjagd", + "fernkampf", + "fernkampf", + "teamhilfe", + "verteidigung", + "teamhilfe" + ], + "seed": 1011, + "swapped": { + "boss": [ + "11:won:1" + ], + "hero_levels": [ + 5, + 2, + 2, + 3, + 3, + 3 + ], + "lane_losses_at_end": [ + 1, + 1 + ], + "max_combat": 125.599999999995, + "result": "blau", + "rounds": 11, + "timeouts": 0, + "total_gold_curve": [ + 853, + 770, + 1005, + 837, + 1028, + 1003, + 1080, + 1435, + 1393, + 2210, + 1509 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "glutpakt", + "glutpakt", + "glutpakt", + "glutpakt" + ], + "index": 12, + "matchup": false, + "normal": { + "boss": [], + "hero_levels": [ + 8, + 8, + 6, + 7, + 6, + 7 + ], + "lane_losses_at_end": [ + 1, + 2 + ], + "max_combat": 97.1499999999966, + "result": "blau", + "rounds": 13, + "timeouts": 0, + "total_gold_curve": [ + 903, + 710, + 1020, + 852, + 933, + 1028, + 1255, + 1250, + 1915, + 2022, + 1800, + 2196, + 1372 + ] + }, + "profiles": [ + "fernkampf", + "fernkampf", + "teamhilfe", + "teamhilfe", + "ausgewogen", + "fernkampf" + ], + "seed": 1012, + "swapped": { + "boss": [], + "hero_levels": [ + 7, + 6, + 7, + 8, + 8, + 6 + ], + "lane_losses_at_end": [ + 2, + 1 + ], + "max_combat": 97.1499999999966, + "result": "rot", + "rounds": 13, + "timeouts": 0, + "total_gold_curve": [ + 903, + 710, + 1020, + 852, + 933, + 1028, + 1255, + 1250, + 1915, + 2022, + 1800, + 2196, + 1372 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "wurzelbund", + "wurzelbund", + "glutpakt", + "wurzelbund", + "glutpakt" + ], + "index": 13, + "matchup": false, + "normal": { + "boss": [], + "hero_levels": [ + 5, + 7, + 5, + 7, + 7, + 6 + ], + "lane_losses_at_end": [ + 2, + 0 + ], + "max_combat": 100.949999999996, + "result": "rot", + "rounds": 13, + "timeouts": 0, + "total_gold_curve": [ + 843, + 690, + 890, + 932, + 923, + 1003, + 1165, + 1165, + 1546, + 2068, + 1937, + 1591, + 1903 + ] + }, + "profiles": [ + "teamhilfe", + "druck", + "fernkampf", + "fernkampf", + "druck", + "ausgewogen" + ], + "seed": 1013, + "swapped": { + "boss": [], + "hero_levels": [ + 7, + 7, + 6, + 5, + 7, + 5 + ], + "lane_losses_at_end": [ + 0, + 2 + ], + "max_combat": 100.949999999996, + "result": "blau", + "rounds": 13, + "timeouts": 0, + "total_gold_curve": [ + 843, + 690, + 890, + 932, + 923, + 1003, + 1165, + 1165, + 1546, + 2068, + 1937, + 1591, + 1903 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "glutpakt", + "glutpakt", + "wurzelbund" + ], + "index": 14, + "matchup": false, + "normal": { + "boss": [ + "9:won:1", + "10:deployed:1" + ], + "hero_levels": [ + 3, + 3, + 3, + 0, + 0, + 0 + ], + "lane_losses_at_end": [ + 0, + 2 + ], + "max_combat": 106.299999999996, + "result": "blau", + "rounds": 10, + "timeouts": 0, + "total_gold_curve": [ + 803, + 810, + 920, + 847, + 1063, + 1098, + 1150, + 1300, + 2053, + 1069 + ] + }, + "profiles": [ + "bossjagd", + "druck", + "fernkampf", + "bossjagd", + "bossjagd", + "bossjagd" + ], + "seed": 1014, + "swapped": { + "boss": [ + "9:won:0", + "10:deployed:0" + ], + "hero_levels": [ + 0, + 0, + 0, + 3, + 3, + 3 + ], + "lane_losses_at_end": [ + 2, + 0 + ], + "max_combat": 106.299999999996, + "result": "rot", + "rounds": 10, + "timeouts": 0, + "total_gold_curve": [ + 803, + 810, + 920, + 847, + 1063, + 1098, + 1150, + 1300, + 2053, + 1069 + ] + } + }, + { + "bunds": [ + "glutpakt", + "wurzelbund", + "glutpakt", + "wurzelbund", + "glutpakt", + "glutpakt" + ], + "index": 15, + "matchup": false, + "normal": { + "boss": [], + "hero_levels": [ + 7, + 5, + 4, + 5, + 4, + 4 + ], + "lane_losses_at_end": [ + 0, + 1 + ], + "max_combat": 130.449999999995, + "result": "blau", + "rounds": 12, + "timeouts": 0, + "total_gold_curve": [ + 898, + 705, + 950, + 902, + 988, + 1023, + 1140, + 1335, + 1367, + 2204, + 1627, + 1312 + ] + }, + "profiles": [ + "fernkampf", + "fernkampf", + "verteidigung", + "druck", + "fernkampf", + "ausgewogen" + ], + "seed": 1015, + "swapped": { + "boss": [], + "hero_levels": [ + 5, + 4, + 4, + 7, + 5, + 4 + ], + "lane_losses_at_end": [ + 1, + 0 + ], + "max_combat": 130.449999999995, + "result": "rot", + "rounds": 12, + "timeouts": 0, + "total_gold_curve": [ + 898, + 705, + 950, + 902, + 988, + 1023, + 1140, + 1335, + 1367, + 2204, + 1627, + 1312 + ] + } + }, + { + "bunds": [ + "glutpakt", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt", + "glutpakt" + ], + "index": 16, + "matchup": false, + "normal": { + "boss": [], + "hero_levels": [ + 7, + 5, + 2, + 7, + 5, + 7 + ], + "lane_losses_at_end": [ + 3, + 1 + ], + "max_combat": 117.449999999995, + "result": "rot", + "rounds": 14, + "timeouts": 0, + "total_gold_curve": [ + 758, + 825, + 980, + 812, + 928, + 978, + 1060, + 1100, + 1198, + 1545, + 1990, + 1766, + 2282, + 2477 + ] + }, + "profiles": [ + "teamhilfe", + "verteidigung", + "verteidigung", + "teamhilfe", + "verteidigung", + "ausgewogen" + ], + "seed": 1016, + "swapped": { + "boss": [], + "hero_levels": [ + 7, + 5, + 7, + 7, + 5, + 2 + ], + "lane_losses_at_end": [ + 1, + 3 + ], + "max_combat": 117.449999999995, + "result": "blau", + "rounds": 14, + "timeouts": 0, + "total_gold_curve": [ + 758, + 825, + 980, + 812, + 928, + 978, + 1060, + 1100, + 1198, + 1545, + 1990, + 1766, + 2282, + 2477 + ] + } + }, + { + "bunds": [ + "glutpakt", + "glutpakt", + "glutpakt", + "wurzelbund", + "glutpakt", + "wurzelbund" + ], + "index": 17, + "matchup": false, + "normal": { + "boss": [ + "9:won:1", + "10:deployed:1" + ], + "hero_levels": [ + 4, + 3, + 4, + 0, + 0, + 0 + ], + "lane_losses_at_end": [ + 1, + 1 + ], + "max_combat": 94.1499999999968, + "result": "blau", + "rounds": 10, + "timeouts": 0, + "total_gold_curve": [ + 893, + 720, + 940, + 882, + 1033, + 1053, + 1170, + 1495, + 1887, + 1552 + ] + }, + "profiles": [ + "fernkampf", + "verteidigung", + "fernkampf", + "druck", + "fernkampf", + "bossjagd" + ], + "seed": 1017, + "swapped": { + "boss": [ + "9:won:0", + "10:deployed:0" + ], + "hero_levels": [ + 0, + 0, + 0, + 4, + 3, + 4 + ], + "lane_losses_at_end": [ + 1, + 1 + ], + "max_combat": 94.1499999999968, + "result": "rot", + "rounds": 10, + "timeouts": 0, + "total_gold_curve": [ + 893, + 720, + 940, + 882, + 1033, + 1053, + 1170, + 1495, + 1887, + 1552 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt", + "glutpakt" + ], + "index": 18, + "matchup": false, + "normal": { + "boss": [ + "9:won:0", + "10:deployed:0" + ], + "hero_levels": [ + 2, + 3, + 3, + 6, + 2, + 3 + ], + "lane_losses_at_end": [ + 2, + 1 + ], + "max_combat": 104.449999999996, + "result": "blau", + "rounds": 10, + "timeouts": 0, + "total_gold_curve": [ + 753, + 780, + 880, + 867, + 898, + 1073, + 1310, + 1235, + 2211, + 1798 + ] + }, + "profiles": [ + "verteidigung", + "ausgewogen", + "bossjagd", + "druck", + "teamhilfe", + "druck" + ], + "seed": 1018, + "swapped": { + "boss": [ + "9:won:1", + "10:deployed:1" + ], + "hero_levels": [ + 6, + 2, + 3, + 2, + 3, + 3 + ], + "lane_losses_at_end": [ + 1, + 2 + ], + "max_combat": 104.449999999996, + "result": "rot", + "rounds": 10, + "timeouts": 0, + "total_gold_curve": [ + 753, + 780, + 880, + 867, + 898, + 1073, + 1310, + 1235, + 2211, + 1798 + ] + } + }, + { + "bunds": [ + "glutpakt", + "glutpakt", + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund" + ], + "index": 19, + "matchup": false, + "normal": { + "boss": [ + "10:won:1" + ], + "hero_levels": [ + 5, + 4, + 5, + 3, + 4, + 1 + ], + "lane_losses_at_end": [ + 1, + 2 + ], + "max_combat": 130.649999999995, + "result": "blau", + "rounds": 10, + "timeouts": 0, + "total_gold_curve": [ + 798, + 770, + 930, + 787, + 958, + 1198, + 1260, + 1564, + 2114, + 1456 + ] + }, + "profiles": [ + "druck", + "teamhilfe", + "teamhilfe", + "fernkampf", + "verteidigung", + "bossjagd" + ], + "seed": 1019, + "swapped": { + "boss": [ + "10:won:0" + ], + "hero_levels": [ + 3, + 4, + 1, + 5, + 4, + 5 + ], + "lane_losses_at_end": [ + 2, + 1 + ], + "max_combat": 130.649999999995, + "result": "rot", + "rounds": 10, + "timeouts": 0, + "total_gold_curve": [ + 798, + 770, + 930, + 787, + 958, + 1198, + 1260, + 1564, + 2114, + 1456 + ] + } + } +] \ No newline at end of file diff --git a/docs/luneraria/logs/m3/balance-step4-summary.txt b/docs/luneraria/logs/m3/balance-step4-summary.txt new file mode 100644 index 0000000..bfb8d27 --- /dev/null +++ b/docs/luneraria/logs/m3/balance-step4-summary.txt @@ -0,0 +1,6 @@ +Partien 40 · Ergebnisse {'rot': 20, 'blau': 20} · Testlimit 0 · Zeitfenster-Kämpfe 0 · längster Kampf 157 s +Wellen bis Ende: min 9 · median 11 · max 14 +Seitentausch: dieselbe Zusammensetzung gewinnt beide Male in 20/20 Paaren +Profile (Siege/Einsätze): {'ausgewogen': '28/48 (58 %)', 'bossjagd': '8/34 (24 %)', 'druck': '18/34 (53 %)', 'fernkampf': '26/48 (54 %)', 'teamhilfe': '22/48 (46 %)', 'verteidigung': '18/28 (64 %)'} +Bünde: {'glutpakt': '68/124 (55 %)', 'wurzelbund': '52/116 (45 %)'} +Boss: {'won': 20, 'deployed': 8} · Heldenstufen am Ende (Median): 4 diff --git a/docs/luneraria/logs/m3/balance-step4.json b/docs/luneraria/logs/m3/balance-step4.json new file mode 100644 index 0000000..6c25477 --- /dev/null +++ b/docs/luneraria/logs/m3/balance-step4.json @@ -0,0 +1,1754 @@ +[ + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "wurzelbund", + "wurzelbund" + ], + "index": 0, + "matchup": false, + "normal": { + "boss": [], + "hero_levels": [ + 5, + 5, + 4, + 4, + 2, + 3 + ], + "lane_losses_at_end": [ + 1, + 1 + ], + "max_combat": 111.149999999996, + "result": "rot", + "rounds": 12, + "timeouts": 0, + "total_gold_curve": [ + 743, + 745, + 895, + 762, + 958, + 923, + 1005, + 965, + 1383, + 1670, + 2647, + 1302 + ] + }, + "profiles": [ + "ausgewogen", + "druck", + "teamhilfe", + "teamhilfe", + "verteidigung", + "ausgewogen" + ], + "seed": 1000, + "swapped": { + "boss": [], + "hero_levels": [ + 4, + 2, + 3, + 5, + 5, + 4 + ], + "lane_losses_at_end": [ + 1, + 1 + ], + "max_combat": 111.149999999996, + "result": "blau", + "rounds": 12, + "timeouts": 0, + "total_gold_curve": [ + 743, + 745, + 895, + 762, + 958, + 923, + 1005, + 965, + 1383, + 1670, + 2647, + 1302 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "wurzelbund", + "glutpakt" + ], + "index": 1, + "matchup": false, + "normal": { + "boss": [ + "9:won:0" + ], + "hero_levels": [ + 2, + 2, + 3, + 3, + 3, + 2 + ], + "lane_losses_at_end": [ + 1, + 0 + ], + "max_combat": 98.7999999999965, + "result": "rot", + "rounds": 9, + "timeouts": 0, + "total_gold_curve": [ + 843, + 750, + 970, + 812, + 1018, + 1143, + 1125, + 1774, + 1274 + ] + }, + "profiles": [ + "bossjagd", + "bossjagd", + "teamhilfe", + "fernkampf", + "teamhilfe", + "fernkampf" + ], + "seed": 1001, + "swapped": { + "boss": [ + "9:won:1" + ], + "hero_levels": [ + 3, + 3, + 2, + 2, + 2, + 3 + ], + "lane_losses_at_end": [ + 0, + 1 + ], + "max_combat": 98.7999999999965, + "result": "blau", + "rounds": 9, + "timeouts": 0, + "total_gold_curve": [ + 843, + 750, + 970, + 812, + 1018, + 1143, + 1125, + 1774, + 1274 + ] + } + }, + { + "bunds": [ + "glutpakt", + "wurzelbund", + "wurzelbund", + "wurzelbund", + "glutpakt", + "glutpakt" + ], + "index": 2, + "matchup": false, + "normal": { + "boss": [ + "10:won:0", + "11:deployed:0" + ], + "hero_levels": [ + 3, + 0, + 0, + 4, + 4, + 3 + ], + "lane_losses_at_end": [ + 1, + 1 + ], + "max_combat": 122.249999999995, + "result": "blau", + "rounds": 11, + "timeouts": 0, + "total_gold_curve": [ + 753, + 780, + 920, + 892, + 968, + 963, + 1095, + 1240, + 1572, + 1689, + 2208 + ] + }, + "profiles": [ + "bossjagd", + "ausgewogen", + "teamhilfe", + "druck", + "ausgewogen", + "verteidigung" + ], + "seed": 1002, + "swapped": { + "boss": [ + "10:won:1", + "11:deployed:1" + ], + "hero_levels": [ + 4, + 4, + 3, + 3, + 0, + 0 + ], + "lane_losses_at_end": [ + 1, + 1 + ], + "max_combat": 122.249999999995, + "result": "rot", + "rounds": 11, + "timeouts": 0, + "total_gold_curve": [ + 753, + 780, + 920, + 892, + 968, + 963, + 1095, + 1240, + 1572, + 1689, + 2208 + ] + } + }, + { + "bunds": [ + "glutpakt", + "wurzelbund", + "wurzelbund", + "wurzelbund", + "wurzelbund", + "wurzelbund" + ], + "index": 3, + "matchup": false, + "normal": { + "boss": [], + "hero_levels": [ + 7, + 4, + 4, + 5, + 6, + 3 + ], + "lane_losses_at_end": [ + 1, + 2 + ], + "max_combat": 115.099999999996, + "result": "blau", + "rounds": 12, + "timeouts": 0, + "total_gold_curve": [ + 838, + 695, + 865, + 907, + 953, + 963, + 1140, + 1420, + 1496, + 2068, + 2091, + 1092 + ] + }, + "profiles": [ + "fernkampf", + "druck", + "ausgewogen", + "druck", + "fernkampf", + "verteidigung" + ], + "seed": 1003, + "swapped": { + "boss": [], + "hero_levels": [ + 5, + 6, + 3, + 7, + 4, + 4 + ], + "lane_losses_at_end": [ + 2, + 1 + ], + "max_combat": 115.099999999996, + "result": "rot", + "rounds": 12, + "timeouts": 0, + "total_gold_curve": [ + 838, + 695, + 865, + 907, + 953, + 963, + 1140, + 1420, + 1496, + 2068, + 2091, + 1092 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "wurzelbund", + "wurzelbund", + "glutpakt", + "glutpakt", + "glutpakt" + ], + "index": 4, + "matchup": false, + "normal": { + "boss": [], + "hero_levels": [ + 2, + 2, + 3, + 2, + 5, + 5 + ], + "lane_losses_at_end": [ + 2, + 1 + ], + "max_combat": 91.249999999997, + "result": "rot", + "rounds": 10, + "timeouts": 0, + "total_gold_curve": [ + 848, + 725, + 975, + 827, + 978, + 1108, + 1315, + 1301, + 2232, + 1128 + ] + }, + "profiles": [ + "ausgewogen", + "teamhilfe", + "fernkampf", + "ausgewogen", + "verteidigung", + "fernkampf" + ], + "seed": 1004, + "swapped": { + "boss": [], + "hero_levels": [ + 2, + 5, + 5, + 2, + 2, + 3 + ], + "lane_losses_at_end": [ + 1, + 2 + ], + "max_combat": 91.249999999997, + "result": "blau", + "rounds": 10, + "timeouts": 0, + "total_gold_curve": [ + 848, + 725, + 975, + 827, + 978, + 1108, + 1315, + 1301, + 2232, + 1128 + ] + } + }, + { + "bunds": [ + "glutpakt", + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt" + ], + "index": 5, + "matchup": false, + "normal": { + "boss": [], + "hero_levels": [ + 5, + 4, + 4, + 0, + 0, + 0 + ], + "lane_losses_at_end": [ + 0, + 3 + ], + "max_combat": 108.999999999996, + "result": "blau", + "rounds": 10, + "timeouts": 0, + "total_gold_curve": [ + 853, + 785, + 985, + 837, + 1008, + 1028, + 1115, + 1480, + 1613, + 1804 + ] + }, + "profiles": [ + "teamhilfe", + "fernkampf", + "verteidigung", + "fernkampf", + "bossjagd", + "bossjagd" + ], + "seed": 1005, + "swapped": { + "boss": [], + "hero_levels": [ + 0, + 0, + 0, + 5, + 4, + 4 + ], + "lane_losses_at_end": [ + 3, + 0 + ], + "max_combat": 108.999999999996, + "result": "rot", + "rounds": 10, + "timeouts": 0, + "total_gold_curve": [ + 853, + 785, + 985, + 837, + 1008, + 1028, + 1115, + 1480, + 1613, + 1804 + ] + } + }, + { + "bunds": [ + "glutpakt", + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt" + ], + "index": 6, + "matchup": false, + "normal": { + "boss": [ + "8:won:0", + "9:deployed:0" + ], + "hero_levels": [ + 3, + 5, + 3, + 4, + 0, + 0 + ], + "lane_losses_at_end": [ + 2, + 1 + ], + "max_combat": 102.299999999996, + "result": "blau", + "rounds": 9, + "timeouts": 0, + "total_gold_curve": [ + 803, + 755, + 980, + 842, + 1098, + 958, + 1614, + 1529, + 1258 + ] + }, + "profiles": [ + "bossjagd", + "ausgewogen", + "teamhilfe", + "fernkampf", + "teamhilfe", + "ausgewogen" + ], + "seed": 1006, + "swapped": { + "boss": [ + "8:won:1", + "9:deployed:1" + ], + "hero_levels": [ + 4, + 0, + 0, + 3, + 5, + 3 + ], + "lane_losses_at_end": [ + 1, + 2 + ], + "max_combat": 102.299999999996, + "result": "rot", + "rounds": 9, + "timeouts": 0, + "total_gold_curve": [ + 803, + 755, + 980, + 842, + 1098, + 958, + 1614, + 1529, + 1258 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt", + "glutpakt" + ], + "index": 7, + "matchup": false, + "normal": { + "boss": [], + "hero_levels": [ + 3, + 6, + 3, + 5, + 0, + 4 + ], + "lane_losses_at_end": [ + 1, + 1 + ], + "max_combat": 134.549999999996, + "result": "blau", + "rounds": 11, + "timeouts": 0, + "total_gold_curve": [ + 753, + 740, + 965, + 837, + 1028, + 878, + 1342, + 1277, + 1659, + 1321, + 1454 + ] + }, + "profiles": [ + "ausgewogen", + "ausgewogen", + "ausgewogen", + "teamhilfe", + "teamhilfe", + "ausgewogen" + ], + "seed": 1007, + "swapped": { + "boss": [], + "hero_levels": [ + 5, + 0, + 4, + 3, + 6, + 3 + ], + "lane_losses_at_end": [ + 1, + 1 + ], + "max_combat": 134.549999999996, + "result": "rot", + "rounds": 11, + "timeouts": 0, + "total_gold_curve": [ + 753, + 740, + 965, + 837, + 1028, + 878, + 1342, + 1277, + 1659, + 1321, + 1454 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "wurzelbund", + "wurzelbund", + "glutpakt", + "glutpakt", + "glutpakt" + ], + "index": 8, + "matchup": false, + "normal": { + "boss": [ + "12:won:0" + ], + "hero_levels": [ + 3, + 2, + 3, + 3, + 5, + 4 + ], + "lane_losses_at_end": [ + 3, + 0 + ], + "max_combat": 100.749999999996, + "result": "rot", + "rounds": 12, + "timeouts": 0, + "total_gold_curve": [ + 753, + 755, + 875, + 807, + 958, + 1073, + 1100, + 1050, + 1303, + 1310, + 2506, + 1909 + ] + }, + "profiles": [ + "teamhilfe", + "ausgewogen", + "bossjagd", + "druck", + "ausgewogen", + "druck" + ], + "seed": 1008, + "swapped": { + "boss": [ + "12:won:1" + ], + "hero_levels": [ + 3, + 5, + 4, + 3, + 2, + 3 + ], + "lane_losses_at_end": [ + 0, + 3 + ], + "max_combat": 100.749999999996, + "result": "blau", + "rounds": 12, + "timeouts": 0, + "total_gold_curve": [ + 753, + 755, + 875, + 807, + 958, + 1073, + 1100, + 1050, + 1303, + 1310, + 2506, + 1909 + ] + } + }, + { + "bunds": [ + "glutpakt", + "wurzelbund", + "glutpakt", + "glutpakt", + "wurzelbund", + "glutpakt" + ], + "index": 9, + "matchup": false, + "normal": { + "boss": [ + "11:won:1" + ], + "hero_levels": [ + 0, + 4, + 3, + 3, + 5, + 0 + ], + "lane_losses_at_end": [ + 1, + 2 + ], + "max_combat": 157.150000000001, + "result": "blau", + "rounds": 11, + "timeouts": 0, + "total_gold_curve": [ + 758, + 790, + 895, + 882, + 988, + 1028, + 1120, + 1085, + 1492, + 1544, + 2526 + ] + }, + "profiles": [ + "verteidigung", + "druck", + "druck", + "bossjagd", + "ausgewogen", + "teamhilfe" + ], + "seed": 1009, + "swapped": { + "boss": [ + "11:won:0" + ], + "hero_levels": [ + 3, + 5, + 0, + 0, + 4, + 3 + ], + "lane_losses_at_end": [ + 2, + 1 + ], + "max_combat": 157.150000000001, + "result": "rot", + "rounds": 11, + "timeouts": 0, + "total_gold_curve": [ + 758, + 790, + 895, + 882, + 988, + 1028, + 1120, + 1085, + 1492, + 1544, + 2526 + ] + } + }, + { + "bunds": [ + "glutpakt", + "wurzelbund", + "wurzelbund", + "wurzelbund", + "glutpakt", + "glutpakt" + ], + "index": 10, + "matchup": false, + "normal": { + "boss": [ + "12:won:1" + ], + "hero_levels": [ + 5, + 5, + 4, + 3, + 3, + 0 + ], + "lane_losses_at_end": [ + 0, + 2 + ], + "max_combat": 93.0999999999969, + "result": "blau", + "rounds": 12, + "timeouts": 0, + "total_gold_curve": [ + 753, + 755, + 915, + 832, + 983, + 968, + 1040, + 1020, + 1148, + 1530, + 1998, + 1643 + ] + }, + "profiles": [ + "druck", + "ausgewogen", + "ausgewogen", + "teamhilfe", + "bossjagd", + "ausgewogen" + ], + "seed": 1010, + "swapped": { + "boss": [ + "12:won:0" + ], + "hero_levels": [ + 3, + 3, + 0, + 5, + 5, + 4 + ], + "lane_losses_at_end": [ + 2, + 0 + ], + "max_combat": 93.0999999999969, + "result": "rot", + "rounds": 12, + "timeouts": 0, + "total_gold_curve": [ + 753, + 755, + 915, + 832, + 983, + 968, + 1040, + 1020, + 1148, + 1530, + 1998, + 1643 + ] + } + }, + { + "bunds": [ + "glutpakt", + "glutpakt", + "glutpakt", + "glutpakt", + "glutpakt", + "wurzelbund" + ], + "index": 11, + "matchup": false, + "normal": { + "boss": [ + "11:won:0" + ], + "hero_levels": [ + 3, + 3, + 3, + 5, + 2, + 2 + ], + "lane_losses_at_end": [ + 1, + 1 + ], + "max_combat": 125.199999999995, + "result": "rot", + "rounds": 11, + "timeouts": 0, + "total_gold_curve": [ + 853, + 770, + 1005, + 837, + 1028, + 1003, + 1080, + 1435, + 1393, + 2210, + 1509 + ] + }, + "profiles": [ + "bossjagd", + "fernkampf", + "fernkampf", + "teamhilfe", + "verteidigung", + "teamhilfe" + ], + "seed": 1011, + "swapped": { + "boss": [ + "11:won:1" + ], + "hero_levels": [ + 5, + 2, + 2, + 3, + 3, + 3 + ], + "lane_losses_at_end": [ + 1, + 1 + ], + "max_combat": 125.199999999995, + "result": "blau", + "rounds": 11, + "timeouts": 0, + "total_gold_curve": [ + 853, + 770, + 1005, + 837, + 1028, + 1003, + 1080, + 1435, + 1393, + 2210, + 1509 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "glutpakt", + "glutpakt", + "glutpakt", + "glutpakt" + ], + "index": 12, + "matchup": false, + "normal": { + "boss": [], + "hero_levels": [ + 8, + 8, + 6, + 7, + 6, + 7 + ], + "lane_losses_at_end": [ + 1, + 2 + ], + "max_combat": 97.1499999999966, + "result": "blau", + "rounds": 13, + "timeouts": 0, + "total_gold_curve": [ + 903, + 710, + 1020, + 852, + 933, + 1028, + 1255, + 1250, + 1915, + 2022, + 1800, + 2196, + 1372 + ] + }, + "profiles": [ + "fernkampf", + "fernkampf", + "teamhilfe", + "teamhilfe", + "ausgewogen", + "fernkampf" + ], + "seed": 1012, + "swapped": { + "boss": [], + "hero_levels": [ + 7, + 6, + 7, + 8, + 8, + 6 + ], + "lane_losses_at_end": [ + 2, + 1 + ], + "max_combat": 97.1499999999966, + "result": "rot", + "rounds": 13, + "timeouts": 0, + "total_gold_curve": [ + 903, + 710, + 1020, + 852, + 933, + 1028, + 1255, + 1250, + 1915, + 2022, + 1800, + 2196, + 1372 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "wurzelbund", + "wurzelbund", + "glutpakt", + "wurzelbund", + "glutpakt" + ], + "index": 13, + "matchup": false, + "normal": { + "boss": [], + "hero_levels": [ + 5, + 7, + 5, + 7, + 7, + 6 + ], + "lane_losses_at_end": [ + 2, + 0 + ], + "max_combat": 100.949999999996, + "result": "rot", + "rounds": 13, + "timeouts": 0, + "total_gold_curve": [ + 843, + 690, + 890, + 932, + 923, + 1003, + 1165, + 1165, + 1546, + 2068, + 1937, + 1591, + 1903 + ] + }, + "profiles": [ + "teamhilfe", + "druck", + "fernkampf", + "fernkampf", + "druck", + "ausgewogen" + ], + "seed": 1013, + "swapped": { + "boss": [], + "hero_levels": [ + 7, + 7, + 6, + 5, + 7, + 5 + ], + "lane_losses_at_end": [ + 0, + 2 + ], + "max_combat": 100.949999999996, + "result": "blau", + "rounds": 13, + "timeouts": 0, + "total_gold_curve": [ + 843, + 690, + 890, + 932, + 923, + 1003, + 1165, + 1165, + 1546, + 2068, + 1937, + 1591, + 1903 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "glutpakt", + "glutpakt", + "wurzelbund" + ], + "index": 14, + "matchup": false, + "normal": { + "boss": [ + "9:won:0", + "10:deployed:0" + ], + "hero_levels": [ + 3, + 0, + 0, + 2, + 2, + 2 + ], + "lane_losses_at_end": [ + 1, + 1 + ], + "max_combat": 125.599999999995, + "result": "blau", + "rounds": 10, + "timeouts": 0, + "total_gold_curve": [ + 803, + 810, + 920, + 847, + 1063, + 1098, + 1150, + 1300, + 1573, + 1534 + ] + }, + "profiles": [ + "bossjagd", + "druck", + "fernkampf", + "bossjagd", + "bossjagd", + "bossjagd" + ], + "seed": 1014, + "swapped": { + "boss": [ + "9:won:1", + "10:deployed:1" + ], + "hero_levels": [ + 2, + 2, + 2, + 3, + 0, + 0 + ], + "lane_losses_at_end": [ + 1, + 1 + ], + "max_combat": 125.599999999995, + "result": "rot", + "rounds": 10, + "timeouts": 0, + "total_gold_curve": [ + 803, + 810, + 920, + 847, + 1063, + 1098, + 1150, + 1300, + 1573, + 1534 + ] + } + }, + { + "bunds": [ + "glutpakt", + "wurzelbund", + "glutpakt", + "wurzelbund", + "glutpakt", + "glutpakt" + ], + "index": 15, + "matchup": false, + "normal": { + "boss": [], + "hero_levels": [ + 7, + 5, + 4, + 5, + 4, + 4 + ], + "lane_losses_at_end": [ + 0, + 1 + ], + "max_combat": 130.449999999995, + "result": "blau", + "rounds": 12, + "timeouts": 0, + "total_gold_curve": [ + 898, + 705, + 950, + 902, + 988, + 1023, + 1140, + 1335, + 1367, + 2204, + 1627, + 1312 + ] + }, + "profiles": [ + "fernkampf", + "fernkampf", + "verteidigung", + "druck", + "fernkampf", + "ausgewogen" + ], + "seed": 1015, + "swapped": { + "boss": [], + "hero_levels": [ + 5, + 4, + 4, + 7, + 5, + 4 + ], + "lane_losses_at_end": [ + 1, + 0 + ], + "max_combat": 130.449999999995, + "result": "rot", + "rounds": 12, + "timeouts": 0, + "total_gold_curve": [ + 898, + 705, + 950, + 902, + 988, + 1023, + 1140, + 1335, + 1367, + 2204, + 1627, + 1312 + ] + } + }, + { + "bunds": [ + "glutpakt", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt", + "glutpakt" + ], + "index": 16, + "matchup": false, + "normal": { + "boss": [], + "hero_levels": [ + 7, + 5, + 2, + 7, + 5, + 7 + ], + "lane_losses_at_end": [ + 3, + 1 + ], + "max_combat": 117.449999999995, + "result": "rot", + "rounds": 14, + "timeouts": 0, + "total_gold_curve": [ + 758, + 825, + 980, + 812, + 928, + 978, + 1060, + 1100, + 1198, + 1545, + 1990, + 1766, + 2282, + 2477 + ] + }, + "profiles": [ + "teamhilfe", + "verteidigung", + "verteidigung", + "teamhilfe", + "verteidigung", + "ausgewogen" + ], + "seed": 1016, + "swapped": { + "boss": [], + "hero_levels": [ + 7, + 5, + 7, + 7, + 5, + 2 + ], + "lane_losses_at_end": [ + 1, + 3 + ], + "max_combat": 117.449999999995, + "result": "blau", + "rounds": 14, + "timeouts": 0, + "total_gold_curve": [ + 758, + 825, + 980, + 812, + 928, + 978, + 1060, + 1100, + 1198, + 1545, + 1990, + 1766, + 2282, + 2477 + ] + } + }, + { + "bunds": [ + "glutpakt", + "glutpakt", + "glutpakt", + "wurzelbund", + "glutpakt", + "wurzelbund" + ], + "index": 17, + "matchup": false, + "normal": { + "boss": [], + "hero_levels": [ + 5, + 3, + 4, + 0, + 0, + 0 + ], + "lane_losses_at_end": [ + 0, + 2 + ], + "max_combat": 121.549999999995, + "result": "blau", + "rounds": 10, + "timeouts": 0, + "total_gold_curve": [ + 893, + 720, + 940, + 882, + 1033, + 1053, + 1170, + 1495, + 1617, + 1533 + ] + }, + "profiles": [ + "fernkampf", + "verteidigung", + "fernkampf", + "druck", + "fernkampf", + "bossjagd" + ], + "seed": 1017, + "swapped": { + "boss": [], + "hero_levels": [ + 0, + 0, + 0, + 5, + 3, + 4 + ], + "lane_losses_at_end": [ + 2, + 0 + ], + "max_combat": 121.549999999995, + "result": "rot", + "rounds": 10, + "timeouts": 0, + "total_gold_curve": [ + 893, + 720, + 940, + 882, + 1033, + 1053, + 1170, + 1495, + 1617, + 1533 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt", + "glutpakt" + ], + "index": 18, + "matchup": false, + "normal": { + "boss": [ + "9:won:0", + "10:deployed:0" + ], + "hero_levels": [ + 2, + 3, + 3, + 6, + 2, + 3 + ], + "lane_losses_at_end": [ + 2, + 1 + ], + "max_combat": 105.049999999996, + "result": "blau", + "rounds": 10, + "timeouts": 0, + "total_gold_curve": [ + 753, + 780, + 880, + 867, + 898, + 1073, + 1310, + 1235, + 2211, + 1528 + ] + }, + "profiles": [ + "verteidigung", + "ausgewogen", + "bossjagd", + "druck", + "teamhilfe", + "druck" + ], + "seed": 1018, + "swapped": { + "boss": [ + "9:won:1", + "10:deployed:1" + ], + "hero_levels": [ + 6, + 2, + 3, + 2, + 3, + 3 + ], + "lane_losses_at_end": [ + 1, + 2 + ], + "max_combat": 105.049999999996, + "result": "rot", + "rounds": 10, + "timeouts": 0, + "total_gold_curve": [ + 753, + 780, + 880, + 867, + 898, + 1073, + 1310, + 1235, + 2211, + 1528 + ] + } + }, + { + "bunds": [ + "glutpakt", + "glutpakt", + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund" + ], + "index": 19, + "matchup": false, + "normal": { + "boss": [ + "10:won:1" + ], + "hero_levels": [ + 5, + 4, + 5, + 3, + 4, + 1 + ], + "lane_losses_at_end": [ + 1, + 2 + ], + "max_combat": 130.649999999995, + "result": "blau", + "rounds": 10, + "timeouts": 0, + "total_gold_curve": [ + 798, + 770, + 930, + 787, + 958, + 1198, + 1260, + 1564, + 2114, + 1456 + ] + }, + "profiles": [ + "druck", + "teamhilfe", + "teamhilfe", + "fernkampf", + "verteidigung", + "bossjagd" + ], + "seed": 1019, + "swapped": { + "boss": [ + "10:won:0" + ], + "hero_levels": [ + 3, + 4, + 1, + 5, + 4, + 5 + ], + "lane_losses_at_end": [ + 2, + 1 + ], + "max_combat": 130.649999999995, + "result": "rot", + "rounds": 10, + "timeouts": 0, + "total_gold_curve": [ + 798, + 770, + 930, + 787, + 958, + 1198, + 1260, + 1564, + 2114, + 1456 + ] + } + } +] \ No newline at end of file diff --git a/docs/luneraria/logs/m3/matchups-step2-summary.txt b/docs/luneraria/logs/m3/matchups-step2-summary.txt new file mode 100644 index 0000000..bc7d4a6 --- /dev/null +++ b/docs/luneraria/logs/m3/matchups-step2-summary.txt @@ -0,0 +1,14 @@ +Partien 30 · Ergebnisse {'blau': 15, 'rot': 15} · Testlimit 0 · Zeitfenster-Kämpfe 4 · längster Kampf 180 s +Wellen bis Ende: min 6 · median 10 · max 16 +Seitentausch: dieselbe Zusammensetzung gewinnt beide Male in 15/15 Paaren +Profile (Siege/Einsätze): {'ausgewogen': '24/30 (80 %)', 'bossjagd': '0/30 (0 %)', 'druck': '12/30 (40 %)', 'fernkampf': '18/30 (60 %)', 'teamhilfe': '30/30 (100 %)', 'verteidigung': '6/30 (20 %)'} +Bünde: {'glutpakt': '30/60 (50 %)', 'wurzelbund': '60/120 (50 %)'} +Boss: {'won': 8, 'deployed': 4} · Heldenstufen am Ende (Median): 4 +Duelle (Siege Zeile gegen Spalte, je 2 Partien mit Seitentausch): + ausgew bossja druck fernka teamhi vertei gesamt +ausgewogen - 2 2 2 0 2 8/10 +bossjagd 0 - 0 0 0 0 0/10 +druck 0 2 - 0 0 2 4/10 +fernkampf 0 2 2 - 0 2 6/10 +teamhilfe 2 2 2 2 - 2 10/10 +verteidigung 0 2 0 0 0 - 2/10 diff --git a/docs/luneraria/logs/m3/matchups-step2.json b/docs/luneraria/logs/m3/matchups-step2.json new file mode 100644 index 0000000..ba4b9c4 --- /dev/null +++ b/docs/luneraria/logs/m3/matchups-step2.json @@ -0,0 +1,1271 @@ +[ + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt", + "wurzelbund" + ], + "index": 0, + "matchup": true, + "normal": { + "boss": [], + "hero_levels": [ + 3, + 5, + 5, + 0, + 0, + 0 + ], + "lane_losses_at_end": [ + 0, + 3 + ], + "max_combat": 98.2999999999966, + "result": "blau", + "rounds": 11, + "timeouts": 0, + "total_gold_curve": [ + 748, + 785, + 960, + 992, + 1013, + 923, + 1095, + 1085, + 1448, + 1910, + 1423 + ] + }, + "profiles": [ + "ausgewogen", + "ausgewogen", + "ausgewogen", + "verteidigung", + "verteidigung", + "verteidigung" + ], + "seed": 2000, + "swapped": { + "boss": [], + "hero_levels": [ + 0, + 0, + 0, + 3, + 5, + 5 + ], + "lane_losses_at_end": [ + 3, + 0 + ], + "max_combat": 98.2999999999966, + "result": "rot", + "rounds": 11, + "timeouts": 0, + "total_gold_curve": [ + 748, + 785, + 960, + 992, + 1013, + 923, + 1095, + 1085, + 1448, + 1910, + 1423 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt", + "wurzelbund" + ], + "index": 1, + "matchup": true, + "normal": { + "boss": [], + "hero_levels": [ + 4, + 5, + 6, + 3, + 5, + 3 + ], + "lane_losses_at_end": [ + 0, + 2 + ], + "max_combat": 104.049999999996, + "result": "blau", + "rounds": 13, + "timeouts": 0, + "total_gold_curve": [ + 748, + 710, + 820, + 927, + 918, + 998, + 1145, + 980, + 1143, + 1515, + 1583, + 1902, + 1577 + ] + }, + "profiles": [ + "ausgewogen", + "ausgewogen", + "ausgewogen", + "druck", + "druck", + "druck" + ], + "seed": 2001, + "swapped": { + "boss": [], + "hero_levels": [ + 3, + 5, + 3, + 4, + 5, + 6 + ], + "lane_losses_at_end": [ + 2, + 0 + ], + "max_combat": 104.049999999996, + "result": "rot", + "rounds": 13, + "timeouts": 0, + "total_gold_curve": [ + 748, + 710, + 820, + 927, + 918, + 998, + 1145, + 980, + 1143, + 1515, + 1583, + 1902, + 1577 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt", + "wurzelbund" + ], + "index": 2, + "matchup": true, + "normal": { + "boss": [], + "hero_levels": [ + 5, + 7, + 7, + 7, + 6, + 6 + ], + "lane_losses_at_end": [ + 0, + 3 + ], + "max_combat": 180.000000000007, + "result": "blau", + "rounds": 14, + "timeouts": 1, + "total_gold_curve": [ + 893, + 670, + 965, + 837, + 978, + 938, + 1050, + 1260, + 1178, + 1930, + 1503, + 1942, + 2973, + 774 + ] + }, + "profiles": [ + "ausgewogen", + "ausgewogen", + "ausgewogen", + "fernkampf", + "fernkampf", + "fernkampf" + ], + "seed": 2002, + "swapped": { + "boss": [], + "hero_levels": [ + 7, + 6, + 6, + 5, + 7, + 7 + ], + "lane_losses_at_end": [ + 3, + 0 + ], + "max_combat": 180.000000000007, + "result": "rot", + "rounds": 14, + "timeouts": 1, + "total_gold_curve": [ + 893, + 670, + 965, + 837, + 978, + 938, + 1050, + 1260, + 1178, + 1930, + 1503, + 1942, + 2973, + 774 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt", + "wurzelbund" + ], + "index": 3, + "matchup": true, + "normal": { + "boss": [], + "hero_levels": [ + 0, + 0, + 0, + 3, + 3, + 2 + ], + "lane_losses_at_end": [ + 1, + 1 + ], + "max_combat": 126.749999999995, + "result": "rot", + "rounds": 8, + "timeouts": 0, + "total_gold_curve": [ + 748, + 740, + 960, + 802, + 1048, + 1098, + 1485, + 971 + ] + }, + "profiles": [ + "ausgewogen", + "ausgewogen", + "ausgewogen", + "teamhilfe", + "teamhilfe", + "teamhilfe" + ], + "seed": 2003, + "swapped": { + "boss": [], + "hero_levels": [ + 3, + 3, + 2, + 0, + 0, + 0 + ], + "lane_losses_at_end": [ + 1, + 1 + ], + "max_combat": 126.749999999995, + "result": "blau", + "rounds": 8, + "timeouts": 0, + "total_gold_curve": [ + 748, + 740, + 960, + 802, + 1048, + 1098, + 1485, + 971 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt", + "wurzelbund" + ], + "index": 4, + "matchup": true, + "normal": { + "boss": [ + "9:won:1" + ], + "hero_levels": [ + 0, + 0, + 0, + 0, + 0, + 0 + ], + "lane_losses_at_end": [ + 0, + 2 + ], + "max_combat": 128.249999999995, + "result": "blau", + "rounds": 9, + "timeouts": 0, + "total_gold_curve": [ + 748, + 785, + 930, + 842, + 983, + 993, + 1105, + 1115, + 1833 + ] + }, + "profiles": [ + "ausgewogen", + "ausgewogen", + "ausgewogen", + "bossjagd", + "bossjagd", + "bossjagd" + ], + "seed": 2004, + "swapped": { + "boss": [ + "9:won:0" + ], + "hero_levels": [ + 0, + 0, + 0, + 0, + 0, + 0 + ], + "lane_losses_at_end": [ + 2, + 0 + ], + "max_combat": 128.249999999995, + "result": "rot", + "rounds": 9, + "timeouts": 0, + "total_gold_curve": [ + 748, + 785, + 930, + 842, + 983, + 993, + 1105, + 1115, + 1833 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt", + "wurzelbund" + ], + "index": 5, + "matchup": true, + "normal": { + "boss": [], + "hero_levels": [ + 5, + 4, + 4, + 9, + 9, + 9 + ], + "lane_losses_at_end": [ + 3, + 0 + ], + "max_combat": 102.449999999996, + "result": "rot", + "rounds": 16, + "timeouts": 0, + "total_gold_curve": [ + 748, + 785, + 850, + 1077, + 1013, + 1043, + 1230, + 1045, + 1103, + 1325, + 1563, + 1602, + 1778, + 2065, + 2027, + 2791 + ] + }, + "profiles": [ + "verteidigung", + "verteidigung", + "verteidigung", + "druck", + "druck", + "druck" + ], + "seed": 2005, + "swapped": { + "boss": [], + "hero_levels": [ + 9, + 9, + 9, + 5, + 4, + 4 + ], + "lane_losses_at_end": [ + 0, + 3 + ], + "max_combat": 102.449999999996, + "result": "blau", + "rounds": 16, + "timeouts": 0, + "total_gold_curve": [ + 748, + 785, + 850, + 1077, + 1013, + 1043, + 1230, + 1045, + 1103, + 1325, + 1563, + 1602, + 1778, + 2065, + 2027, + 2791 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt", + "wurzelbund" + ], + "index": 6, + "matchup": true, + "normal": { + "boss": [], + "hero_levels": [ + 0, + 0, + 0, + 7, + 7, + 7 + ], + "lane_losses_at_end": [ + 3, + 0 + ], + "max_combat": 89.0999999999971, + "result": "rot", + "rounds": 12, + "timeouts": 0, + "total_gold_curve": [ + 893, + 745, + 995, + 987, + 1073, + 983, + 1135, + 1475, + 1328, + 1820, + 1678, + 1230 + ] + }, + "profiles": [ + "verteidigung", + "verteidigung", + "verteidigung", + "fernkampf", + "fernkampf", + "fernkampf" + ], + "seed": 2006, + "swapped": { + "boss": [], + "hero_levels": [ + 7, + 7, + 7, + 0, + 0, + 0 + ], + "lane_losses_at_end": [ + 0, + 3 + ], + "max_combat": 89.0999999999971, + "result": "blau", + "rounds": 12, + "timeouts": 0, + "total_gold_curve": [ + 893, + 745, + 995, + 987, + 1073, + 983, + 1135, + 1475, + 1328, + 1820, + 1678, + 1230 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt", + "wurzelbund" + ], + "index": 7, + "matchup": true, + "normal": { + "boss": [], + "hero_levels": [ + 0, + 0, + 0, + 3, + 2, + 2 + ], + "lane_losses_at_end": [ + 1, + 0 + ], + "max_combat": 127.699999999995, + "result": "rot", + "rounds": 8, + "timeouts": 0, + "total_gold_curve": [ + 748, + 815, + 990, + 1102, + 1043, + 1213, + 1570, + 896 + ] + }, + "profiles": [ + "verteidigung", + "verteidigung", + "verteidigung", + "teamhilfe", + "teamhilfe", + "teamhilfe" + ], + "seed": 2007, + "swapped": { + "boss": [], + "hero_levels": [ + 3, + 2, + 2, + 0, + 0, + 0 + ], + "lane_losses_at_end": [ + 0, + 1 + ], + "max_combat": 127.699999999995, + "result": "blau", + "rounds": 8, + "timeouts": 0, + "total_gold_curve": [ + 748, + 815, + 990, + 1102, + 1043, + 1213, + 1570, + 896 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt", + "wurzelbund" + ], + "index": 8, + "matchup": true, + "normal": { + "boss": [ + "9:won:1", + "10:deployed:1" + ], + "hero_levels": [ + 2, + 2, + 3, + 0, + 0, + 0 + ], + "lane_losses_at_end": [ + 1, + 3 + ], + "max_combat": 180.000000000007, + "result": "blau", + "rounds": 10, + "timeouts": 1, + "total_gold_curve": [ + 748, + 860, + 960, + 992, + 1078, + 1038, + 1190, + 1180, + 2463, + 1326 + ] + }, + "profiles": [ + "verteidigung", + "verteidigung", + "verteidigung", + "bossjagd", + "bossjagd", + "bossjagd" + ], + "seed": 2008, + "swapped": { + "boss": [ + "9:won:0", + "10:deployed:0" + ], + "hero_levels": [ + 0, + 0, + 0, + 2, + 2, + 3 + ], + "lane_losses_at_end": [ + 3, + 1 + ], + "max_combat": 180.000000000007, + "result": "rot", + "rounds": 10, + "timeouts": 1, + "total_gold_curve": [ + 748, + 860, + 960, + 992, + 1078, + 1038, + 1190, + 1180, + 2463, + 1326 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt", + "wurzelbund" + ], + "index": 9, + "matchup": true, + "normal": { + "boss": [], + "hero_levels": [ + 0, + 3, + 0, + 7, + 7, + 7 + ], + "lane_losses_at_end": [ + 3, + 0 + ], + "max_combat": 98.5999999999965, + "result": "rot", + "rounds": 12, + "timeouts": 0, + "total_gold_curve": [ + 893, + 670, + 855, + 922, + 978, + 1058, + 1185, + 1370, + 1323, + 1575, + 1618, + 1628 + ] + }, + "profiles": [ + "druck", + "druck", + "druck", + "fernkampf", + "fernkampf", + "fernkampf" + ], + "seed": 2009, + "swapped": { + "boss": [], + "hero_levels": [ + 7, + 7, + 7, + 0, + 3, + 0 + ], + "lane_losses_at_end": [ + 0, + 3 + ], + "max_combat": 98.5999999999965, + "result": "blau", + "rounds": 12, + "timeouts": 0, + "total_gold_curve": [ + 893, + 670, + 855, + 922, + 978, + 1058, + 1185, + 1370, + 1323, + 1575, + 1618, + 1628 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt", + "wurzelbund" + ], + "index": 10, + "matchup": true, + "normal": { + "boss": [], + "hero_levels": [ + 0, + 0, + 0, + 0, + 0, + 0 + ], + "lane_losses_at_end": [ + 1, + 1 + ], + "max_combat": 98.2499999999966, + "result": "rot", + "rounds": 6, + "timeouts": 0, + "total_gold_curve": [ + 748, + 740, + 850, + 1247, + 1313, + 896 + ] + }, + "profiles": [ + "druck", + "druck", + "druck", + "teamhilfe", + "teamhilfe", + "teamhilfe" + ], + "seed": 2010, + "swapped": { + "boss": [], + "hero_levels": [ + 0, + 0, + 0, + 0, + 0, + 0 + ], + "lane_losses_at_end": [ + 1, + 1 + ], + "max_combat": 98.2499999999966, + "result": "blau", + "rounds": 6, + "timeouts": 0, + "total_gold_curve": [ + 748, + 740, + 850, + 1247, + 1313, + 896 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt", + "wurzelbund" + ], + "index": 11, + "matchup": true, + "normal": { + "boss": [ + "9:won:1", + "10:deployed:1" + ], + "hero_levels": [ + 2, + 3, + 3, + 0, + 0, + 0 + ], + "lane_losses_at_end": [ + 1, + 3 + ], + "max_combat": 120.699999999995, + "result": "blau", + "rounds": 10, + "timeouts": 0, + "total_gold_curve": [ + 748, + 785, + 820, + 927, + 983, + 1113, + 1240, + 1075, + 2188, + 2794 + ] + }, + "profiles": [ + "druck", + "druck", + "druck", + "bossjagd", + "bossjagd", + "bossjagd" + ], + "seed": 2011, + "swapped": { + "boss": [ + "9:won:0", + "10:deployed:0" + ], + "hero_levels": [ + 0, + 0, + 0, + 2, + 3, + 3 + ], + "lane_losses_at_end": [ + 3, + 1 + ], + "max_combat": 120.699999999995, + "result": "rot", + "rounds": 10, + "timeouts": 0, + "total_gold_curve": [ + 748, + 785, + 820, + 927, + 983, + 1113, + 1240, + 1075, + 2188, + 2794 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt", + "wurzelbund" + ], + "index": 12, + "matchup": true, + "normal": { + "boss": [], + "hero_levels": [ + 2, + 4, + 3, + 3, + 0, + 2 + ], + "lane_losses_at_end": [ + 1, + 2 + ], + "max_combat": 87.0999999999972, + "result": "rot", + "rounds": 8, + "timeouts": 0, + "total_gold_curve": [ + 893, + 700, + 995, + 1097, + 1148, + 1288, + 1732, + 1343 + ] + }, + "profiles": [ + "fernkampf", + "fernkampf", + "fernkampf", + "teamhilfe", + "teamhilfe", + "teamhilfe" + ], + "seed": 2012, + "swapped": { + "boss": [], + "hero_levels": [ + 3, + 0, + 2, + 2, + 4, + 3 + ], + "lane_losses_at_end": [ + 2, + 1 + ], + "max_combat": 87.0999999999972, + "result": "blau", + "rounds": 8, + "timeouts": 0, + "total_gold_curve": [ + 893, + 700, + 995, + 1097, + 1148, + 1288, + 1732, + 1343 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt", + "wurzelbund" + ], + "index": 13, + "matchup": true, + "normal": { + "boss": [ + "9:won:1" + ], + "hero_levels": [ + 3, + 3, + 3, + 0, + 0, + 0 + ], + "lane_losses_at_end": [ + 0, + 3 + ], + "max_combat": 81.8999999999975, + "result": "blau", + "rounds": 9, + "timeouts": 0, + "total_gold_curve": [ + 893, + 745, + 965, + 837, + 1043, + 1053, + 1145, + 1655, + 1703 + ] + }, + "profiles": [ + "fernkampf", + "fernkampf", + "fernkampf", + "bossjagd", + "bossjagd", + "bossjagd" + ], + "seed": 2013, + "swapped": { + "boss": [ + "9:won:0" + ], + "hero_levels": [ + 0, + 0, + 0, + 3, + 3, + 3 + ], + "lane_losses_at_end": [ + 3, + 0 + ], + "max_combat": 81.8999999999975, + "result": "rot", + "rounds": 9, + "timeouts": 0, + "total_gold_curve": [ + 893, + 745, + 965, + 837, + 1043, + 1053, + 1145, + 1655, + 1703 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt", + "wurzelbund" + ], + "index": 14, + "matchup": true, + "normal": { + "boss": [], + "hero_levels": [ + 0, + 0, + 0, + 0, + 0, + 0 + ], + "lane_losses_at_end": [ + 1, + 1 + ], + "max_combat": 100.349999999996, + "result": "blau", + "rounds": 6, + "timeouts": 0, + "total_gold_curve": [ + 748, + 815, + 1110, + 1207, + 1138, + 927 + ] + }, + "profiles": [ + "teamhilfe", + "teamhilfe", + "teamhilfe", + "bossjagd", + "bossjagd", + "bossjagd" + ], + "seed": 2014, + "swapped": { + "boss": [], + "hero_levels": [ + 0, + 0, + 0, + 0, + 0, + 0 + ], + "lane_losses_at_end": [ + 1, + 1 + ], + "max_combat": 100.349999999996, + "result": "rot", + "rounds": 6, + "timeouts": 0, + "total_gold_curve": [ + 748, + 815, + 1110, + 1207, + 1138, + 927 + ] + } + } +] \ No newline at end of file diff --git a/docs/luneraria/logs/m3/matchups-step2.log b/docs/luneraria/logs/m3/matchups-step2.log new file mode 100644 index 0000000..86aa3b3 --- /dev/null +++ b/docs/luneraria/logs/m3/matchups-step2.log @@ -0,0 +1,21 @@ +Godot Engine v4.7.2.stable.fedora.ed1daf0bf - https://godotengine.org + +BALANCE 00 ["ausgewogen", "ausgewogen", "ausgewogen"] | ["verteidigung", "verteidigung", "verteidigung"] -> blau (11) / swapped rot (11) +BALANCE 01 ["ausgewogen", "ausgewogen", "ausgewogen"] | ["druck", "druck", "druck"] -> blau (13) / swapped rot (13) +BALANCE 02 ["ausgewogen", "ausgewogen", "ausgewogen"] | ["fernkampf", "fernkampf", "fernkampf"] -> blau (14) / swapped rot (14) +BALANCE 03 ["ausgewogen", "ausgewogen", "ausgewogen"] | ["teamhilfe", "teamhilfe", "teamhilfe"] -> rot (8) / swapped blau (8) +BALANCE 04 ["ausgewogen", "ausgewogen", "ausgewogen"] | ["bossjagd", "bossjagd", "bossjagd"] -> blau (9) / swapped rot (9) +BALANCE 05 ["verteidigung", "verteidigung", "verteidigung"] | ["druck", "druck", "druck"] -> rot (16) / swapped blau (16) +BALANCE 06 ["verteidigung", "verteidigung", "verteidigung"] | ["fernkampf", "fernkampf", "fernkampf"] -> rot (12) / swapped blau (12) +BALANCE 07 ["verteidigung", "verteidigung", "verteidigung"] | ["teamhilfe", "teamhilfe", "teamhilfe"] -> rot (8) / swapped blau (8) +BALANCE 08 ["verteidigung", "verteidigung", "verteidigung"] | ["bossjagd", "bossjagd", "bossjagd"] -> blau (10) / swapped rot (10) +BALANCE 09 ["druck", "druck", "druck"] | ["fernkampf", "fernkampf", "fernkampf"] -> rot (12) / swapped blau (12) +BALANCE 10 ["druck", "druck", "druck"] | ["teamhilfe", "teamhilfe", "teamhilfe"] -> rot (6) / swapped blau (6) +BALANCE 11 ["druck", "druck", "druck"] | ["bossjagd", "bossjagd", "bossjagd"] -> blau (10) / swapped rot (10) +BALANCE 12 ["fernkampf", "fernkampf", "fernkampf"] | ["teamhilfe", "teamhilfe", "teamhilfe"] -> rot (8) / swapped blau (8) +BALANCE 13 ["fernkampf", "fernkampf", "fernkampf"] | ["bossjagd", "bossjagd", "bossjagd"] -> blau (9) / swapped rot (9) +BALANCE 14 ["teamhilfe", "teamhilfe", "teamhilfe"] | ["bossjagd", "bossjagd", "bossjagd"] -> blau (6) / swapped rot (6) +WARNING: 10 ObjectDB instances were leaked at exit (run with `--verbose` for details). + at: cleanup (core/object/object.cpp:2536) +ERROR: 1 resources still in use at exit (run with --verbose for details). + at: clear (core/io/resource.cpp:822) diff --git a/docs/luneraria/logs/m3/matchups-step3-summary.txt b/docs/luneraria/logs/m3/matchups-step3-summary.txt new file mode 100644 index 0000000..dc0eb0c --- /dev/null +++ b/docs/luneraria/logs/m3/matchups-step3-summary.txt @@ -0,0 +1,14 @@ +Partien 30 · Ergebnisse {'blau': 15, 'rot': 15} · Testlimit 0 · Zeitfenster-Kämpfe 0 · längster Kampf 169 s +Wellen bis Ende: min 9 · median 10 · max 14 +Seitentausch: dieselbe Zusammensetzung gewinnt beide Male in 15/15 Paaren +Profile (Siege/Einsätze): {'ausgewogen': '30/30 (100 %)', 'bossjagd': '0/30 (0 %)', 'druck': '6/30 (20 %)', 'fernkampf': '12/30 (40 %)', 'teamhilfe': '18/30 (60 %)', 'verteidigung': '24/30 (80 %)'} +Bünde: {'glutpakt': '30/60 (50 %)', 'wurzelbund': '60/120 (50 %)'} +Boss: {'won': 10, 'deployed': 2} · Heldenstufen am Ende (Median): 5 +Duelle (Siege Zeile gegen Spalte, je 2 Partien mit Seitentausch): + ausgew bossja druck fernka teamhi vertei gesamt +ausgewogen - 2 2 2 2 2 10/10 +bossjagd 0 - 0 0 0 0 0/10 +druck 0 2 - 0 0 0 2/10 +fernkampf 0 2 2 - 0 0 4/10 +teamhilfe 0 2 2 2 - 0 6/10 +verteidigung 0 2 2 2 2 - 8/10 diff --git a/docs/luneraria/logs/m3/matchups-step3.json b/docs/luneraria/logs/m3/matchups-step3.json new file mode 100644 index 0000000..05a12ef --- /dev/null +++ b/docs/luneraria/logs/m3/matchups-step3.json @@ -0,0 +1,1285 @@ +[ + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt", + "wurzelbund" + ], + "index": 0, + "matchup": true, + "normal": { + "boss": [], + "hero_levels": [ + 6, + 4, + 3, + 0, + 3, + 0 + ], + "lane_losses_at_end": [ + 0, + 3 + ], + "max_combat": 109.249999999996, + "result": "blau", + "rounds": 13, + "timeouts": 0, + "total_gold_curve": [ + 748, + 785, + 930, + 802, + 943, + 903, + 1015, + 1025, + 1153, + 1425, + 1679, + 1892, + 2444 + ] + }, + "profiles": [ + "ausgewogen", + "ausgewogen", + "ausgewogen", + "verteidigung", + "verteidigung", + "verteidigung" + ], + "seed": 2000, + "swapped": { + "boss": [], + "hero_levels": [ + 0, + 3, + 0, + 6, + 4, + 3 + ], + "lane_losses_at_end": [ + 3, + 0 + ], + "max_combat": 109.249999999996, + "result": "rot", + "rounds": 13, + "timeouts": 0, + "total_gold_curve": [ + 748, + 785, + 930, + 802, + 943, + 903, + 1015, + 1025, + 1153, + 1425, + 1679, + 1892, + 2444 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt", + "wurzelbund" + ], + "index": 1, + "matchup": true, + "normal": { + "boss": [], + "hero_levels": [ + 5, + 5, + 4, + 0, + 0, + 0 + ], + "lane_losses_at_end": [ + 0, + 3 + ], + "max_combat": 107.949999999996, + "result": "blau", + "rounds": 11, + "timeouts": 0, + "total_gold_curve": [ + 748, + 710, + 820, + 927, + 918, + 998, + 1145, + 1370, + 1521, + 1853, + 1646 + ] + }, + "profiles": [ + "ausgewogen", + "ausgewogen", + "ausgewogen", + "druck", + "druck", + "druck" + ], + "seed": 2001, + "swapped": { + "boss": [], + "hero_levels": [ + 0, + 0, + 0, + 5, + 5, + 4 + ], + "lane_losses_at_end": [ + 3, + 0 + ], + "max_combat": 107.949999999996, + "result": "rot", + "rounds": 11, + "timeouts": 0, + "total_gold_curve": [ + 748, + 710, + 820, + 927, + 918, + 998, + 1145, + 1370, + 1521, + 1853, + 1646 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt", + "wurzelbund" + ], + "index": 2, + "matchup": true, + "normal": { + "boss": [], + "hero_levels": [ + 5, + 7, + 7, + 7, + 7, + 7 + ], + "lane_losses_at_end": [ + 1, + 3 + ], + "max_combat": 97.6499999999966, + "result": "blau", + "rounds": 14, + "timeouts": 0, + "total_gold_curve": [ + 893, + 670, + 965, + 837, + 978, + 938, + 1050, + 1260, + 1178, + 1930, + 1503, + 2002, + 2452, + 1929 + ] + }, + "profiles": [ + "ausgewogen", + "ausgewogen", + "ausgewogen", + "fernkampf", + "fernkampf", + "fernkampf" + ], + "seed": 2002, + "swapped": { + "boss": [], + "hero_levels": [ + 7, + 7, + 7, + 5, + 7, + 7 + ], + "lane_losses_at_end": [ + 3, + 1 + ], + "max_combat": 97.6499999999966, + "result": "rot", + "rounds": 14, + "timeouts": 0, + "total_gold_curve": [ + 893, + 670, + 965, + 837, + 978, + 938, + 1050, + 1260, + 1178, + 1930, + 1503, + 2002, + 2452, + 1929 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt", + "wurzelbund" + ], + "index": 3, + "matchup": true, + "normal": { + "boss": [], + "hero_levels": [ + 3, + 5, + 5, + 3, + 2, + 2 + ], + "lane_losses_at_end": [ + 1, + 2 + ], + "max_combat": 110.949999999996, + "result": "blau", + "rounds": 10, + "timeouts": 0, + "total_gold_curve": [ + 748, + 740, + 960, + 802, + 1048, + 858, + 1474, + 1389, + 2261, + 1133 + ] + }, + "profiles": [ + "ausgewogen", + "ausgewogen", + "ausgewogen", + "teamhilfe", + "teamhilfe", + "teamhilfe" + ], + "seed": 2003, + "swapped": { + "boss": [], + "hero_levels": [ + 3, + 2, + 2, + 3, + 5, + 5 + ], + "lane_losses_at_end": [ + 2, + 1 + ], + "max_combat": 110.949999999996, + "result": "rot", + "rounds": 10, + "timeouts": 0, + "total_gold_curve": [ + 748, + 740, + 960, + 802, + 1048, + 858, + 1474, + 1389, + 2261, + 1133 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt", + "wurzelbund" + ], + "index": 4, + "matchup": true, + "normal": { + "boss": [ + "9:won:1" + ], + "hero_levels": [ + 0, + 0, + 0, + 0, + 0, + 0 + ], + "lane_losses_at_end": [ + 0, + 3 + ], + "max_combat": 101.749999999996, + "result": "blau", + "rounds": 9, + "timeouts": 0, + "total_gold_curve": [ + 748, + 785, + 930, + 842, + 983, + 993, + 1105, + 1115, + 2125 + ] + }, + "profiles": [ + "ausgewogen", + "ausgewogen", + "ausgewogen", + "bossjagd", + "bossjagd", + "bossjagd" + ], + "seed": 2004, + "swapped": { + "boss": [ + "9:won:0" + ], + "hero_levels": [ + 0, + 0, + 0, + 0, + 0, + 0 + ], + "lane_losses_at_end": [ + 3, + 0 + ], + "max_combat": 101.749999999996, + "result": "rot", + "rounds": 9, + "timeouts": 0, + "total_gold_curve": [ + 748, + 785, + 930, + 842, + 983, + 993, + 1105, + 1115, + 2125 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt", + "wurzelbund" + ], + "index": 5, + "matchup": true, + "normal": { + "boss": [], + "hero_levels": [ + 4, + 6, + 6, + 0, + 0, + 0 + ], + "lane_losses_at_end": [ + 0, + 3 + ], + "max_combat": 105.349999999996, + "result": "blau", + "rounds": 11, + "timeouts": 0, + "total_gold_curve": [ + 748, + 785, + 820, + 887, + 943, + 1023, + 1150, + 1375, + 1576, + 1623, + 1544 + ] + }, + "profiles": [ + "verteidigung", + "verteidigung", + "verteidigung", + "druck", + "druck", + "druck" + ], + "seed": 2005, + "swapped": { + "boss": [], + "hero_levels": [ + 0, + 0, + 0, + 4, + 6, + 6 + ], + "lane_losses_at_end": [ + 3, + 0 + ], + "max_combat": 105.349999999996, + "result": "rot", + "rounds": 11, + "timeouts": 0, + "total_gold_curve": [ + 748, + 785, + 820, + 887, + 943, + 1023, + 1150, + 1375, + 1576, + 1623, + 1544 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt", + "wurzelbund" + ], + "index": 6, + "matchup": true, + "normal": { + "boss": [], + "hero_levels": [ + 5, + 6, + 7, + 7, + 7, + 7 + ], + "lane_losses_at_end": [ + 1, + 2 + ], + "max_combat": 109.149999999996, + "result": "blau", + "rounds": 14, + "timeouts": 0, + "total_gold_curve": [ + 893, + 745, + 965, + 797, + 1003, + 963, + 1055, + 1115, + 1213, + 1985, + 1839, + 1918, + 2075, + 1948 + ] + }, + "profiles": [ + "verteidigung", + "verteidigung", + "verteidigung", + "fernkampf", + "fernkampf", + "fernkampf" + ], + "seed": 2006, + "swapped": { + "boss": [], + "hero_levels": [ + 7, + 7, + 7, + 5, + 6, + 7 + ], + "lane_losses_at_end": [ + 2, + 1 + ], + "max_combat": 109.149999999996, + "result": "rot", + "rounds": 14, + "timeouts": 0, + "total_gold_curve": [ + 893, + 745, + 965, + 797, + 1003, + 963, + 1055, + 1115, + 1213, + 1985, + 1839, + 1918, + 2075, + 1948 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt", + "wurzelbund" + ], + "index": 7, + "matchup": true, + "normal": { + "boss": [], + "hero_levels": [ + 3, + 5, + 5, + 0, + 0, + 0 + ], + "lane_losses_at_end": [ + 1, + 2 + ], + "max_combat": 95.8999999999967, + "result": "blau", + "rounds": 9, + "timeouts": 0, + "total_gold_curve": [ + 748, + 815, + 960, + 762, + 1073, + 883, + 1779, + 1564, + 1173 + ] + }, + "profiles": [ + "verteidigung", + "verteidigung", + "verteidigung", + "teamhilfe", + "teamhilfe", + "teamhilfe" + ], + "seed": 2007, + "swapped": { + "boss": [], + "hero_levels": [ + 0, + 0, + 0, + 3, + 5, + 5 + ], + "lane_losses_at_end": [ + 2, + 1 + ], + "max_combat": 95.8999999999967, + "result": "rot", + "rounds": 9, + "timeouts": 0, + "total_gold_curve": [ + 748, + 815, + 960, + 762, + 1073, + 883, + 1779, + 1564, + 1173 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt", + "wurzelbund" + ], + "index": 8, + "matchup": true, + "normal": { + "boss": [ + "9:won:1" + ], + "hero_levels": [ + 3, + 3, + 3, + 0, + 0, + 0 + ], + "lane_losses_at_end": [ + 0, + 3 + ], + "max_combat": 85.2499999999973, + "result": "blau", + "rounds": 9, + "timeouts": 0, + "total_gold_curve": [ + 748, + 860, + 930, + 802, + 1008, + 1018, + 1110, + 1420, + 1770 + ] + }, + "profiles": [ + "verteidigung", + "verteidigung", + "verteidigung", + "bossjagd", + "bossjagd", + "bossjagd" + ], + "seed": 2008, + "swapped": { + "boss": [ + "9:won:0" + ], + "hero_levels": [ + 0, + 0, + 0, + 3, + 3, + 3 + ], + "lane_losses_at_end": [ + 3, + 0 + ], + "max_combat": 85.2499999999973, + "result": "rot", + "rounds": 9, + "timeouts": 0, + "total_gold_curve": [ + 748, + 860, + 930, + 802, + 1008, + 1018, + 1110, + 1420, + 1770 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt", + "wurzelbund" + ], + "index": 9, + "matchup": true, + "normal": { + "boss": [], + "hero_levels": [ + 0, + 0, + 0, + 5, + 5, + 5 + ], + "lane_losses_at_end": [ + 3, + 0 + ], + "max_combat": 162.200000000003, + "result": "rot", + "rounds": 10, + "timeouts": 0, + "total_gold_curve": [ + 893, + 670, + 855, + 922, + 978, + 1058, + 1185, + 1610, + 1651, + 1522 + ] + }, + "profiles": [ + "druck", + "druck", + "druck", + "fernkampf", + "fernkampf", + "fernkampf" + ], + "seed": 2009, + "swapped": { + "boss": [], + "hero_levels": [ + 5, + 5, + 5, + 0, + 0, + 0 + ], + "lane_losses_at_end": [ + 0, + 3 + ], + "max_combat": 162.200000000003, + "result": "blau", + "rounds": 10, + "timeouts": 0, + "total_gold_curve": [ + 893, + 670, + 855, + 922, + 978, + 1058, + 1185, + 1610, + 1651, + 1522 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt", + "wurzelbund" + ], + "index": 10, + "matchup": true, + "normal": { + "boss": [], + "hero_levels": [ + 5, + 3, + 6, + 4, + 3, + 2 + ], + "lane_losses_at_end": [ + 1, + 1 + ], + "max_combat": 102.899999999996, + "result": "rot", + "rounds": 10, + "timeouts": 0, + "total_gold_curve": [ + 748, + 740, + 850, + 887, + 1048, + 978, + 1609, + 1309, + 2030, + 1731 + ] + }, + "profiles": [ + "druck", + "druck", + "druck", + "teamhilfe", + "teamhilfe", + "teamhilfe" + ], + "seed": 2010, + "swapped": { + "boss": [], + "hero_levels": [ + 4, + 3, + 2, + 5, + 3, + 6 + ], + "lane_losses_at_end": [ + 1, + 1 + ], + "max_combat": 102.899999999996, + "result": "blau", + "rounds": 10, + "timeouts": 0, + "total_gold_curve": [ + 748, + 740, + 850, + 887, + 1048, + 978, + 1609, + 1309, + 2030, + 1731 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt", + "wurzelbund" + ], + "index": 11, + "matchup": true, + "normal": { + "boss": [ + "9:won:1" + ], + "hero_levels": [ + 0, + 0, + 0, + 0, + 0, + 0 + ], + "lane_losses_at_end": [ + 0, + 3 + ], + "max_combat": 110.649999999996, + "result": "blau", + "rounds": 9, + "timeouts": 0, + "total_gold_curve": [ + 748, + 785, + 820, + 927, + 983, + 1113, + 1240, + 1165, + 2307 + ] + }, + "profiles": [ + "druck", + "druck", + "druck", + "bossjagd", + "bossjagd", + "bossjagd" + ], + "seed": 2011, + "swapped": { + "boss": [ + "9:won:0" + ], + "hero_levels": [ + 0, + 0, + 0, + 0, + 0, + 0 + ], + "lane_losses_at_end": [ + 3, + 0 + ], + "max_combat": 110.649999999996, + "result": "rot", + "rounds": 9, + "timeouts": 0, + "total_gold_curve": [ + 748, + 785, + 820, + 927, + 983, + 1113, + 1240, + 1165, + 2307 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt", + "wurzelbund" + ], + "index": 12, + "matchup": true, + "normal": { + "boss": [], + "hero_levels": [ + 5, + 5, + 6, + 5, + 4, + 4 + ], + "lane_losses_at_end": [ + 1, + 2 + ], + "max_combat": 168.600000000004, + "result": "rot", + "rounds": 10, + "timeouts": 0, + "total_gold_curve": [ + 893, + 700, + 995, + 797, + 1108, + 1068, + 1554, + 2113, + 1670, + 916 + ] + }, + "profiles": [ + "fernkampf", + "fernkampf", + "fernkampf", + "teamhilfe", + "teamhilfe", + "teamhilfe" + ], + "seed": 2012, + "swapped": { + "boss": [], + "hero_levels": [ + 5, + 4, + 4, + 5, + 5, + 6 + ], + "lane_losses_at_end": [ + 2, + 1 + ], + "max_combat": 168.600000000004, + "result": "blau", + "rounds": 10, + "timeouts": 0, + "total_gold_curve": [ + 893, + 700, + 995, + 797, + 1108, + 1068, + 1554, + 2113, + 1670, + 916 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt", + "wurzelbund" + ], + "index": 13, + "matchup": true, + "normal": { + "boss": [ + "9:won:1" + ], + "hero_levels": [ + 3, + 3, + 3, + 0, + 0, + 0 + ], + "lane_losses_at_end": [ + 0, + 3 + ], + "max_combat": 73.8999999999979, + "result": "blau", + "rounds": 9, + "timeouts": 0, + "total_gold_curve": [ + 893, + 745, + 965, + 837, + 1043, + 1053, + 1145, + 1655, + 1648 + ] + }, + "profiles": [ + "fernkampf", + "fernkampf", + "fernkampf", + "bossjagd", + "bossjagd", + "bossjagd" + ], + "seed": 2013, + "swapped": { + "boss": [ + "9:won:0" + ], + "hero_levels": [ + 0, + 0, + 0, + 3, + 3, + 3 + ], + "lane_losses_at_end": [ + 3, + 0 + ], + "max_combat": 73.8999999999979, + "result": "rot", + "rounds": 9, + "timeouts": 0, + "total_gold_curve": [ + 893, + 745, + 965, + 837, + 1043, + 1053, + 1145, + 1655, + 1648 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt", + "wurzelbund" + ], + "index": 14, + "matchup": true, + "normal": { + "boss": [ + "8:won:1", + "9:deployed:1" + ], + "hero_levels": [ + 4, + 4, + 4, + 4, + 4, + 5 + ], + "lane_losses_at_end": [ + 2, + 2 + ], + "max_combat": 131.849999999996, + "result": "blau", + "rounds": 10, + "timeouts": 0, + "total_gold_curve": [ + 748, + 815, + 960, + 802, + 1113, + 1183, + 1344, + 1667, + 2855, + 1864 + ] + }, + "profiles": [ + "teamhilfe", + "teamhilfe", + "teamhilfe", + "bossjagd", + "bossjagd", + "bossjagd" + ], + "seed": 2014, + "swapped": { + "boss": [ + "8:won:0", + "9:deployed:0" + ], + "hero_levels": [ + 4, + 4, + 5, + 4, + 4, + 4 + ], + "lane_losses_at_end": [ + 2, + 2 + ], + "max_combat": 131.849999999996, + "result": "rot", + "rounds": 10, + "timeouts": 0, + "total_gold_curve": [ + 748, + 815, + 960, + 802, + 1113, + 1183, + 1344, + 1667, + 2855, + 1864 + ] + } + } +] \ No newline at end of file diff --git a/docs/luneraria/logs/m3/matchups-step4-summary.txt b/docs/luneraria/logs/m3/matchups-step4-summary.txt new file mode 100644 index 0000000..81dda2c --- /dev/null +++ b/docs/luneraria/logs/m3/matchups-step4-summary.txt @@ -0,0 +1,14 @@ +Partien 30 · Ergebnisse {'blau': 15, 'rot': 15} · Testlimit 0 · Zeitfenster-Kämpfe 0 · längster Kampf 169 s +Wellen bis Ende: min 9 · median 10 · max 14 +Seitentausch: dieselbe Zusammensetzung gewinnt beide Male in 15/15 Paaren +Profile (Siege/Einsätze): {'ausgewogen': '30/30 (100 %)', 'bossjagd': '0/30 (0 %)', 'druck': '6/30 (20 %)', 'fernkampf': '12/30 (40 %)', 'teamhilfe': '18/30 (60 %)', 'verteidigung': '24/30 (80 %)'} +Bünde: {'glutpakt': '30/60 (50 %)', 'wurzelbund': '60/120 (50 %)'} +Boss: {'won': 4, 'deployed': 2} · Heldenstufen am Ende (Median): 5 +Duelle (Siege Zeile gegen Spalte, je 2 Partien mit Seitentausch): + ausgew bossja druck fernka teamhi vertei gesamt +ausgewogen - 2 2 2 2 2 10/10 +bossjagd 0 - 0 0 0 0 0/10 +druck 0 2 - 0 0 0 2/10 +fernkampf 0 2 2 - 0 0 4/10 +teamhilfe 0 2 2 2 - 0 6/10 +verteidigung 0 2 2 2 2 - 8/10 diff --git a/docs/luneraria/logs/m3/matchups-step4.json b/docs/luneraria/logs/m3/matchups-step4.json new file mode 100644 index 0000000..00daf9c --- /dev/null +++ b/docs/luneraria/logs/m3/matchups-step4.json @@ -0,0 +1,1283 @@ +[ + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt", + "wurzelbund" + ], + "index": 0, + "matchup": true, + "normal": { + "boss": [], + "hero_levels": [ + 6, + 4, + 3, + 0, + 3, + 0 + ], + "lane_losses_at_end": [ + 0, + 3 + ], + "max_combat": 109.249999999996, + "result": "blau", + "rounds": 13, + "timeouts": 0, + "total_gold_curve": [ + 748, + 785, + 930, + 802, + 943, + 903, + 1015, + 1025, + 1153, + 1425, + 1679, + 1892, + 2444 + ] + }, + "profiles": [ + "ausgewogen", + "ausgewogen", + "ausgewogen", + "verteidigung", + "verteidigung", + "verteidigung" + ], + "seed": 2000, + "swapped": { + "boss": [], + "hero_levels": [ + 0, + 3, + 0, + 6, + 4, + 3 + ], + "lane_losses_at_end": [ + 3, + 0 + ], + "max_combat": 109.249999999996, + "result": "rot", + "rounds": 13, + "timeouts": 0, + "total_gold_curve": [ + 748, + 785, + 930, + 802, + 943, + 903, + 1015, + 1025, + 1153, + 1425, + 1679, + 1892, + 2444 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt", + "wurzelbund" + ], + "index": 1, + "matchup": true, + "normal": { + "boss": [], + "hero_levels": [ + 5, + 5, + 4, + 0, + 0, + 0 + ], + "lane_losses_at_end": [ + 0, + 3 + ], + "max_combat": 107.949999999996, + "result": "blau", + "rounds": 11, + "timeouts": 0, + "total_gold_curve": [ + 748, + 710, + 820, + 927, + 918, + 998, + 1145, + 1370, + 1521, + 1853, + 1646 + ] + }, + "profiles": [ + "ausgewogen", + "ausgewogen", + "ausgewogen", + "druck", + "druck", + "druck" + ], + "seed": 2001, + "swapped": { + "boss": [], + "hero_levels": [ + 0, + 0, + 0, + 5, + 5, + 4 + ], + "lane_losses_at_end": [ + 3, + 0 + ], + "max_combat": 107.949999999996, + "result": "rot", + "rounds": 11, + "timeouts": 0, + "total_gold_curve": [ + 748, + 710, + 820, + 927, + 918, + 998, + 1145, + 1370, + 1521, + 1853, + 1646 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt", + "wurzelbund" + ], + "index": 2, + "matchup": true, + "normal": { + "boss": [], + "hero_levels": [ + 5, + 7, + 7, + 7, + 7, + 7 + ], + "lane_losses_at_end": [ + 1, + 3 + ], + "max_combat": 97.6499999999966, + "result": "blau", + "rounds": 14, + "timeouts": 0, + "total_gold_curve": [ + 893, + 670, + 965, + 837, + 978, + 938, + 1050, + 1260, + 1178, + 1930, + 1503, + 2002, + 2452, + 1929 + ] + }, + "profiles": [ + "ausgewogen", + "ausgewogen", + "ausgewogen", + "fernkampf", + "fernkampf", + "fernkampf" + ], + "seed": 2002, + "swapped": { + "boss": [], + "hero_levels": [ + 7, + 7, + 7, + 5, + 7, + 7 + ], + "lane_losses_at_end": [ + 3, + 1 + ], + "max_combat": 97.6499999999966, + "result": "rot", + "rounds": 14, + "timeouts": 0, + "total_gold_curve": [ + 893, + 670, + 965, + 837, + 978, + 938, + 1050, + 1260, + 1178, + 1930, + 1503, + 2002, + 2452, + 1929 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt", + "wurzelbund" + ], + "index": 3, + "matchup": true, + "normal": { + "boss": [], + "hero_levels": [ + 3, + 5, + 5, + 3, + 2, + 2 + ], + "lane_losses_at_end": [ + 1, + 2 + ], + "max_combat": 110.949999999996, + "result": "blau", + "rounds": 10, + "timeouts": 0, + "total_gold_curve": [ + 748, + 740, + 960, + 802, + 1048, + 858, + 1474, + 1389, + 2261, + 1133 + ] + }, + "profiles": [ + "ausgewogen", + "ausgewogen", + "ausgewogen", + "teamhilfe", + "teamhilfe", + "teamhilfe" + ], + "seed": 2003, + "swapped": { + "boss": [], + "hero_levels": [ + 3, + 2, + 2, + 3, + 5, + 5 + ], + "lane_losses_at_end": [ + 2, + 1 + ], + "max_combat": 110.949999999996, + "result": "rot", + "rounds": 10, + "timeouts": 0, + "total_gold_curve": [ + 748, + 740, + 960, + 802, + 1048, + 858, + 1474, + 1389, + 2261, + 1133 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt", + "wurzelbund" + ], + "index": 4, + "matchup": true, + "normal": { + "boss": [], + "hero_levels": [ + 3, + 4, + 5, + 0, + 0, + 0 + ], + "lane_losses_at_end": [ + 0, + 3 + ], + "max_combat": 105.099999999996, + "result": "blau", + "rounds": 11, + "timeouts": 0, + "total_gold_curve": [ + 748, + 785, + 930, + 842, + 983, + 993, + 1105, + 1115, + 1543, + 1915, + 1715 + ] + }, + "profiles": [ + "ausgewogen", + "ausgewogen", + "ausgewogen", + "bossjagd", + "bossjagd", + "bossjagd" + ], + "seed": 2004, + "swapped": { + "boss": [], + "hero_levels": [ + 0, + 0, + 0, + 3, + 4, + 5 + ], + "lane_losses_at_end": [ + 3, + 0 + ], + "max_combat": 105.099999999996, + "result": "rot", + "rounds": 11, + "timeouts": 0, + "total_gold_curve": [ + 748, + 785, + 930, + 842, + 983, + 993, + 1105, + 1115, + 1543, + 1915, + 1715 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt", + "wurzelbund" + ], + "index": 5, + "matchup": true, + "normal": { + "boss": [], + "hero_levels": [ + 4, + 6, + 6, + 0, + 0, + 0 + ], + "lane_losses_at_end": [ + 0, + 3 + ], + "max_combat": 105.349999999996, + "result": "blau", + "rounds": 11, + "timeouts": 0, + "total_gold_curve": [ + 748, + 785, + 820, + 887, + 943, + 1023, + 1150, + 1375, + 1576, + 1623, + 1544 + ] + }, + "profiles": [ + "verteidigung", + "verteidigung", + "verteidigung", + "druck", + "druck", + "druck" + ], + "seed": 2005, + "swapped": { + "boss": [], + "hero_levels": [ + 0, + 0, + 0, + 4, + 6, + 6 + ], + "lane_losses_at_end": [ + 3, + 0 + ], + "max_combat": 105.349999999996, + "result": "rot", + "rounds": 11, + "timeouts": 0, + "total_gold_curve": [ + 748, + 785, + 820, + 887, + 943, + 1023, + 1150, + 1375, + 1576, + 1623, + 1544 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt", + "wurzelbund" + ], + "index": 6, + "matchup": true, + "normal": { + "boss": [], + "hero_levels": [ + 5, + 6, + 7, + 7, + 7, + 7 + ], + "lane_losses_at_end": [ + 1, + 2 + ], + "max_combat": 109.149999999996, + "result": "blau", + "rounds": 14, + "timeouts": 0, + "total_gold_curve": [ + 893, + 745, + 965, + 797, + 1003, + 963, + 1055, + 1115, + 1213, + 1985, + 1839, + 1918, + 2075, + 1948 + ] + }, + "profiles": [ + "verteidigung", + "verteidigung", + "verteidigung", + "fernkampf", + "fernkampf", + "fernkampf" + ], + "seed": 2006, + "swapped": { + "boss": [], + "hero_levels": [ + 7, + 7, + 7, + 5, + 6, + 7 + ], + "lane_losses_at_end": [ + 2, + 1 + ], + "max_combat": 109.149999999996, + "result": "rot", + "rounds": 14, + "timeouts": 0, + "total_gold_curve": [ + 893, + 745, + 965, + 797, + 1003, + 963, + 1055, + 1115, + 1213, + 1985, + 1839, + 1918, + 2075, + 1948 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt", + "wurzelbund" + ], + "index": 7, + "matchup": true, + "normal": { + "boss": [], + "hero_levels": [ + 3, + 5, + 5, + 0, + 0, + 0 + ], + "lane_losses_at_end": [ + 1, + 2 + ], + "max_combat": 95.8999999999967, + "result": "blau", + "rounds": 9, + "timeouts": 0, + "total_gold_curve": [ + 748, + 815, + 960, + 762, + 1073, + 883, + 1779, + 1564, + 1173 + ] + }, + "profiles": [ + "verteidigung", + "verteidigung", + "verteidigung", + "teamhilfe", + "teamhilfe", + "teamhilfe" + ], + "seed": 2007, + "swapped": { + "boss": [], + "hero_levels": [ + 0, + 0, + 0, + 3, + 5, + 5 + ], + "lane_losses_at_end": [ + 2, + 1 + ], + "max_combat": 95.8999999999967, + "result": "rot", + "rounds": 9, + "timeouts": 0, + "total_gold_curve": [ + 748, + 815, + 960, + 762, + 1073, + 883, + 1779, + 1564, + 1173 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt", + "wurzelbund" + ], + "index": 8, + "matchup": true, + "normal": { + "boss": [], + "hero_levels": [ + 4, + 3, + 3, + 0, + 0, + 0 + ], + "lane_losses_at_end": [ + 0, + 3 + ], + "max_combat": 103.349999999996, + "result": "blau", + "rounds": 10, + "timeouts": 0, + "total_gold_curve": [ + 748, + 860, + 930, + 802, + 1008, + 1018, + 1110, + 1420, + 1758, + 1350 + ] + }, + "profiles": [ + "verteidigung", + "verteidigung", + "verteidigung", + "bossjagd", + "bossjagd", + "bossjagd" + ], + "seed": 2008, + "swapped": { + "boss": [], + "hero_levels": [ + 0, + 0, + 0, + 4, + 3, + 3 + ], + "lane_losses_at_end": [ + 3, + 0 + ], + "max_combat": 103.349999999996, + "result": "rot", + "rounds": 10, + "timeouts": 0, + "total_gold_curve": [ + 748, + 860, + 930, + 802, + 1008, + 1018, + 1110, + 1420, + 1758, + 1350 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt", + "wurzelbund" + ], + "index": 9, + "matchup": true, + "normal": { + "boss": [], + "hero_levels": [ + 0, + 0, + 0, + 5, + 5, + 5 + ], + "lane_losses_at_end": [ + 3, + 0 + ], + "max_combat": 162.200000000003, + "result": "rot", + "rounds": 10, + "timeouts": 0, + "total_gold_curve": [ + 893, + 670, + 855, + 922, + 978, + 1058, + 1185, + 1610, + 1651, + 1522 + ] + }, + "profiles": [ + "druck", + "druck", + "druck", + "fernkampf", + "fernkampf", + "fernkampf" + ], + "seed": 2009, + "swapped": { + "boss": [], + "hero_levels": [ + 5, + 5, + 5, + 0, + 0, + 0 + ], + "lane_losses_at_end": [ + 0, + 3 + ], + "max_combat": 162.200000000003, + "result": "blau", + "rounds": 10, + "timeouts": 0, + "total_gold_curve": [ + 893, + 670, + 855, + 922, + 978, + 1058, + 1185, + 1610, + 1651, + 1522 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt", + "wurzelbund" + ], + "index": 10, + "matchup": true, + "normal": { + "boss": [], + "hero_levels": [ + 5, + 3, + 6, + 4, + 3, + 2 + ], + "lane_losses_at_end": [ + 1, + 1 + ], + "max_combat": 102.899999999996, + "result": "rot", + "rounds": 10, + "timeouts": 0, + "total_gold_curve": [ + 748, + 740, + 850, + 887, + 1048, + 978, + 1609, + 1309, + 2030, + 1731 + ] + }, + "profiles": [ + "druck", + "druck", + "druck", + "teamhilfe", + "teamhilfe", + "teamhilfe" + ], + "seed": 2010, + "swapped": { + "boss": [], + "hero_levels": [ + 4, + 3, + 2, + 5, + 3, + 6 + ], + "lane_losses_at_end": [ + 1, + 1 + ], + "max_combat": 102.899999999996, + "result": "blau", + "rounds": 10, + "timeouts": 0, + "total_gold_curve": [ + 748, + 740, + 850, + 887, + 1048, + 978, + 1609, + 1309, + 2030, + 1731 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt", + "wurzelbund" + ], + "index": 11, + "matchup": true, + "normal": { + "boss": [ + "10:won:1" + ], + "hero_levels": [ + 3, + 3, + 3, + 2, + 0, + 2 + ], + "lane_losses_at_end": [ + 0, + 3 + ], + "max_combat": 149.05, + "result": "blau", + "rounds": 10, + "timeouts": 0, + "total_gold_curve": [ + 748, + 785, + 820, + 927, + 983, + 1113, + 1240, + 1165, + 1836, + 2132 + ] + }, + "profiles": [ + "druck", + "druck", + "druck", + "bossjagd", + "bossjagd", + "bossjagd" + ], + "seed": 2011, + "swapped": { + "boss": [ + "10:won:0" + ], + "hero_levels": [ + 2, + 0, + 2, + 3, + 3, + 3 + ], + "lane_losses_at_end": [ + 3, + 0 + ], + "max_combat": 149.05, + "result": "rot", + "rounds": 10, + "timeouts": 0, + "total_gold_curve": [ + 748, + 785, + 820, + 927, + 983, + 1113, + 1240, + 1165, + 1836, + 2132 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt", + "wurzelbund" + ], + "index": 12, + "matchup": true, + "normal": { + "boss": [], + "hero_levels": [ + 5, + 5, + 6, + 5, + 4, + 4 + ], + "lane_losses_at_end": [ + 1, + 2 + ], + "max_combat": 168.600000000004, + "result": "rot", + "rounds": 10, + "timeouts": 0, + "total_gold_curve": [ + 893, + 700, + 995, + 797, + 1108, + 1068, + 1554, + 2113, + 1670, + 916 + ] + }, + "profiles": [ + "fernkampf", + "fernkampf", + "fernkampf", + "teamhilfe", + "teamhilfe", + "teamhilfe" + ], + "seed": 2012, + "swapped": { + "boss": [], + "hero_levels": [ + 5, + 4, + 4, + 5, + 5, + 6 + ], + "lane_losses_at_end": [ + 2, + 1 + ], + "max_combat": 168.600000000004, + "result": "blau", + "rounds": 10, + "timeouts": 0, + "total_gold_curve": [ + 893, + 700, + 995, + 797, + 1108, + 1068, + 1554, + 2113, + 1670, + 916 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt", + "wurzelbund" + ], + "index": 13, + "matchup": true, + "normal": { + "boss": [], + "hero_levels": [ + 5, + 4, + 5, + 0, + 0, + 0 + ], + "lane_losses_at_end": [ + 0, + 3 + ], + "max_combat": 88.7999999999971, + "result": "blau", + "rounds": 10, + "timeouts": 0, + "total_gold_curve": [ + 893, + 745, + 965, + 837, + 1043, + 1053, + 1145, + 1655, + 1833, + 1239 + ] + }, + "profiles": [ + "fernkampf", + "fernkampf", + "fernkampf", + "bossjagd", + "bossjagd", + "bossjagd" + ], + "seed": 2013, + "swapped": { + "boss": [], + "hero_levels": [ + 0, + 0, + 0, + 5, + 4, + 5 + ], + "lane_losses_at_end": [ + 3, + 0 + ], + "max_combat": 88.7999999999971, + "result": "rot", + "rounds": 10, + "timeouts": 0, + "total_gold_curve": [ + 893, + 745, + 965, + 837, + 1043, + 1053, + 1145, + 1655, + 1833, + 1239 + ] + } + }, + { + "bunds": [ + "wurzelbund", + "glutpakt", + "wurzelbund", + "wurzelbund", + "glutpakt", + "wurzelbund" + ], + "index": 14, + "matchup": true, + "normal": { + "boss": [ + "8:won:1", + "9:deployed:1" + ], + "hero_levels": [ + 4, + 4, + 4, + 4, + 3, + 5 + ], + "lane_losses_at_end": [ + 2, + 2 + ], + "max_combat": 147.199999999999, + "result": "blau", + "rounds": 10, + "timeouts": 0, + "total_gold_curve": [ + 748, + 815, + 960, + 802, + 1113, + 1183, + 1344, + 1667, + 3125, + 1887 + ] + }, + "profiles": [ + "teamhilfe", + "teamhilfe", + "teamhilfe", + "bossjagd", + "bossjagd", + "bossjagd" + ], + "seed": 2014, + "swapped": { + "boss": [ + "8:won:0", + "9:deployed:0" + ], + "hero_levels": [ + 4, + 3, + 5, + 4, + 4, + 4 + ], + "lane_losses_at_end": [ + 2, + 2 + ], + "max_combat": 147.199999999999, + "result": "rot", + "rounds": 10, + "timeouts": 0, + "total_gold_curve": [ + 748, + 815, + 960, + 802, + 1113, + 1183, + 1344, + 1667, + 3125, + 1887 + ] + } + } +] \ No newline at end of file diff --git a/docs/luneraria/logs/m3/stress.json b/docs/luneraria/logs/m3/stress.json new file mode 100644 index 0000000..20f00ba --- /dev/null +++ b/docs/luneraria/logs/m3/stress.json @@ -0,0 +1,79 @@ +{ + "checks": 17, + "failures": [], + "high_index": [ + { + "end": "core", + "monster_hp": 12763.92, + "peak_mobile": 120, + "phase": "vorbei", + "steps": 2312, + "wall_ms": 2585, + "wave": 100 + }, + { + "end": "core", + "monster_hp": 771787.92, + "peak_mobile": 120, + "phase": "vorbei", + "steps": 1484, + "wall_ms": 1871, + "wave": 1000 + }, + { + "end": "core", + "monster_hp": 72514027.92, + "peak_mobile": 120, + "phase": "vorbei", + "steps": 1484, + "wall_ms": 1899, + "wave": 10000 + }, + { + "end": "core", + "monster_hp": 720051360427.92, + "peak_mobile": 120, + "phase": "vorbei", + "steps": 1484, + "wall_ms": 2032, + "wave": 1000000 + } + ], + "long_run": { + "final_gold": [ + 106607, + 106626, + 106626, + 107653, + 107653, + 107653 + ], + "final_wave_monster_hp": 39840.0, + "memory_mb_every25": [ + 52.1640138626099, + 53.2806062698364, + 53.3198556900024, + 53.3384599685669, + 53.3838663101196, + 53.3969850540161, + 53.4318552017212, + 53.4756746292114, + 53.4982004165649 + ], + "objects_every25": [ + 1706.0, + 1853.0, + 1853.0, + 1853.0, + 1853.0, + 1853.0, + 1853.0, + 1853.0, + 1853.0 + ], + "result": "Testlimit erreicht / Partie nicht abgeschlossen", + "round_ms_first10": 1049.535, + "round_ms_last10": 2006.0815, + "rounds": 200 + } +} \ No newline at end of file diff --git a/docs/luneraria/logs/m3/stress.log b/docs/luneraria/logs/m3/stress.log new file mode 100644 index 0000000..7e24b4b --- /dev/null +++ b/docs/luneraria/logs/m3/stress.log @@ -0,0 +1,20 @@ +Godot Engine v4.7.2.stable.fedora.ed1daf0bf - https://godotengine.org + +STRESS_WAVE {"end":"core","monster_hp":12763.92,"peak_mobile":120,"phase":"vorbei","steps":2312,"wall_ms":2585,"wave":100} +STRESS_WAVE {"end":"core","monster_hp":771787.92,"peak_mobile":120,"phase":"vorbei","steps":1484,"wall_ms":1871,"wave":1000} +STRESS_WAVE {"end":"core","monster_hp":72514027.92,"peak_mobile":120,"phase":"vorbei","steps":1484,"wall_ms":1899,"wave":10000} +STRESS_WAVE {"end":"core","monster_hp":720051360427.92,"peak_mobile":120,"phase":"vorbei","steps":1484,"wall_ms":2032,"wave":1000000} +STRESS_PROGRESS round 26 · 2600 ms · 53.3 MB · 1853 objects +STRESS_PROGRESS round 51 · 2317 ms · 53.3 MB · 1853 objects +STRESS_PROGRESS round 76 · 2211 ms · 53.3 MB · 1853 objects +STRESS_PROGRESS round 101 · 2382 ms · 53.4 MB · 1853 objects +STRESS_PROGRESS round 126 · 2335 ms · 53.4 MB · 1853 objects +STRESS_PROGRESS round 151 · 2153 ms · 53.4 MB · 1853 objects +STRESS_PROGRESS round 176 · 2156 ms · 53.5 MB · 1853 objects +STRESS_PROGRESS round 201 · 1922 ms · 53.5 MB · 1853 objects +STRESS_LONG {"final_gold":[106607,106626,106626,107653,107653,107653],"final_wave_monster_hp":39840.0,"memory_mb_every25":[52.1640138626099,53.2806062698364,53.3198556900024,53.3384599685669,53.3838663101196,53.3969850540161,53.4318552017212,53.4756746292114,53.4982004165649],"objects_every25":[1706.0,1853.0,1853.0,1853.0,1853.0,1853.0,1853.0,1853.0,1853.0],"result":"Testlimit erreicht / Partie nicht abgeschlossen","round_ms_first10":1049.535,"round_ms_last10":2006.0815,"rounds":200} +STRESS_PASS: 17 checks; 0 failures +WARNING: 16 ObjectDB instances were leaked at exit (run with `--verbose` for details). + at: cleanup (core/object/object.cpp:2536) +ERROR: 1 resources still in use at exit (run with --verbose for details). + at: clear (core/io/resource.cpp:822) diff --git a/docs/luneraria/screenshots/combat-mid-live.png b/docs/luneraria/screenshots/combat-mid-live.png index 979f532..bfb9a42 100644 Binary files a/docs/luneraria/screenshots/combat-mid-live.png and b/docs/luneraria/screenshots/combat-mid-live.png differ diff --git a/docs/luneraria/screenshots/combat-mid.png b/docs/luneraria/screenshots/combat-mid.png index 99f88ac..549ea29 100644 Binary files a/docs/luneraria/screenshots/combat-mid.png and b/docs/luneraria/screenshots/combat-mid.png differ diff --git a/docs/luneraria/screenshots/combat-top.png b/docs/luneraria/screenshots/combat-top.png index 9b100cb..8b7ba3a 100644 Binary files a/docs/luneraria/screenshots/combat-top.png and b/docs/luneraria/screenshots/combat-top.png differ diff --git a/docs/luneraria/screenshots/core-siege.png b/docs/luneraria/screenshots/core-siege.png index 7e950d5..5ba9a0b 100644 Binary files a/docs/luneraria/screenshots/core-siege.png and b/docs/luneraria/screenshots/core-siege.png differ diff --git a/docs/luneraria/screenshots/m2-announcements.png b/docs/luneraria/screenshots/m2-announcements.png index 7ff3bdf..e6f81f6 100644 Binary files a/docs/luneraria/screenshots/m2-announcements.png and b/docs/luneraria/screenshots/m2-announcements.png differ diff --git a/docs/luneraria/screenshots/m2-announcements.png.import b/docs/luneraria/screenshots/m2-announcements.png.import new file mode 100644 index 0000000..9ef9a3f --- /dev/null +++ b/docs/luneraria/screenshots/m2-announcements.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://3j55xt0p730y" +path="res://.godot/imported/m2-announcements.png-8845438d876d7dfd9983fee3eccd8c8a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://docs/luneraria/screenshots/m2-announcements.png" +dest_files=["res://.godot/imported/m2-announcements.png-8845438d876d7dfd9983fee3eccd8c8a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/docs/luneraria/screenshots/m2-boss-deployed.png b/docs/luneraria/screenshots/m2-boss-deployed.png index 1b0aac4..9aee249 100644 Binary files a/docs/luneraria/screenshots/m2-boss-deployed.png and b/docs/luneraria/screenshots/m2-boss-deployed.png differ diff --git a/docs/luneraria/screenshots/m2-boss-deployed.png.import b/docs/luneraria/screenshots/m2-boss-deployed.png.import new file mode 100644 index 0000000..3317049 --- /dev/null +++ b/docs/luneraria/screenshots/m2-boss-deployed.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dxhjccj540c8h" +path="res://.godot/imported/m2-boss-deployed.png-1a935be950c90a5e4c9f495145d22448.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://docs/luneraria/screenshots/m2-boss-deployed.png" +dest_files=["res://.godot/imported/m2-boss-deployed.png-1a935be950c90a5e4c9f495145d22448.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/docs/luneraria/screenshots/m2-grotto-fight.png b/docs/luneraria/screenshots/m2-grotto-fight.png index fe279a0..adf672f 100644 Binary files a/docs/luneraria/screenshots/m2-grotto-fight.png and b/docs/luneraria/screenshots/m2-grotto-fight.png differ diff --git a/docs/luneraria/screenshots/m2-grotto-fight.png.import b/docs/luneraria/screenshots/m2-grotto-fight.png.import new file mode 100644 index 0000000..a085adf --- /dev/null +++ b/docs/luneraria/screenshots/m2-grotto-fight.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dvkm35euexvxh" +path="res://.godot/imported/m2-grotto-fight.png-ee0d0afa6644844ebc078409b5ca52d0.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://docs/luneraria/screenshots/m2-grotto-fight.png" +dest_files=["res://.godot/imported/m2-grotto-fight.png-ee0d0afa6644844ebc078409b5ca52d0.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/docs/luneraria/screenshots/m2-hero-field.png b/docs/luneraria/screenshots/m2-hero-field.png index f3c29f9..01c689c 100644 Binary files a/docs/luneraria/screenshots/m2-hero-field.png and b/docs/luneraria/screenshots/m2-hero-field.png differ diff --git a/docs/luneraria/screenshots/m2-hero-field.png.import b/docs/luneraria/screenshots/m2-hero-field.png.import new file mode 100644 index 0000000..4f5536b --- /dev/null +++ b/docs/luneraria/screenshots/m2-hero-field.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://fngh7pobxawa" +path="res://.godot/imported/m2-hero-field.png-9bfd17f193f8d06079a9a5e5857e52d5.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://docs/luneraria/screenshots/m2-hero-field.png" +dest_files=["res://.godot/imported/m2-hero-field.png-9bfd17f193f8d06079a9a5e5857e52d5.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/docs/luneraria/screenshots/m2-hero-items.png b/docs/luneraria/screenshots/m2-hero-items.png index 27580c6..ec7863d 100644 Binary files a/docs/luneraria/screenshots/m2-hero-items.png and b/docs/luneraria/screenshots/m2-hero-items.png differ diff --git a/docs/luneraria/screenshots/m2-hero-items.png.import b/docs/luneraria/screenshots/m2-hero-items.png.import new file mode 100644 index 0000000..f721a85 --- /dev/null +++ b/docs/luneraria/screenshots/m2-hero-items.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dfpvffvws1lb6" +path="res://.godot/imported/m2-hero-items.png-226f26a5daf93acf898dd5b8cf0889d9.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://docs/luneraria/screenshots/m2-hero-items.png" +dest_files=["res://.godot/imported/m2-hero-items.png-226f26a5daf93acf898dd5b8cf0889d9.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/docs/luneraria/screenshots/m2-hud-hero-round1.png b/docs/luneraria/screenshots/m2-hud-hero-round1.png index c69885f..e9b6886 100644 Binary files a/docs/luneraria/screenshots/m2-hud-hero-round1.png and b/docs/luneraria/screenshots/m2-hud-hero-round1.png differ diff --git a/docs/luneraria/screenshots/m2-hud-hero-round1.png.import b/docs/luneraria/screenshots/m2-hud-hero-round1.png.import new file mode 100644 index 0000000..6e6629a --- /dev/null +++ b/docs/luneraria/screenshots/m2-hud-hero-round1.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://begh1xbp2rhed" +path="res://.godot/imported/m2-hud-hero-round1.png-f3e60518b7120373d97e0c22410d91d9.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://docs/luneraria/screenshots/m2-hud-hero-round1.png" +dest_files=["res://.godot/imported/m2-hud-hero-round1.png-f3e60518b7120373d97e0c22410d91d9.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/docs/luneraria/screenshots/menu-setup.png b/docs/luneraria/screenshots/menu-setup.png index 351ac9e..3bf1227 100644 Binary files a/docs/luneraria/screenshots/menu-setup.png and b/docs/luneraria/screenshots/menu-setup.png differ diff --git a/docs/luneraria/screenshots/menu-title.png b/docs/luneraria/screenshots/menu-title.png index fbd0a6f..b910f0e 100644 Binary files a/docs/luneraria/screenshots/menu-title.png and b/docs/luneraria/screenshots/menu-title.png differ diff --git a/docs/luneraria/screenshots/overview-combat.png b/docs/luneraria/screenshots/overview-combat.png index 963bfbf..696ee07 100644 Binary files a/docs/luneraria/screenshots/overview-combat.png and b/docs/luneraria/screenshots/overview-combat.png differ diff --git a/docs/luneraria/screenshots/overview-planning.png b/docs/luneraria/screenshots/overview-planning.png index 4ca13b2..5216974 100644 Binary files a/docs/luneraria/screenshots/overview-planning.png and b/docs/luneraria/screenshots/overview-planning.png differ diff --git a/docs/luneraria/screenshots/peoples-detail-a.png b/docs/luneraria/screenshots/peoples-detail-a.png new file mode 100644 index 0000000..5cbbe4a Binary files /dev/null and b/docs/luneraria/screenshots/peoples-detail-a.png differ diff --git a/docs/luneraria/screenshots/peoples-detail-a.png.import b/docs/luneraria/screenshots/peoples-detail-a.png.import new file mode 100644 index 0000000..4502f1f --- /dev/null +++ b/docs/luneraria/screenshots/peoples-detail-a.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://duftukfvp52td" +path="res://.godot/imported/peoples-detail-a.png-118d5eec104c6029d82c3e3d681d9e75.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://docs/luneraria/screenshots/peoples-detail-a.png" +dest_files=["res://.godot/imported/peoples-detail-a.png-118d5eec104c6029d82c3e3d681d9e75.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/docs/luneraria/screenshots/peoples-detail-b.png b/docs/luneraria/screenshots/peoples-detail-b.png new file mode 100644 index 0000000..2334725 Binary files /dev/null and b/docs/luneraria/screenshots/peoples-detail-b.png differ diff --git a/docs/luneraria/screenshots/peoples-detail-b.png.import b/docs/luneraria/screenshots/peoples-detail-b.png.import new file mode 100644 index 0000000..04e9395 --- /dev/null +++ b/docs/luneraria/screenshots/peoples-detail-b.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bmvplruukfavi" +path="res://.godot/imported/peoples-detail-b.png-15cbd8e6c27d49fa094208adf926d286.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://docs/luneraria/screenshots/peoples-detail-b.png" +dest_files=["res://.godot/imported/peoples-detail-b.png-15cbd8e6c27d49fa094208adf926d286.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/docs/luneraria/screenshots/peoples-faehigkeit.png b/docs/luneraria/screenshots/peoples-faehigkeit.png new file mode 100644 index 0000000..648274f Binary files /dev/null and b/docs/luneraria/screenshots/peoples-faehigkeit.png differ diff --git a/docs/luneraria/screenshots/peoples-faehigkeit.png.import b/docs/luneraria/screenshots/peoples-faehigkeit.png.import new file mode 100644 index 0000000..6df3742 --- /dev/null +++ b/docs/luneraria/screenshots/peoples-faehigkeit.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://va4q2eseadfl" +path="res://.godot/imported/peoples-faehigkeit.png-a0b4febe7e6c042745fb5766a530f292.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://docs/luneraria/screenshots/peoples-faehigkeit.png" +dest_files=["res://.godot/imported/peoples-faehigkeit.png-a0b4febe7e6c042745fb5766a530f292.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/docs/luneraria/screenshots/peoples-kontakt.png b/docs/luneraria/screenshots/peoples-kontakt.png new file mode 100644 index 0000000..5344f34 Binary files /dev/null and b/docs/luneraria/screenshots/peoples-kontakt.png differ diff --git a/docs/luneraria/screenshots/peoples-kontakt.png.import b/docs/luneraria/screenshots/peoples-kontakt.png.import new file mode 100644 index 0000000..23e7375 --- /dev/null +++ b/docs/luneraria/screenshots/peoples-kontakt.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cy6bhq2d77kks" +path="res://.godot/imported/peoples-kontakt.png-af278189a101c072c081c7bddf3c3781.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://docs/luneraria/screenshots/peoples-kontakt.png" +dest_files=["res://.godot/imported/peoples-kontakt.png-af278189a101c072c081c7bddf3c3781.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/docs/luneraria/screenshots/peoples-laufen.png b/docs/luneraria/screenshots/peoples-laufen.png new file mode 100644 index 0000000..5ef201d Binary files /dev/null and b/docs/luneraria/screenshots/peoples-laufen.png differ diff --git a/docs/luneraria/screenshots/peoples-laufen.png.import b/docs/luneraria/screenshots/peoples-laufen.png.import new file mode 100644 index 0000000..a2e8e70 --- /dev/null +++ b/docs/luneraria/screenshots/peoples-laufen.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://1ugn3inhkgvg" +path="res://.godot/imported/peoples-laufen.png-b562392a9450bd976498e4afff497b78.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://docs/luneraria/screenshots/peoples-laufen.png" +dest_files=["res://.godot/imported/peoples-laufen.png-b562392a9450bd976498e4afff497b78.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/docs/luneraria/screenshots/peoples-ruhe.png b/docs/luneraria/screenshots/peoples-ruhe.png new file mode 100644 index 0000000..5d2e21c Binary files /dev/null and b/docs/luneraria/screenshots/peoples-ruhe.png differ diff --git a/docs/luneraria/screenshots/peoples-ruhe.png.import b/docs/luneraria/screenshots/peoples-ruhe.png.import new file mode 100644 index 0000000..caf0cc7 --- /dev/null +++ b/docs/luneraria/screenshots/peoples-ruhe.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cm2ouge0ue0iu" +path="res://.godot/imported/peoples-ruhe.png-eddf9b45053b40c79df430dc349e2dea.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://docs/luneraria/screenshots/peoples-ruhe.png" +dest_files=["res://.godot/imported/peoples-ruhe.png-eddf9b45053b40c79df430dc349e2dea.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/docs/luneraria/screenshots/peoples-tod.png b/docs/luneraria/screenshots/peoples-tod.png new file mode 100644 index 0000000..1b55fda Binary files /dev/null and b/docs/luneraria/screenshots/peoples-tod.png differ diff --git a/docs/luneraria/screenshots/peoples-tod.png.import b/docs/luneraria/screenshots/peoples-tod.png.import new file mode 100644 index 0000000..f97e823 --- /dev/null +++ b/docs/luneraria/screenshots/peoples-tod.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dda4mvijbb7hm" +path="res://.godot/imported/peoples-tod.png-bd111b2c449299f368237555c9809982.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://docs/luneraria/screenshots/peoples-tod.png" +dest_files=["res://.godot/imported/peoples-tod.png-bd111b2c449299f368237555c9809982.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/docs/luneraria/screenshots/planning-1280x720.png b/docs/luneraria/screenshots/planning-1280x720.png index 8927a68..1faf99f 100644 Binary files a/docs/luneraria/screenshots/planning-1280x720.png and b/docs/luneraria/screenshots/planning-1280x720.png differ diff --git a/docs/luneraria/screenshots/planning-1920x1080.png b/docs/luneraria/screenshots/planning-1920x1080.png index 73701f2..1aee539 100644 Binary files a/docs/luneraria/screenshots/planning-1920x1080.png and b/docs/luneraria/screenshots/planning-1920x1080.png differ diff --git a/docs/luneraria/screenshots/planning.png b/docs/luneraria/screenshots/planning.png index d438a5c..febb920 100644 Binary files a/docs/luneraria/screenshots/planning.png and b/docs/luneraria/screenshots/planning.png differ diff --git a/docs/luneraria/screenshots/result.png b/docs/luneraria/screenshots/result.png index b9522d3..11205a4 100644 Binary files a/docs/luneraria/screenshots/result.png and b/docs/luneraria/screenshots/result.png differ diff --git a/docs/luneraria/screenshots/settlement.png b/docs/luneraria/screenshots/settlement.png index 8afdd95..39dfc62 100644 Binary files a/docs/luneraria/screenshots/settlement.png and b/docs/luneraria/screenshots/settlement.png differ diff --git a/scripts/luneraria/core/lune_bot.gd b/scripts/luneraria/core/lune_bot.gd index 5dce4b9..0b2d3a1 100644 --- a/scripts/luneraria/core/lune_bot.gd +++ b/scripts/luneraria/core/lune_bot.gd @@ -6,21 +6,28 @@ extends RefCounted const PROFILES: Dictionary = { "ausgewogen": {"front": .30, "melee": .25, "ranged": .25, "splash": .10, "support": .10, "upgrade_from": 6, "support_lane": 0.0, - "hero": "held_ringborke", "hero_round": 3, "items": ["basaltschild", "glutklinge", "rankenmantel"], "talents": [0, 1], "champion": ""}, - "verteidigung": {"front": .40, "melee": .15, "ranged": .20, "splash": .10, "support": .15, "upgrade_from": 5, "support_lane": 0.0, - "hero": "held_ringborke", "hero_round": 4, "items": ["rankenmantel", "basaltschild", "quellamulett"], "talents": [0, 0], "champion": ""}, + "hero": "held_ringborke", "hero_round": 4, "items": ["basaltschild", "glutklinge", "rankenmantel"], "talents": [0, 1], "champion": ""}, + "verteidigung": {"front": .35, "melee": .20, "ranged": .25, "splash": .10, "support": .10, "upgrade_from": 7, "support_lane": .15, + "hero": "held_ringborke", "hero_round": 3, "items": ["basaltschild", "rankenmantel", "glutklinge"], "talents": [1, 1], "champion": "ch_schutz"}, "druck": {"front": .20, "melee": .35, "ranged": .25, "splash": .15, "support": .05, "upgrade_from": 8, "support_lane": 0.0, "hero": "held_funkenlanze", "hero_round": 3, "items": ["glutklinge", "funkenfokus", "kristallsplitter"], "talents": [1, 1], "champion": "ch_belagerung"}, "fernkampf": {"front": .25, "melee": .10, "ranged": .40, "splash": .15, "support": .10, "upgrade_from": 6, "support_lane": 0.0, "hero": "held_funkenlanze", "hero_round": 4, "items": ["funkenfokus", "glutklinge", "kristallsplitter"], "talents": [0, 0], "champion": "ch_druck"}, - "teamhilfe": {"front": .30, "melee": .20, "ranged": .25, "splash": .10, "support": .15, "upgrade_from": 6, "support_lane": .35, - "hero": "held_quellgeflecht", "hero_round": 3, "items": ["quellamulett", "rankenmantel", "basaltschild"], "talents": [1, 0], "champion": "ch_schutz"}, - "bossjagd": {"front": .35, "melee": .25, "ranged": .20, "splash": .10, "support": .10, "upgrade_from": 6, "support_lane": 0.0, - "hero": "held_ringborke", "hero_round": 3, "items": ["glutklinge", "basaltschild", "rankenmantel"], "talents": [1, 1], "champion": "", - "boss_from": 5, "boss_troops": 7}, + "teamhilfe": {"front": .30, "melee": .20, "ranged": .25, "splash": .10, "support": .15, "upgrade_from": 6, "support_lane": .22, + "hero": "held_quellgeflecht", "hero_round": 4, "items": ["quellamulett", "rankenmantel", "basaltschild"], "talents": [1, 0], "champion": "ch_schutz"}, + "bossjagd": {"front": .35, "melee": .25, "ranged": .20, "splash": .10, "support": .10, "upgrade_from": 6, "support_lane": .10, + "hero": "held_ringborke", "hero_round": 3, "items": ["glutklinge", "basaltschild", "rankenmantel"], "talents": [1, 1], "champion": "ch_belagerung", + "boss_from": 5, "boss_troops": 5}, } +## Boss hunters attack from this Lanchester ratio on; each failed own attempt multiplies it. +const BOSS_BAR: float = 1.35 +const BOSS_BAR_STEP: float = 1.25 + static func profile_for(match_state: LuneMatch, slot: int) -> String: + # A match may fix the profile per slot (balance runs); otherwise a seed-based rotation. + if match_state.bot_profiles.size() == 6: + return match_state.bot_profiles[slot] var names: Array = PROFILES.keys() return names[(slot * 7 + match_state.seed_value) % names.size()] @@ -33,16 +40,9 @@ static func plan(match_state: LuneMatch, slot: int, profile_name: String = "") - for id: String in ids: roles[match_state.role_of(id)] = id _plan_hero(match_state, slot, profile) - # Heroes get one planned item every second round before the army is topped up. - var hero: Dictionary = player.draft.hero - if not hero.is_empty() and (hero.items as Array).size() < LuneContent.ITEM_SLOTS and match_state.round_number % 2 == 0: - for item_id: String in profile.items: - if not (hero.items as Array).has(item_id): - match_state.submit(slot, {"type": "item_buy", "item_id": item_id}) - break - # Champion bots invest in a pushing wave every third round before topping up the army. - if match_state.round_number % 3 == 0: - _plan_champion(match_state, slot, profile) + # Items and champions are planned before the army so the roster cannot drain the reserve. + _plan_items(match_state, slot, profile, 110) + _plan_champion(match_state, slot, profile) for guard: int in range(40): var draft: Dictionary = player.draft var roster: Array = draft.roster @@ -52,14 +52,17 @@ static func plan(match_state: LuneMatch, slot: int, profile_name: String = "") - if not acted and roster.size() < match_state.balance.supply_cap: var role: String = _needed_role(match_state, roster, profile) var lane: String = player.lane - if float(profile.support_lane) > 0.0 and match_state.rng.randf() < float(profile.support_lane): - lane = _weakest_lane(match_state, int(player.team)) + # Deterministic help cadence (lane offset, not shared RNG) keeps mirror matches fair. + if float(profile.support_lane) > 0.0: + var every: int = maxi(2, int(roundf(1.0 / maxf(0.1, float(profile.support_lane))))) + var lane_index: int = LuneMap.LANES.find(player.lane) + if (match_state.round_number + lane_index) % every == 0: + lane = _weakest_lane(match_state, int(player.team)) acted = match_state.submit(slot, {"type": "buy", "type_id": roles[role], "lane": lane}).ok if not acted and roster.size() < int(profile.upgrade_from): acted = _try_upgrade(match_state, slot, roster) if not acted: break - _plan_items(match_state, slot, profile) _plan_boss(match_state, slot, profile) match_state.set_ready(slot) @@ -76,22 +79,35 @@ static func _plan_hero(match_state: LuneMatch, slot: int, profile: Dictionary) - if (hero.talents as Array).size() == tier and level >= LuneContent.TALENT_LEVELS[tier]: match_state.submit(slot, {"type": "talent", "tier": tier, "choice": talents[tier]}) -static func _plan_items(match_state: LuneMatch, slot: int, profile: Dictionary) -> void: +static func _plan_items(match_state: LuneMatch, slot: int, profile: Dictionary, reserve: int) -> void: var hero: Dictionary = match_state.players[slot].draft.hero - if hero.is_empty(): + if hero.is_empty() or (hero.items as Array).size() >= LuneContent.ITEM_SLOTS: return for item_id: String in profile.items: - # Keep a reserve for the army: items only from surplus gold. - if int(match_state.players[slot].draft.gold) >= int(LuneContent.ITEMS[item_id].cost) + 60: - match_state.submit(slot, {"type": "item_buy", "item_id": item_id}) + if (hero.items as Array).has(item_id): + continue + # Keep a reserve so the army still grows; one free slot is enough to stop buying. + if int(match_state.players[slot].draft.gold) >= int(LuneContent.ITEMS[item_id].cost) + reserve: + if match_state.submit(slot, {"type": "item_buy", "item_id": item_id}).ok: + return static func _plan_champion(match_state: LuneMatch, slot: int, profile: Dictionary) -> void: var champion: String = str(profile.get("champion", "")) if champion.is_empty() or match_state.round_number < 3: return var player: Dictionary = match_state.players[slot] - if int(player.draft.gold) >= int(LuneContent.CHAMPIONS[champion].cost) + 80: - match_state.submit(slot, {"type": "champion_book", "champion_id": champion, "lane": player.lane}) + # At most one booked champion still on its way; bookings last exactly one round. + for booking: Dictionary in player.bookings: + if int(booking.deploy_round) > match_state.round_number: + return + var memory: Dictionary = match_state.bot_memory.get(slot, {}) + if int(memory.get("champion_round", 0)) + 2 >= match_state.round_number: + return + if int(player.draft.gold) < int(LuneContent.CHAMPIONS[champion].cost) + 40: + return + if match_state.submit(slot, {"type": "champion_book", "champion_id": champion, "lane": player.lane}).ok: + memory["champion_round"] = match_state.round_number + match_state.bot_memory[slot] = memory ## Only boss-hunting bots go to the grotto, and only with a strong own army; the boss state ## and lock are public information. @@ -109,9 +125,33 @@ static func _plan_boss(match_state: LuneMatch, slot: int, profile: Dictionary) - var picks: Array = roster.duplicate() picks.sort_custom(func(a: Dictionary, b: Dictionary) -> bool: return int(a.seq) < int(b.seq) if int(a.invested) == int(b.invested) else int(a.invested) > int(b.invested)) - for index: int in range(mini(int(profile.boss_troops) - 1, picks.size())): - match_state.submit(slot, {"type": "assign", "uid": picks[index].uid, "lane": LuneMatch.GROTTO}) + picks.resize(mini(int(profile.boss_troops) - 1, picks.size())) var hero: Dictionary = player.draft.hero + # The boss formula is public. Lanchester estimate: total HP x total damage of the + # expedition against the boss. A failed own attempt (public outcome) raises the bar. + var total_hp: float = 0.0 + var total_damage: float = 0.0 + for entry: Dictionary in picks: + var definition: Dictionary = DataCatalog.get_unit(entry.type_id) + total_hp += float(definition.max_hp) + total_damage += float(definition.damage) + if not hero.is_empty(): + var hero_def: Dictionary = LuneContent.hero_definition(hero) + total_hp += float(hero_def.max_hp) + total_damage += float(hero_def.damage) * (1.0 + float(hero_def.damage_bonus)) + var boss_def: Dictionary = LuneContent.boss_definition(match_state.round_number, match_state.balance, false) + var ratio: float = total_hp * total_damage / (float(boss_def.max_hp) * float(boss_def.damage)) + var memory: Dictionary = match_state.bot_memory.get(slot, {"bar": BOSS_BAR, "sent": 0}) + for event: Dictionary in match_state.boss_log: + if int(event.round) == int(memory.sent) and event.event == "failed": + memory.bar = float(memory.bar) * BOSS_BAR_STEP + memory.sent = 0 + match_state.bot_memory[slot] = memory + if ratio < float(memory.bar): + return + memory.sent = match_state.round_number + for entry: Dictionary in picks: + match_state.submit(slot, {"type": "assign", "uid": entry.uid, "lane": LuneMatch.GROTTO}) if not hero.is_empty(): match_state.submit(slot, {"type": "assign", "uid": hero.uid, "lane": LuneMatch.GROTTO}) diff --git a/scripts/luneraria/core/lune_content.gd b/scripts/luneraria/core/lune_content.gd index 115986c..a5bb94e 100644 --- a/scripts/luneraria/core/lune_content.gd +++ b/scripts/luneraria/core/lune_content.gd @@ -68,7 +68,7 @@ const CHAMPIONS: Dictionary = { "ch_druck": {"name": "Dunstsänger", "role": "Druck auf Unterstützer", "model": "s03", "cost": 150, "base": "s03", "text": "Sucht Unterstützer und Fernkämpfer und verlangsamt deren Angriffe.", "priority": "support", "view_scale": 1.25}, "ch_belagerung": {"name": "Keilbrecher", "role": "Belagerung", "model": "s04", "cost": 180, "base": "s04", - "text": "Greift bevorzugt Gebäude an und verursacht dort ×2,5 Schaden.", "priority": "building", "siege_factor": 2.5, "view_scale": 1.3}, + "text": "Greift bevorzugt Gebäude an und verursacht dort ×1,8 Schaden.", "priority": "building", "siege_factor": 1.8, "view_scale": 1.3}, } const CHAMPION_HP: float = 2.2 const CHAMPION_DAMAGE: float = 1.6 @@ -78,10 +78,10 @@ const MAX_BOOKINGS: int = 2 # ---------------------------------------------------------------- boss const BOSS_MODEL: String = "e10" -const BOSS_GROTTO_HP: float = 4200.0 +const BOSS_GROTTO_HP: float = 3600.0 const BOSS_GROTTO_DAMAGE: float = 55.0 -const BOSS_DEPLOY_HP: float = 5200.0 -const BOSS_DEPLOY_DAMAGE: float = 70.0 +const BOSS_DEPLOY_HP: float = 6800.0 +const BOSS_DEPLOY_DAMAGE: float = 88.0 const BOSS_BOUNTY: int = 520 const BOSS_BOUNTY_PER_ROUND: int = 60 ## Victory in round n: deployment in n+1, locked in n+1..n+3, available again from n+4. diff --git a/scripts/luneraria/core/lune_match.gd b/scripts/luneraria/core/lune_match.gd index e0f2956..88f2559 100644 --- a/scripts/luneraria/core/lune_match.gd +++ b/scripts/luneraria/core/lune_match.gd @@ -30,6 +30,10 @@ var history: Array[Dictionary] = [] ## Public boss state. claim = {team, won_round, deploy_round, chooser_slot, lane} or {}. var boss: Dictionary = {} var boss_log: Array[Dictionary] = [] +## Optional fixed bot profile per slot (balance runs); empty means seed-based rotation. +var bot_profiles: Array = [] +## Bot-private notes per slot, derived only from the bot's own actions and public outcomes. +var bot_memory: Dictionary = {} ## Remainder rotation per team so both teams follow the same distribution pattern. var _reward_rotation: Array[int] = [0, 0] @@ -51,6 +55,8 @@ func setup(options: Dictionary = {}) -> void: boss = {"next_available_round": 1, "claim": {}} boss_log.clear() players.clear() + bot_profiles = options.get("profiles", []) + bot_memory.clear() var human: int = int(options.get("human_slot", 1)) var bunds: Array = options.get("bunds", []) for slot: int in range(6): diff --git a/scripts/luneraria/ui/lune_hero_panel.gd.uid b/scripts/luneraria/ui/lune_hero_panel.gd.uid new file mode 100644 index 0000000..89f86f0 --- /dev/null +++ b/scripts/luneraria/ui/lune_hero_panel.gd.uid @@ -0,0 +1 @@ +uid://cwjygujajwr7r diff --git a/scripts/luneraria/ui/lune_reinforce_panel.gd.uid b/scripts/luneraria/ui/lune_reinforce_panel.gd.uid new file mode 100644 index 0000000..42bdfb8 --- /dev/null +++ b/scripts/luneraria/ui/lune_reinforce_panel.gd.uid @@ -0,0 +1 @@ +uid://2f0vric1dx0m diff --git a/scripts/luneraria/view/lune_world.gd b/scripts/luneraria/view/lune_world.gd index d30e4b8..a5583c5 100644 --- a/scripts/luneraria/view/lune_world.gd +++ b/scripts/luneraria/view/lune_world.gd @@ -10,6 +10,9 @@ const TOWER := "res://assets/luneraria/tower.glb" const ROCK := "res://assets/compare/compare-rock.glb" const TREE := "res://assets/compare/compare-tree.glb" const GROTTO := "res://assets/luneraria/grotto.glb" +const PROPS := "res://assets/luneraria/map_props.glb" +## Lane crossings over the river (x = z); all three lie on the symmetry axis. +const CROSSINGS: Array[Vector2] = [Vector2(-25, -25), Vector2(0, 0), Vector2(25, 25)] const TEAM_COLORS: Array[Color] = [Color("5aa8ff"), Color("ff6a55")] const TEAM_NAMES: Array[String] = ["Blau", "Rot"] @@ -28,7 +31,11 @@ func build(match_map: LuneMap) -> void: _lane_ribbon(map.lanes[lane]) for team: int in range(2): _base_plaza(team) + _load_props() + _bridges() + _curbs() _decoration() + _edges() for team: int in range(2): var crystal := StandardMaterial3D.new() crystal.albedo_color = TEAM_COLORS[team] @@ -199,42 +206,127 @@ func _far_from_lanes(point: Vector2, margin: float) -> bool: return false return true +var _props: Dictionary = {} + +## Prop meshes from the Blender generator, with the Kluft shaders applied per surface. +func _load_props() -> void: + var scene: Node3D = (load(PROPS) as PackedScene).instantiate() + for child: Node in scene.get_children(): + if child is MeshInstance3D: + var mesh: ArrayMesh = (child as MeshInstance3D).mesh.duplicate() + for surface: int in range(mesh.get_surface_count()): + var source := mesh.surface_get_material(surface) as StandardMaterial3D + if source != null and source.resource_name.begins_with("ENV_") and not source.resource_name.contains("brass"): + mesh.surface_set_material(surface, terrain(source.resource_name, source.albedo_color)) + _props[str(child.name)] = mesh + scene.free() + +func _multimesh(prop: String, transforms: Array[Transform3D], shadows: bool = true) -> MultiMeshInstance3D: + var multimesh := MultiMesh.new() + multimesh.transform_format = MultiMesh.TRANSFORM_3D + multimesh.mesh = _props[prop] + multimesh.instance_count = transforms.size() + for index: int in range(transforms.size()): + multimesh.set_instance_transform(index, transforms[index]) + var instance := MultiMeshInstance3D.new() + instance.name = "Props_" + prop + instance.multimesh = multimesh + instance.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_ON if shadows else GeometryInstance3D.SHADOW_CASTING_SETTING_OFF + add_child(instance) + return instance + +func _bridges() -> void: + var transforms: Array[Transform3D] = [] + for point: Vector2 in CROSSINGS: + transforms.append(Transform3D(Basis(Vector3.UP, PI * .25), to3(point))) + _multimesh("Bridge", transforms) + +func _near_crossing(point: Vector2, margin: float) -> bool: + for crossing: Vector2 in CROSSINGS: + if crossing.distance_to(point) < margin: + return true + return false + +## Curb stones along both lane edges, skipped on bridges, at bases and around towers. +func _curbs() -> void: + var transforms: Array[Transform3D] = [] + var offset: float = map.balance.lane_half_width + 1.0 + for lane: String in LuneMap.LANES: + var path: LuneMap.LanePath = map.lanes[lane] + var s: float = 0.0 + while s < path.length: + var sampled: Array = path.sample(s) + var tangent: Vector2 = sampled[1] + var normal := Vector2(-tangent.y, tangent.x) + for side: float in [-1.0, 1.0]: + var at: Vector2 = sampled[0] + normal * offset * side + var clear: bool = not _near_crossing(at, 6.5) and map.core_position(0).distance_to(at) > 11.0 and map.core_position(1).distance_to(at) > 11.0 + for definition: Dictionary in map.buildings: + clear = clear and Vector2(definition.position).distance_to(at) > 2.2 + if clear: + transforms.append(Transform3D(Basis(Vector3.UP, atan2(-tangent.y, tangent.x)), to3(at))) + s += 1.55 + _multimesh("Curb", transforms, false) + func _decoration() -> void: # Scatter one half deterministically and mirror it, so decoration never favours a team. var rng := RandomNumberGenerator.new() rng.seed = 90210 - var tree: PackedScene = load(TREE) - var rock: PackedScene = load(ROCK) + var kinds: Array[String] = ["Pine_0", "Pine_1", "Pine_2", "Oak_0", "Oak_1", "Oak_2", "Rock_0", "Rock_1", "Rock_2", "Shrub_0", "Shrub_1"] + var weights: Array[float] = [1.2, 1.0, 1.0, 1.2, 1.0, .8, .7, .5, .8, 1.0, 1.0] + var total: float = 0.0 + for weight: float in weights: + total += weight + var buckets: Dictionary = {} var placed: Array[Vector2] = [] var tries: int = 0 - while placed.size() < 70 and tries < 4000: + while placed.size() < 150 and tries < 8000: tries += 1 - var point := Vector2(rng.randf_range(-LuneMap.HALF_EXTENT - 4, LuneMap.HALF_EXTENT + 4), rng.randf_range(-LuneMap.HALF_EXTENT - 4, LuneMap.HALF_EXTENT + 4)) - if point.y < point.x or not _far_from_lanes(point, 5.2): + var point := Vector2(rng.randf_range(-LuneMap.HALF_EXTENT - 2, LuneMap.HALF_EXTENT + 2), rng.randf_range(-LuneMap.HALF_EXTENT - 2, LuneMap.HALF_EXTENT + 2)) + if point.y < point.x or not _far_from_lanes(point, 4.9): continue var clear: bool = true for other: Vector2 in placed: - if other.distance_to(point) < 2.6: + if other.distance_to(point) < 1.9: clear = false break if not clear: continue placed.append(point) - var is_tree: bool = rng.randf() < .62 - var size: float = rng.randf_range(1.1, 1.9) if is_tree else rng.randf_range(.8, 1.6) + var roll: float = rng.randf() * total + var kind: String = kinds[-1] + for index: int in range(kinds.size()): + roll -= weights[index] + if roll <= 0.0: + kind = kinds[index] + break + var size: float = rng.randf_range(.85, 1.25) var yaw: float = rng.randf() * TAU + if not buckets.has(kind): + buckets[kind] = [] as Array[Transform3D] for mirrored: bool in [false, true]: var at: Vector2 = LuneMap.mirror(point) if mirrored else point - var holder := Node3D.new() - holder.position = to3(at) - holder.rotation.y = yaw if not mirrored else -yaw - holder.scale = Vector3.ONE * size - var prop: Node3D = (tree if is_tree else rock).instantiate() - # The compare-pack props were exported with their height along -Z; stand them up. - prop.rotation.x = PI * .5 - _dress(prop) - holder.add_child(prop) - add_child(holder) + var basis := Basis(Vector3.UP, -yaw if mirrored else yaw).scaled(Vector3.ONE * size) + (buckets[kind] as Array[Transform3D]).append(Transform3D(basis, to3(at))) + for kind: String in buckets: + _multimesh(kind, buckets[kind], not kind.begins_with("Shrub")) + +## A ring of cliff segments frames the playable square. +func _edges() -> void: + var transforms: Array[Transform3D] = [] + var edge: float = LuneMap.HALF_EXTENT + 5.0 + for side: int in range(4): + for index: int in range(7): + var along: float = -edge + 6.0 + index * 12.0 + var at: Vector2 + var yaw: float + match side: + 0: at = Vector2(along, -edge); yaw = 0.0 + 1: at = Vector2(edge, along); yaw = PI * .5 + 2: at = Vector2(-along, edge); yaw = PI + _: at = Vector2(-edge, -along); yaw = PI * 1.5 + transforms.append(Transform3D(Basis(Vector3.UP, yaw), to3(at))) + _multimesh("EdgeCliff", transforms) var _vertex_colored: StandardMaterial3D @@ -278,6 +370,24 @@ func _building(definition: Dictionary) -> void: tower.rotation.y = atan2(toward.x, toward.y) root.add_child(tower) root.set_meta("tower", tower) + if definition.tier == "core": + for side: float in [-1.0, 1.0]: + # Team banners flank each Ancient. + var banner := MeshInstance3D.new() + banner.mesh = _props["Banner"] + var cloth := StandardMaterial3D.new() + cloth.albedo_color = TEAM_COLORS[team] + cloth.cull_mode = BaseMaterial3D.CULL_DISABLED + cloth.roughness = .8 + for surface: int in range(banner.mesh.get_surface_count()): + var source: Material = banner.mesh.surface_get_material(surface) + if source != null and source.resource_name == "PR_Banner": + banner.set_surface_override_material(surface, cloth) + var toward: Vector2 = (map.core_position(1 - team) - map.core_position(team)).normalized() + var across := Vector2(-toward.y, toward.x) * side * 3.6 + banner.position = Vector3(across.x, 0, across.y) + Vector3(toward.x, 0, toward.y) * 2.4 + banner.rotation.y = atan2(toward.x, toward.y) + root.add_child(banner) var light := OmniLight3D.new() light.position = Vector3(0, 5.2 if definition.tier != "core" else 2.4, 0) light.light_color = TEAM_COLORS[team] diff --git a/scripts/presentation/asset_library.gd b/scripts/presentation/asset_library.gd index 84b92ec..73c67c3 100644 --- a/scripts/presentation/asset_library.gd +++ b/scripts/presentation/asset_library.gd @@ -32,6 +32,8 @@ func _ready() -> void: _models[str(entry.id)] = entry for entry: Dictionary in _read_manifest("res://assets/phase19_manifest.json"): _models[str(entry.id)] = entry + for entry: Dictionary in _read_manifest("res://assets/luneraria/peoples_manifest.json"): + _models[str(entry.id)] = entry for entry: Dictionary in _read_manifest("res://assets/phase3_manifest.json"): _icons[str(entry.id)] = str(entry.resource_path) _icons["worker"] = _icons.get("worker_funkenleser", "") @@ -197,7 +199,7 @@ func _collect(node: Node3D, parent_transform: Transform3D, surfaces: Dictionary) _collect(child, transform, surfaces) func presentation_material(source: Material) -> Material: - if source is StandardMaterial3D and (source.resource_name.begins_with("AB_") or source.resource_name.begins_with("BB_") or source.resource_name.begins_with("QG_") or source.resource_name.begins_with("FL_") or source.resource_name.begins_with("GB_")): + if source is StandardMaterial3D and (source.resource_name.begins_with("AB_") or source.resource_name.begins_with("BB_") or source.resource_name.begins_with("QG_") or source.resource_name.begins_with("FL_") or source.resource_name.begins_with("GB_") or source.resource_name.begins_with("LP_")): var ab_key: String = source.resource_name if not _surface_materials.has(ab_key): if source.emission_enabled or ab_key in ["AB_Lamp", "AB_Spark", "AB_Glass", "AB_Lens", "BB_Magma", "BB_Ember", "QG_Spring", "FL_Ember", "FL_Crystal", "GB_Crystal", "GB_Ember"]: @@ -208,9 +210,9 @@ func presentation_material(source: Material) -> Material: ab.set_shader_parameter("roughness_value", source.roughness) ab.set_shader_parameter("metallic_value", source.metallic) var surface: int = 5 if source.metallic > .5 else 0 - if "Leather" in ab_key or "Boot" in ab_key or "Glove" in ab_key: + if "Leather" in ab_key or "Boot" in ab_key or "Glove" in ab_key or ab_key.begins_with("LP_") and ab_key.ends_with("_Skin"): surface = 1 - elif "Cloth" in ab_key or "Hood" in ab_key: + elif "Cloth" in ab_key or "Hood" in ab_key or "LP_" in ab_key and ("Fur" in ab_key or "Hair" in ab_key): surface = 2 elif "Bark" in ab_key or "Wood" in ab_key or "Vine" in ab_key: surface = 3 diff --git a/tools/luneraria/analyze_balance.py b/tools/luneraria/analyze_balance.py new file mode 100644 index 0000000..524a388 --- /dev/null +++ b/tools/luneraria/analyze_balance.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +"""Summarise a balance_run JSON: side fairness, profile and Bund strength, match length.""" +import json, sys +from collections import Counter, defaultdict +rows = json.load(open(sys.argv[1])) +side = Counter(); lengths = []; limit = 0; timeouts = 0; consistent = 0 +prof_w = Counter(); prof_n = Counter(); bund_w = Counter(); bund_n = Counter(); boss = Counter() +max_combat = 0.0; hero_levels = [] +for row in rows: + results = [] + for key, flip in (("normal", False), ("swapped", True)): + game = row[key] + profiles = row["profiles"][3:] + row["profiles"][:3] if flip else row["profiles"] + bunds = row["bunds"][3:] + row["bunds"][:3] if flip else row["bunds"] + side[game["result"]] += 1 + lengths.append(game["rounds"]); timeouts += game["timeouts"]; max_combat = max(max_combat, game["max_combat"]) + hero_levels += [l for l in game["hero_levels"] if l > 0] + for e in game["boss"]: + boss[e.split(":")[1]] += 1 + if game["result"] == "testlimit": + limit += 1 + continue + winner = 0 if game["result"] == "blau" else 1 if game["result"] == "rot" else -1 + # Winning composition: which original half won (0 = first three slots of the row). + results.append(-1 if winner < 0 else (winner if not flip else 1 - winner)) + for slot in range(6): + prof_n[profiles[slot]] += 1; bund_n[bunds[slot]] += 1 + if slot // 3 == winner: + prof_w[profiles[slot]] += 1; bund_w[bunds[slot]] += 1 + if len(results) == 2 and results[0] == results[1] and results[0] >= 0: + consistent += 1 +games = sum(side.values()) +print(f"Partien {games} · Ergebnisse {dict(side)} · Testlimit {limit} · Zeitfenster-Kämpfe {timeouts} · längster Kampf {max_combat:.0f} s") +print(f"Wellen bis Ende: min {min(lengths)} · median {sorted(lengths)[len(lengths)//2]} · max {max(lengths)}") +print(f"Seitentausch: dieselbe Zusammensetzung gewinnt beide Male in {consistent}/{len(rows)} Paaren") +print("Profile (Siege/Einsätze):", {p: f"{prof_w[p]}/{prof_n[p]} ({100*prof_w[p]/max(1,prof_n[p]):.0f} %)" for p in sorted(prof_n)}) +print("Bünde:", {b: f"{bund_w[b]}/{bund_n[b]} ({100*bund_w[b]/max(1,bund_n[b]):.0f} %)" for b in sorted(bund_n)}) +print("Boss:", dict(boss), "· Heldenstufen am Ende (Median):", sorted(hero_levels)[len(hero_levels)//2] if hero_levels else "-") + +if rows and all(row.get("matchup") for row in rows): + # Pure profile duels: wins of the row's first profile over its second, both sides. + print("Duelle (Siege Zeile gegen Spalte, je 2 Partien mit Seitentausch):") + names = sorted({row["profiles"][0] for row in rows} | {row["profiles"][3] for row in rows}) + wins = defaultdict(int) + for row in rows: + a, b = row["profiles"][0], row["profiles"][3] + for key, flip in (("normal", False), ("swapped", True)): + r = row[key]["result"] + if r in ("blau", "rot"): + first_won = (r == "blau") != flip + wins[(a, b) if first_won else (b, a)] += 1 + print(" " + " ".join(f"{n[:6]:>7}" for n in names) + " gesamt") + for n in names: + total = sum(wins[(n, m)] for m in names) + print(f"{n[:12]:<12}" + " ".join(f"{('-' if n == m else wins[(n, m)]):>7}" for m in names) + f" {total}/{2 * (len(names) - 1)}") diff --git a/tools/luneraria/balance_run.gd b/tools/luneraria/balance_run.gd new file mode 100644 index 0000000..0b50654 --- /dev/null +++ b/tools/luneraria/balance_run.gd @@ -0,0 +1,74 @@ +extends Node +## Balance run: random team compositions (profiles and Bunds per slot), each played twice +## with the sides swapped. A QA round limit is reported as "Testlimit", never as a result. +## Run: godot --headless --path . res://tools/luneraria/balance_run.tscn -- --compositions=20 --limit=60 --out=res://docs/luneraria/logs/m3/balance.json +## add --matchups for all pairs of pure profile teams instead of random compositions. +func _ready() -> void: + call_deferred("run") + +func play(options: Dictionary, limit: int) -> Dictionary: + var state := LuneMatch.new() + state.setup(options) + var lane_losses: Array[int] = [0, 0] + while state.phase != LuneMatch.Phase.OVER and state.round_number <= limit: + for slot: int in range(6): + LuneBot.plan(state, slot) + state.begin_combat() + state.finish_combat() + for team: int in range(2): + for lane: String in LuneMap.LANES: + lane_losses[team] += int(state.towers_alive(team, lane) == 0) + var result: String = "testlimit" + if state.phase == LuneMatch.Phase.OVER: + result = "draw" if state.winner == LuneMatch.DRAW else ("blau" if state.winner == 0 else "rot") + var gold_curve: Array = state.history.map(func(h: Dictionary) -> int: return (h.gold as Array).reduce(func(a: int, b: int) -> int: return a + b, 0)) + return {"result": result, "rounds": state.history.size(), "lane_losses_at_end": lane_losses, + "boss": state.boss_log.map(func(e: Dictionary) -> String: return "%d:%s:%s" % [e.round, e.event, str(e.get("team", ""))]), + "hero_levels": state.history[-1].hero_levels if not state.history.is_empty() else [], + "timeouts": state.history.filter(func(h: Dictionary) -> bool: return h.end_reason == "timeout").size(), + "max_combat": state.history.map(func(h: Dictionary) -> float: return h.combat_time).max(), "total_gold_curve": gold_curve} + +func run() -> void: + var compositions: int = 20 + var limit: int = 60 + var matchups: bool = false + var out: String = "res://docs/luneraria/logs/m3/balance.json" + for arg: String in OS.get_cmdline_user_args(): + if arg.begins_with("--compositions="): compositions = int(arg.substr(15)) + elif arg.begins_with("--limit="): limit = int(arg.substr(8)) + elif arg.begins_with("--out="): out = arg.substr(6) + elif arg == "--matchups": matchups = true + var rng := RandomNumberGenerator.new() + rng.seed = 20260928 + var names: Array = LuneBot.PROFILES.keys() + # --matchups: every pair of pure profile teams (all three slots alike) with the same + # Bund order on both sides, so only the strategy differs. Otherwise random compositions. + var setups: Array = [] + if matchups: + for i: int in range(names.size()): + for j: int in range(i + 1, names.size()): + setups.append({"profiles": [names[i], names[i], names[i], names[j], names[j], names[j]], + "bunds": ["wurzelbund", "glutpakt", "wurzelbund", "wurzelbund", "glutpakt", "wurzelbund"], "seed": 2000 + setups.size()}) + else: + for index: int in range(compositions): + var drawn := {"profiles": [], "bunds": [], "seed": 1000 + index} + for slot: int in range(6): + drawn.profiles.append(names[rng.randi() % names.size()]) + drawn.bunds.append(DataCatalog.FACTIONS[rng.randi() % DataCatalog.FACTIONS.size()]) + setups.append(drawn) + var rows: Array = [] + for index: int in range(setups.size()): + var profiles: Array = setups[index].profiles + var bunds: Array = setups[index].bunds + var seed: int = int(setups[index].seed) + var normal: Dictionary = play({"seed": seed, "human_slot": -1, "profiles": profiles, "bunds": bunds}, limit) + var swapped_profiles: Array = profiles.slice(3, 6) + profiles.slice(0, 3) + var swapped_bunds: Array = bunds.slice(3, 6) + bunds.slice(0, 3) + var swapped: Dictionary = play({"seed": seed, "human_slot": -1, "profiles": swapped_profiles, "bunds": swapped_bunds}, limit) + var row := {"index": index, "seed": seed, "profiles": profiles, "bunds": bunds, "matchup": matchups, "normal": normal, "swapped": swapped} + rows.append(row) + print("BALANCE %02d %s | %s -> %s (%d) / swapped %s (%d)" % [index, str(profiles.slice(0, 3)), str(profiles.slice(3, 6)), normal.result, normal.rounds, swapped.result, swapped.rounds]) + var file := FileAccess.open(out, FileAccess.WRITE) + file.store_string(JSON.stringify(rows, "\t")) + file.close() + get_tree().quit() diff --git a/tools/luneraria/balance_run.gd.uid b/tools/luneraria/balance_run.gd.uid new file mode 100644 index 0000000..3c8452e --- /dev/null +++ b/tools/luneraria/balance_run.gd.uid @@ -0,0 +1 @@ +uid://b1p7j2o6ib8do diff --git a/tools/luneraria/balance_run.tscn b/tools/luneraria/balance_run.tscn new file mode 100644 index 0000000..790648e --- /dev/null +++ b/tools/luneraria/balance_run.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=3] + +[ext_resource type="Script" path="res://tools/luneraria/balance_run.gd" id="1"] + +[node name="BalanceRun" type="Node"] +script = ExtResource("1") diff --git a/tools/luneraria/boss_scenarios.gd.uid b/tools/luneraria/boss_scenarios.gd.uid new file mode 100644 index 0000000..0cb0eeb --- /dev/null +++ b/tools/luneraria/boss_scenarios.gd.uid @@ -0,0 +1 @@ +uid://dkxn6lwcfrxlk diff --git a/tools/luneraria/build_asset_matrix.py b/tools/luneraria/build_asset_matrix.py index e7e8b41..2074681 100644 --- a/tools/luneraria/build_asset_matrix.py +++ b/tools/luneraria/build_asset_matrix.py @@ -13,6 +13,7 @@ skeletal = {} for name in ['phase11', 'phase12', 'phase15', 'phase16', 'phase18', 'phase19']: for a in json.loads((P / f'assets/{name}_manifest.json').read_text())['assets']: skeletal[a['id']] = (a, name) +peoples = {a['id']: a for a in json.loads((P / 'assets/luneraria/peoples_manifest.json').read_text())['assets']} # id -> (bisherige Rolle, neue Rolle, Volk/Bund-Zuordnung, Eingriff) ROLES = { @@ -56,6 +57,15 @@ ROLES = { 'worker': ('Arbeiter/Funken', 'nicht im neuen Modus (Arbeitsannahme E); Modell bleibt erhalten', '—', 'keiner'), 'map_lane_segment': ('Lane-Kachel alt', 'nicht verwendet; Lanes neu erzeugt', '—', 'keiner'), } +PEOPLE_ROLES = { + 'p_mensch': ('—', 'Volksrepräsentant Front (Ritter)', 'Menschen', 'neu (M3)'), + 'p_elf': ('—', 'Volksrepräsentant Fernkampf (Waldläuferin)', 'Elfen', 'neu (M3)'), + 'p_zwerg': ('—', 'Volksrepräsentant Front (Schildträger)', 'Zwerge', 'neu (M3)'), + 'p_troll': ('—', 'Volksrepräsentant Fläche (Steinhauer)', 'Trolle', 'neu (M3)'), + 'p_fee': ('—', 'Volksrepräsentant Unterstützung (Lichtheilerin)', 'Feen', 'neu (M3)'), + 'p_ork': ('—', 'Volksrepräsentant Nahkampf (Kriegshäuptling)', 'Orks', 'neu (M3)'), + 'p_biest': ('—', 'Volksrepräsentant Nahkampf (Klauenjäger)', 'Biestmenschen', 'neu (M3)'), +} for prefix, people in (('m', 'Nietenkonvent (nicht spielbar)'), ('n', 'Nebelchor (nicht spielbar)')): for i in range(1, 11): ROLES[f'{prefix}{i:02d}'] = ('nicht spielbar, niedrige Detailstufe', 'Reserve für spätere Völker', people, 'Neuaufbau nötig vor Nutzung') @@ -78,17 +88,25 @@ for asset_id in sorted(set(phase4) | set(skeletal)): old, new, people, work = ROLES.get(asset_id, ('?', 'offen', '?', 'Prüfung nötig')) rows.append((asset_id, base.get('name') or base.get('display_name') or skeletal.get(asset_id, ({},))[0].get('name', ''), path, source, rig, tris, old, new, people, material, work)) +for asset_id in sorted(peoples): + record = peoples[asset_id] + old, new, people, work = PEOPLE_ROLES[asset_id] + rows.append((asset_id, record.get('name', ''), record['resource_path'], record.get('source', ''), + '%d Knochen · %d Clips' % (record['bones'], len(record['clips'])), record['triangles'], + old, new, people, 'gewichtetes Skelett (peoples, LP_*-Materialien)', work)) + extra = [ ('menu-stage', 'Menü-Diorama', 'res://assets/environment/menu-stage.glb', 'assets-source/phase20/build_menu_stage.py', 'statisch', '48660', 'Hauptmenü Phase 20A', 'Hauptmenü (Titel angepasst)', '—', 'Kluft-Shader', 'keiner'), ('verdant-rift', 'Grüne Kluft (2-Bretter-Arena)', 'res://assets/environment/verdant-rift.glb', 'assets-source/phase10/verdant-rift.blend', 'statisch', '—', 'Spielfeld Saumwacht', 'Legacy-Modus; Materialien/Shader für die neue Karte wiederverwendet', '—', 'triplanare Kluft-Shader', 'keine neue Nutzung als Karte'), - ('compare-rock / compare-tree', 'Fels, Baum', 'res://assets/compare/', 'assets-source/phase14/build_compare_props.py', 'statisch', '360 / 852', 'Vergleichsszene', 'Kartendekoration Dschungel', '—', 'einfach', 'keiner'), ('lane_tower', 'Laneturm', 'res://assets/luneraria/tower.glb', 'assets-source/luneraria/build_tower.py', 'statisch', '648', '—', '16 Lanetürme und Basistürme, Kristall zur Laufzeit in Teamfarbe', '—', 'ENV_*-Kluftshader, Kristall emissiv', 'neu (M1)'), ('boss_grotto', 'Bossgrotte', 'res://assets/luneraria/grotto.glb', 'assets-source/luneraria/build_grotto.py', 'statisch', '1888', '—', 'Felsinsel mit Höhle auf der Symmetrieachse, zwei gespiegelte Zugänge', '—', 'ENV_*-Kluftshader, violette Kristalle', 'neu (M2)'), + ('map_props', 'Kartenprops (Bäume, Felsen, Büsche, Brücken, Bordsteine, Klippen, Banner)', 'res://assets/luneraria/map_props.glb', 'assets-source/luneraria/build_map_props.py', 'statisch', 'MultiMesh', '—', '3D-Kartendekoration der drei Lanes, Brücken über die Kluft, Klampterrahmen', '—', 'ENV_*-Kluftshader', 'neu (M3)'), ('luneraria-emblem', 'Emblem Luneraria', 'res://assets/ui/emblems/luneraria.svg', 'SVG', '—', '—', '—', 'Titel und Menü', '—', 'Vektor', 'neu (M1)'), ('Emblem/Theme', 'Saumwacht-Theme', 'scripts/ui/theme/, assets/ui/', 'Code / SVG', '—', '—', 'Menü 20A', 'Theme weiterverwendet, Titel/Emblem Luneraria', '—', 'Vektor', 'Titelschrift/Emblem ergänzen'), + ('compare-rock / compare-tree', 'Fels, Baum (Alt)', 'res://assets/compare/', 'assets-source/phase14/build_compare_props.py', 'statisch', '360 / 852', 'Vergleichsszene', 'ab M3 durch map_props ersetzt', '—', 'einfach', 'abgelöst (M3)'), ] lines = ['# Asset-Wiederverwendungsmatrix', '', - 'Erzeugt mit `python3 tools/luneraria/build_asset_matrix.py` aus den Laufzeitmanifesten. „Volk/Bund“ ist die Datenzuordnung. Die sieben Zielvölker (Menschen, Elfen, Zwerge, Trolle, Feen, Orks, Biestmenschen) haben noch **keine eigenen Modelle**; bestehende Wesen bleiben als eigene Bünde Teil der Welt. Als „vorläufig“ markierte Rollen sind Zuordnungen vorhandener Modelle, keine neu erstellten Völker.', '', + 'Erzeugt mit `python3 tools/luneraria/build_asset_matrix.py` aus den Laufzeitmanifesten. „Volk/Bund“ ist die Datenzuordnung. Die sieben Zielvölker haben ab M3 **eigene Repräsentantenmodelle** (`p_*`); sie sind noch nicht als spielbare Fraktionen eingebunden. Bestehende Wesen bleiben als eigene Bünde Teil der Welt. Als „vorläufig“ markierte Rollen sind Zuordnungen vorhandener Modelle, keine neu erstellten Völker.', '', '| ID | Name | Pfad | Quelle | Rig / Clips | Dreiecke | bisherige Rolle | neue Rolle | Volk / Bund | Materialzustand | Eingriff |', '| --- | --- | --- | --- | --- | ---: | --- | --- | --- | --- | --- |'] for row in rows + extra: diff --git a/tools/luneraria/people_lineup.gd b/tools/luneraria/people_lineup.gd new file mode 100644 index 0000000..fc7ccfd --- /dev/null +++ b/tools/luneraria/people_lineup.gd @@ -0,0 +1,86 @@ +extends Node3D +## Review lineup for the seven people representatives: the imported GLBs through +## AssetLibrary (same material path as in the match), posed at fixed clip times. +## Run (window): godot --path . res://tools/luneraria/people_lineup.tscn -- --out=res://docs/luneraria/screenshots +const Sanctuary = preload("res://scripts/presentation/sanctuary.gd") +const IDS: Array[String] = ["p_mensch", "p_elf", "p_zwerg", "p_troll", "p_fee", "p_ork", "p_biest"] +const POSES: Array = [["idle", 0.6, "ruhe"], ["attack_a", 0.9, "kontakt"], ["walk", 0.25, "laufen"], ["ability", 0.8, "faehigkeit"], ["death", 2.0, "tod"]] +var figures: Array = [] +var camera: Camera3D +var out: String = "res://docs/luneraria/screenshots" + +func _ready() -> void: + for arg: String in OS.get_cmdline_user_args(): + if arg.begins_with("--out="): + out = arg.substr(6) + var environment := WorldEnvironment.new() + environment.environment = Environment.new() + environment.environment.background_mode = Environment.BG_COLOR + environment.environment.background_color = Color(.16, .18, .2) + environment.environment.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR + environment.environment.ambient_light_color = Color(.55, .58, .62) + environment.environment.ambient_light_energy = .6 + environment.environment.tonemap_mode = Environment.TONE_MAPPER_ACES + add_child(environment) + var sun := DirectionalLight3D.new() + sun.rotation = Vector3(deg_to_rad(-48), deg_to_rad(-32), 0) + sun.light_energy = 1.35 + sun.shadow_enabled = true + add_child(sun) + var ground := MeshInstance3D.new() + var plane := PlaneMesh.new() + plane.size = Vector2(24, 10) + ground.mesh = plane + var soil := StandardMaterial3D.new() + soil.resource_name = "ENV_soil_path" + soil.albedo_color = Color(.36, .31, .24) + ground.material_override = Sanctuary.terrain_material(soil) + add_child(ground) + for index: int in range(IDS.size()): + var figure: Node3D = AssetLibrary.instantiate_fighter(IDS[index]) + if figure == null: + push_error("missing figure " + IDS[index]) + continue + figure.position = Vector3((float(index) - 3.0) * 1.7, 0, 0) + figure.rotation.y = deg_to_rad(18) + add_child(figure) + figures.append(figure) + var label := Label3D.new() + var record: Dictionary = {} + for entry: Dictionary in JSON.parse_string(FileAccess.get_file_as_string("res://assets/luneraria/peoples_manifest.json")).assets: + if entry.id == IDS[index]: + record = entry + label.text = "%s\n%s" % [record.get("people", IDS[index]), record.get("name", "")] + label.font_size = 44 + label.pixel_size = .004 + label.outline_size = 10 + label.position = figure.position + Vector3(0, -0.02, 1.1) + label.rotation.x = deg_to_rad(-60) + add_child(label) + camera = Camera3D.new() + camera.fov = 34 + add_child(camera) + camera.look_at_from_position(Vector3(0, 2.4, 10.4), Vector3(0, .95, 0)) + camera.current = true + call_deferred("run") + +func run() -> void: + DirAccess.make_dir_recursive_absolute(ProjectSettings.globalize_path(out)) + for pose: Array in POSES: + for figure: Node3D in figures: + figure.preview(pose[0], pose[1]) + for frame: int in range(8): + await get_tree().process_frame + get_viewport().get_texture().get_image().save_png("%s/peoples-%s.png" % [out, pose[2]]) + print("LINEUP ", pose[2]) + # Close-up of the first three and the last four for detail. + for group: Array in [[0, 3, "a"], [3, 7, "b"]]: + var centre: float = ((float(group[0]) + float(group[1]) - 1.0) / 2.0 - 3.0) * 1.7 + camera.fov = 26 + camera.look_at_from_position(Vector3(centre, 1.9, 6.2), Vector3(centre, 1.0, 0)) + for figure: Node3D in figures: + figure.preview("idle", 0.6) + for frame: int in range(8): + await get_tree().process_frame + get_viewport().get_texture().get_image().save_png("%s/peoples-detail-%s.png" % [out, group[2]]) + get_tree().quit() diff --git a/tools/luneraria/people_lineup.gd.uid b/tools/luneraria/people_lineup.gd.uid new file mode 100644 index 0000000..2071165 --- /dev/null +++ b/tools/luneraria/people_lineup.gd.uid @@ -0,0 +1 @@ +uid://clfe7ktxl1f5f diff --git a/tools/luneraria/people_lineup.tscn b/tools/luneraria/people_lineup.tscn new file mode 100644 index 0000000..76f6376 --- /dev/null +++ b/tools/luneraria/people_lineup.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=3] + +[ext_resource type="Script" path="res://tools/luneraria/people_lineup.gd" id="1"] + +[node name="PeopleLineup" type="Node3D"] +script = ExtResource("1") diff --git a/tools/luneraria/stress_run.gd b/tools/luneraria/stress_run.gd new file mode 100644 index 0000000..64ec2da --- /dev/null +++ b/tools/luneraria/stress_run.gd @@ -0,0 +1,99 @@ +extends Node +## Stress tests, separate from balance: (1) very high wave indices must stay finite and +## capped; (2) a long mirror match must not grow memory or per-round cost. The QA round +## limit ends the run as "Testlimit erreicht / Partie nicht abgeschlossen". +## Run: godot --headless --path . res://tools/luneraria/stress_run.tscn +var checks: int = 0 +var failures: Array[String] = [] + +func check(ok: bool, label: String) -> void: + checks += 1 + if not ok: + failures.append(label) + printerr("STRESS_FAIL: ", label) + +func _ready() -> void: + call_deferred("run") + +func run() -> void: + var report := {"high_index": [], "long_run": {}} + for index: int in [100, 1000, 10000, 1000000]: + var state := LuneMatch.new() + state.setup({"seed": 5, "human_slot": -1}) + state.round_number = index + for slot: int in range(6): + state.players[slot].draft.gold = 5000 + LuneBot.plan(state, slot, "ausgewogen") + var started: int = Time.get_ticks_msec() + state.begin_combat() + var peak: int = state.combat.mobile_count() + var finite: bool = true + var steps: int = 0 + while state.phase == LuneMatch.Phase.COMBAT: + state.step_combat() + steps += 1 + if state.phase == LuneMatch.Phase.COMBAT: + peak = maxi(peak, state.combat.mobile_count()) + if steps % 50 == 0: + for unit: LuneUnit in state.combat.units: + finite = finite and is_finite(unit.hp) and is_finite(unit.position.x) and is_finite(unit.position.y) + var cap: int = 6 * state.balance.wave_max_count + 6 * (state.balance.supply_cap + 1) + 6 * LuneContent.MAX_BOOKINGS + 2 + check(finite, "wave %d: all values finite" % index) + check(peak <= cap, "wave %d: at most %d mobile units (peak %d)" % [index, cap, peak]) + check(steps <= int(state.balance.combat_time_limit / state.balance.sim_step) + 1, "wave %d: combat phase terminates" % index) + var row := {"wave": index, "peak_mobile": peak, "steps": steps, "wall_ms": Time.get_ticks_msec() - started, + "end": state.last_settlement.get("end_reason", ""), "phase": "vorbei" if state.phase == LuneMatch.Phase.OVER else "läuft", + "monster_hp": LuneWaves.build(state.balance, index)[0].max_hp} + report.high_index.append(row) + print("STRESS_WAVE ", JSON.stringify(row)) + # Long run over the whole loop (plan -> combat -> settlement). A normal mirror match ends + # in a draw after 7-14 waves, so this run uses a documented test fixture: before every + # round all towers are restored and both Ancients get 10^12 HP. It measures memory, + # object count and cost per round, not balance; the QA limit then ends the run as + # "Testlimit erreicht / Partie nicht abgeschlossen". + var state := LuneMatch.new() + state.setup({"seed": 3, "human_slot": -1, "bunds": ["wurzelbund", "wurzelbund", "wurzelbund", "wurzelbund", "wurzelbund", "wurzelbund"]}) + var limit: int = 200 + var round_ms: Array[float] = [] + var memory: Array[float] = [Performance.get_monitor(Performance.MEMORY_STATIC) / 1048576.0] + var objects: Array[float] = [Performance.get_monitor(Performance.OBJECT_COUNT)] + for definition: Dictionary in state.map.buildings: + if definition.tier == "core": + definition.max_hp = 1.0e12 + while state.phase != LuneMatch.Phase.OVER and state.round_number <= limit: + for definition: Dictionary in state.map.buildings: + state.building_hp[definition.id] = float(definition.max_hp) + var started: int = Time.get_ticks_usec() + for slot: int in range(6): + LuneBot.plan(state, slot, "verteidigung") + state.begin_combat() + state.finish_combat() + round_ms.append((Time.get_ticks_usec() - started) / 1000.0) + if round_ms.size() % 25 == 0: + memory.append(Performance.get_monitor(Performance.MEMORY_STATIC) / 1048576.0) + objects.append(Performance.get_monitor(Performance.OBJECT_COUNT)) + print("STRESS_PROGRESS round %d · %.0f ms · %.1f MB · %d objects" % [state.round_number, round_ms[-1], memory[-1], objects[-1]]) + var result: String = "Testlimit erreicht / Partie nicht abgeschlossen" if state.phase != LuneMatch.Phase.OVER else ("Unentschieden" if state.winner == LuneMatch.DRAW else "Sieg %s" % ("Blau" if state.winner == 0 else "Rot")) + var early: float = 0.0 + var late: float = 0.0 + var window: int = mini(10, round_ms.size()) + for i: int in range(window): + early += round_ms[i] / float(window) + late += round_ms[round_ms.size() - 1 - i] / float(window) + check(round_ms.size() >= limit, "long run reaches the QA limit of %d rounds (%d played)" % [limit, round_ms.size()]) + check(objects[-1] <= objects[0] * 1.05 + 100, "no object growth over %d rounds (%s)" % [round_ms.size(), str(objects)]) + check(memory[-1] <= memory[0] * 1.10 + 8.0, "no memory growth over %d rounds (%s MB)" % [round_ms.size(), str(memory)]) + check(result != "Sieg Blau" and result != "Sieg Rot", "mirror match never produces a one-sided winner") + var finite_gold: bool = state.players.all(func(p: Dictionary) -> bool: return int(p.gold) >= 0 and int(p.gold) < 1 << 40) + check(finite_gold, "gold stays a valid integer") + report.long_run = {"rounds": round_ms.size(), "result": result, "round_ms_first10": early, "round_ms_last10": late, + "memory_mb_every25": memory, "objects_every25": objects, "final_gold": state.players.map(func(p: Dictionary) -> int: return int(p.gold)), + "final_wave_monster_hp": LuneWaves.build(state.balance, state.round_number)[0].max_hp} + print("STRESS_LONG ", JSON.stringify(report.long_run)) + report.checks = checks + report.failures = failures + var file := FileAccess.open("res://docs/luneraria/logs/m3/stress.json", FileAccess.WRITE) + file.store_string(JSON.stringify(report, "\t")) + file.close() + print("STRESS_%s: %d checks; %d failures" % ["PASS" if failures.is_empty() else "FAIL", checks, failures.size()]) + get_tree().quit(0 if failures.is_empty() else 1) diff --git a/tools/luneraria/stress_run.gd.uid b/tools/luneraria/stress_run.gd.uid new file mode 100644 index 0000000..a400021 --- /dev/null +++ b/tools/luneraria/stress_run.gd.uid @@ -0,0 +1 @@ +uid://c15m8ejq2binv diff --git a/tools/luneraria/stress_run.tscn b/tools/luneraria/stress_run.tscn new file mode 100644 index 0000000..04056a2 --- /dev/null +++ b/tools/luneraria/stress_run.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=3] + +[ext_resource type="Script" path="res://tools/luneraria/stress_run.gd" id="1"] + +[node name="StressRun" type="Node"] +script = ExtResource("1") diff --git a/tools/luneraria/verify_m2.gd.uid b/tools/luneraria/verify_m2.gd.uid new file mode 100644 index 0000000..6af7f05 --- /dev/null +++ b/tools/luneraria/verify_m2.gd.uid @@ -0,0 +1 @@ +uid://cc6acimfh08jd